Scala:如何定义通用的 flatMap 和 map 方法

Scala: How to define generic flatMap and map methods

问题:如何在特征或抽象 class 上定义一个通用的 flatMap 方法,然后可以在扩展该 trait/class 的对象上实现?

我已经使用 Scala 一段时间了,并且一直在阅读一些关于函数式编程的书籍,作为练习,我一直在尝试将 Haskell 中的一些基本类型实现为 Scala特质。我目前正在尝试制作一个定义 flatMap 方法的 Monad 特征,然后可以由另一个 class 实现——例如,自定义版本的 List——我已经 运行 到一个一大堆类型问题。

起初我尝试了以下方法:

trait Monad[ T[ _ ], +A ] {

  this : T[ A ] =>  // Won't compile unless the object extending this is of type 
                    // corresponding to the above type parameters
    def flatMap[ B ]( fn : A => T[ B ] ) : T[ B ]
    def map[ B ]( fn : A => B ) : T[ B ]
}

abstract class FpList[ +T ] extends Monad[ FpList, T ] {
  def tail : FpList[ T ]
  def head : T

  override def flatMap[ B ]( fn : T => FpList[ B ] ) : FpList[ B ] = this match {
    case FpNil => FpNil
    case FpList( FpNil, v ) => fn( v )
    case FpList( next, v ) => flatMap( next ) ++ fn( v )
  }

  override def map[ B ]( fn : T => B ) : FpList[ B ] = flatMap( v => FpNil :: fn( v ) )
}

以上对我的 FpList class 很好地工作,但不适用于任何不支持的类型 采取 F[X] 的形式。我在尝试实现 Writer monad 时发现了这一点:

case class FpWriter[ T, C <: Monoid[ _ ] ]( value : T, context : C ) extends Monad[ FpWriter, T ] {
 ...
}

这里的问题是,由于FpWriter有两个类型参数,它不能作为类型参数传递给Monad[...]。如果我将 Monad 的定义更改为 trait Monad[ T[ _, _ ], +A ] 它不再接受 FpList 作为类型参数...

为了提出更通用的解决方案,我得出以下结论:

trait Monad[ T, +U <: T, +A ] {
  this : U =>
    def flatMap[ V <: T ]( fn : A => V ) : V
    def map[ B, C <: T ]( fn : A => B ) : C
}

abstract class FpList[ +T ] extends Monad[ FpList[ Any ], FpList[ T ], T ] {
  def tail : FpList[ T ]
  def head : T

  override def flatMap[ V <: FpList[ Any ] ]( fn : A => V ) : V = this match {
        case FpNil => FpNil.asInstanceOf[ V ]
        case FpList( FpNil, v : A ) => fn( v ).asInstanceOf[ V ]
        case FpList( next : FpList[ A ], v : A ) => 
          (flatMap( next )( fn ).asInstanceOf[ V ] ++ fn( v ).asInstanceOf[V]).asInstanceOf[ V ]
    }

  // Runtime error
  override def map[ B, C <: T ]( fn : A => B ) : C = flatMap( v => FpNil :: fn( v ) )
}

这解决了 flatMap 的问题!但是现在 map 被破坏了,因为编译器不知道 return 类型 C。在 flatMap 的情况下,传递给 flatMap 的函数 fn 的签名定义了 return 类型 V。在 map 的情况下, none 由参数 [=19] 标识的类型=] 定义 return C 类型。有没有办法向编译器阐明B和C之间的关系?

其他一些相关 questions/answers 向我指出了使用定义 B 和 C 之间关系的隐式参数的方向,但我不明白该怎么做。有什么想法吗?

更新

所以我通过以下方式尝试了 SimY4 的建议:

单子:

trait MonadStatic [ T[ _ ] ] extends ApplicativeStatic[ T ] {

    def flatMap[ A, B ]( a : T[ A ] )( fn : A => T[ B ] ) : T[ B ]

    override def map[ A, B ]( ele : T[ A ] )( fn : A => B ) : T[ B ] = flatMap( ele )( a => unit( fn( a ) ) )

    def sub[ X, Y ]( a : T[ X ], b : T[ Y ] ) : T[ Y ] = flatMap( a )( ( _ : X ) => b )

}

trait Monad [ T[ _ ], +A ] extends MonadStatic[ T ] with Applicative[ T, A ] {
    this : T[ A ] =>
        def flatMap[ A, B ]( fn : A => T[ B ] ) : T[ B ] = flatMap( this )( fn )
}

幺半群:

trait MonoidStatic [ T[ _ ] ] {
    def empty : T[ Nothing ]
    def emptyM : Monoid[ T, Nothing ] = empty.asInstanceOf[ Monoid[ T, Nothing ] ]
    def combine[ B  ]( a : T[ B ], b : T[ B ] ) : T[ B ]
    def combine[ B ]( a : Monoid[ T, B ], b : Monoid[ T, B ] ) : Monoid[ T, B ] = combine( a.native, b.native ).asInstanceOf[ Monoid[ T, B ] ]

    def concat[ B ]( eles : FpList[ T[ B ] ] ) : T[ B ] = eles match {
        case FpNil => empty.asInstanceOf[ T[ B ] ]
        case FpList( FpNil, head : T[ B ] ) => head
        case FpList( fpList : FpList[ T[ B ] ], head : T[ B ] ) => combine( concat( fpList ), head )
    }
}

trait Monoid[ T[ _ ], +A ] extends MonoidStatic[ T ] {

    this : T[ A ] =>
        def combine[ B >: A ]( a : T[ B ] ) : T[ B ] = combine( this.asInstanceOf[ T[ B ] ], a )
        def combine[ B >: A ]( a : Monoid[ T, B ] ) : T[ B ] = combine( a.native )
        def combineM[ B >: A ]( a : Monoid[ T, B ] ) : Monoid[ T,  B ] = combine( this.monoid, a )
        def native[ B >: A ] : T[ B ] = this.asInstanceOf[ T[ B ] ]
        def monoid[ B >: A ] : Monoid[ T, B ] = this.asInstanceOf[ Monoid[ T, B ] ]

}

现在是 FpWriter:

case class FpWriter[ T, A[ _ ], B ]( value : T, context : Monoid[ A, B ] ) extends Monad[ ({ type U[ X ] = FpWriter[ X, A, B ] })#U, T ] {
    override def flatMap[ R, S ]( a : FpWriter[ R, A, B ] )
                                ( fn : R => FpWriter[ S, A, B ] ) : FpWriter[ S, A, B ] = a match {
        case FpWriter( v : R, c : Monoid[ A, B ] ) =>
            val FpWriter( newV, newC : Monoid[ A, B ] ) = fn( v )
            FpWriter( newV, c.combineM( newC ) )
    }

    override def unit[ C ]( ele : C ) : FpWriter[ C, A, B ] = FpWriter( ele, context.emptyM )

    override def get[ C ]( ele : FpWriter[ C, A, B ] ) : Option[ C ] = Some( ele.value )
}

这会导致编译错误:“涉及 FpWriter 的非法循环引用”

问题似乎是我无法在 FpWriter 本身的签名中的 lambda 中使用 FpWriter。然而,看起来其他人已经尝试了这个的某些版本并且它有效。知道问题出在哪里吗?

更新

@SimY4 指出,在评论中我从 FpWriter 签名的扩展子句中遗漏了 Monad。当我按照他们建议的方式重写函数时,编译没有问题。这是我能够提出的不需要隐式转换的最佳解决方案。

您需要向 scala 编译器解释如何将您的 T[_, _] 转换为 T[_]。在 scala 中,一种方法是使用 lambda 类型:

case class FpWriter[T, C <: Monoid[_]]( value : T, context : C ) extends Monad[({ type L[A] = FpWriter[A, C] })#L, T] {
  ...
}