2

尝试在 Scala 中为虚构的 Partial 类型实现 Kleisli 类别(阅读 Bartosz Milewski 的“程序员的类别理论”,这是第 4 章的练习题)

object Kleisli {
  type Partial[A, B] = A => Option[B]

  implicit class KleisliOps[A, B](f1: Partial[A, B]) {

    def >=>[C](f2: Partial[B, C]): Partial[A, C] =
      (x: A) =>
        for {
          y <- f1(x)
          z <- f2(y)
        } yield z

    def identity(f: Partial[A, B]): Partial[A, B] = x => f(x)

  }

  val safeRecip: Partial[Double, Double] = {
    case 0d => None
    case x => Some(1d / x)
  }

  val safeRoot: Partial[Double, Double] = {
    case x if x < 0 => None
    case x => Some(Math.sqrt(x))
  }
  
  val safeRootRecip: Partial[Double, Double] = safeRoot.>=>(safeRecip) 
  
  safeRootRecip(1d)
  safeRootRecip(10d)
  safeRootRecip(0d)
}

IDE (IntelliJ) 没有显示错误,但是当我运行此代码段时,我得到:

Error:(27, 57) value >=> is not a member of $line5.$read.$iw.$iw.Kleisli.Partial[Double,Double]
val safeRootRecip: Partial[Double, Double] = safeRoot.>=>(safeRecip)

>=>在隐式类之外定义工作正常。可能是什么原因?

4

1 回答 1

6

@sinanspd是对的。在 Dotty 中,代码似乎可以编译: https ://scastie.scala-lang.org/n17APWgMQkWqy93ct2cghw

手动解决

val safeRootRecip: Partial[Double, Double] = KleisliOps(safeRoot).>=>(safeRecip)

编译但编译器本身没有找到这个转换

Information: KleisliOps{<null>} is not a valid implicit value 
  for App.safeRoot.type => ?{def >=> : ?} because:
type mismatch;
 found   : App.safeRoot.type (with underlying type App.Partial[Double,Double])
 required: App.Partial[A,Double]
    (which expands to)  A => Option[Double]
  val safeRootRecip: Partial[Double, Double] = safeRoot.>=>(safeRecip)

似乎A没有推断出类型参数。

(顺便说一句,Martin Odersky 解释了为什么语言中存在隐式转换会使类型推断变得更糟:https ://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388 )

尝试使Partial关于 的协变B和(特别是)逆变A(类似于关于A => Option[B]的协变和关于 的B逆变A

type Partial[-A, +B] = A => Option[B]

然后代码似乎可以编译。

另一种解决方法是将隐式转换 ( X => Y, KleisliOps) 替换为类型类 ( MyTransform) 和myConversion根据此类型类定义的隐式转换 ()(有时这有助于隐式转换)

trait MyTransform[X, Y] {
  def transform(x: X): Y
}
implicit def myConversion[X, Y](x: X)(implicit mt: MyTransform[X, Y]): Y = 
  mt.transform(x)

type Partial[A, B] = A => Option[B]

implicit def partialToKleisliOps[A, B]: MyTransform[Partial[A, B], KleisliOps[A, B]] = 
  f1 => new KleisliOps(f1)
class KleisliOps[A, B](f1: Partial[A, B]) {    
  def >=>[C](f2: Partial[B, C]): Partial[A, C] =
    (x: A) =>
      for {
        y <- f1(x)
        z <- f2(y)
      } yield z

  def identity(f: Partial[A, B]): Partial[A, B] = x => f(x)
}
于 2020-07-06T08:34:55.613 回答