如何在 Scala 3 和 Cats 中实现类似的 Haskell 函数?
ghci> (+) <$> (+1) <*> (+1) $ 10
22
有一个使用
mapN
的解决方案。这里提到了,使用函数作为应用函子/笛卡尔,但我想要一个使用<*>
而不是mapN
的解决方案。我尝试了类似的方法,但无法编译
import cats._
import cats.implicits._
type Func1[Y] = Int => Y
val add1: Func1[Int] = (x: Int) => x + 1
val addXY: Func1[Func1[Int]] = (x: Int) => (y: Int) => x + y
val foo: Func1[Func1[Func1[Int]]] = Applicative[Func1].pure(addXY)
val bar: Func1[Func1[Int]] = Applicative[Func1].pure(add1)
val foobar = foo <*> bar
foobar(10) // should be 22 but it can't compile
请指教。谢谢
Scala Cats 承认函数是
Applicative
实例,就像 Haskell 一样。你写的Haskell代码是
(+) <$> (+1) <*> (+1) $ 10
我们可以在 Scala 中将这些函数编写为
def add1(x: Int) = x + 1
def addXY(x: Int)(y: Int) = x + y
现在
<*>
的工作方式与 Haskell 中一样,但 <$>
不作为中缀存在(尤其是因为 $
不是基于 JVM 的语言中的符号字符)。请记住,f <$> x
相当于 fmap f x
,或(根据 Applicative
定律)pure f <*> x
。
所以我们可以写
val foobar = addXY.pure[[X] =>> Function1[Int, X]] <*> add1 <*> add1
println(foobar(10))
现在,虽然 Cats 没有提供据我所知的中缀
<$>
,但我们可以轻松地自己编写一个中缀作为扩展方法。我们不能将其命名为 <$>
,因为美元符号在 JVM 上很有趣,但我们可以改为写 <@>
。
extension[A, B](left: A => B)
def <@>[F[_]: Functor](right: F[A]): F[B] =
right.fmap(left)
现在我们可以说
val foobar = addXY <@> add1 <*> add1
println(foobar(10))
完整示例:
package example
import cats.*
import cats.implicits.*
def add1(x: Int) = x + 1
def addXY(x: Int)(y: Int) = x + y
extension[A, B](left: A => B)
def <@>[F[_]: Functor](right: F[A]): F[B] =
right.fmap(left)
@main def main(args: String*): Unit = {
val foobar = addXY <@> add1 <*> add1
println(foobar(10))
}
以上所有内容都是用 Scala 3 编写的。如果您使用 Scala 2,那么您必须做一些混乱的事情才能让 Scala 按照您想要的方式处理函数。正如您已经看到的,您必须编写一个
type
别名才能获得正确的类型级通用行为(例如,如果您想调用 pure
)。 def
函数必须显式转换为带有后缀 _
的函数。并且 extension
方法需要是老式的 implicit class
。
总而言之,这是用 Scala 2 编写的相同程序。
package example
import cats._
import cats.implicits._
import scala.language.higherKinds
object Main {
implicit class FmapExtension[A, B](
val left: A => B,
) extends AnyVal {
def <@>[F[_]: Functor](right: F[A]): F[B] =
right.fmap(left)
}
def add1(x: Int) = x + 1
def addXY(x: Int)(y: Int) = x + y
def main(args: Array[String]): Unit = {
val foobar = (addXY _) <@> (add1 _) <*> (add1 _)
println(foobar(10))
}
}