在 Cats 和 Scala 中使用应用函子函数

问题描述 投票:0回答:1

如何在 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 scala-cats
1个回答
0
投票

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))
}

斯卡拉2

以上所有内容都是用 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))
  }

}
© www.soinside.com 2019 - 2024. All rights reserved.