【问题标题】:Scala: How to define generic flatMap and map methodsScala:如何定义通用 flatMap 和 map 方法
【发布时间】:2020-10-14 05:55:19
【问题描述】:

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

我使用 Scala 已经有一段时间了,并且一直在阅读函数式编程方面的知识,作为练习,我一直在尝试将 Haskell 中的一些基本类型实现为 Scala 特征。我目前正在尝试创建一个定义 flatMap 方法的 Monad 特征,然后可以由另一个类(例如,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 类很有效,但不适用于任何不支持的类型 采用 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 被破坏了,因为编译器不知道返回类型 C。在 flatMap 的情况下,传递给 flatMap 的函数 fn 的签名定义了返回类型 V。在 map 的情况下,参数 fn 标识的类型都没有定义返回类型 C 是什么。有什么办法可以澄清B和C与编译器的关系吗?

其他一些相关的问题/答案向我指出了使用定义 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 generics functional-programming monads flatmap


【解决方案1】:

您需要向 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] {
  ...
}

【讨论】:

  • 我试过这个但是,发现我需要将 FpWriter 更改为 FpWriter[T, A, B]( value : T, context : Monoid[ A, B ] ) extends [({ type L[X ] = FpWriter[X, A, B] })#L, T] 。但这会导致编译错误:涉及类 FpWriter 的非法循环引用。关于为什么会失败的任何想法?
  • @jives 你忘了在扩展子句中提到 Monad 特征吗?
  • 可能是。不确定问题是什么:我删除并从头开始,无法重现问题。我现在才看到我在上面的评论中遗漏了 Monad。我不记得我是复制粘贴还是打出来的……(无论如何,谢谢你的帮助!!)
  • 很抱歉花了这么长时间才将其标记为已回答:我一直在尝试以更通用的方式实现它,但从未成功。根据您的建议,我有一个不需要隐式转换的可用 Monad 类。
猜你喜欢
  • 2018-05-07
  • 1970-01-01
  • 2010-11-06
  • 2013-12-10
  • 1970-01-01
  • 2018-07-02
  • 1970-01-01
  • 2018-10-26
  • 1970-01-01
相关资源
最近更新 更多