Scala中andThen与compose组合函数行为差异及类型推导问题
问题:Monad map实现中
compose vs andThen的类型推断差异 我在学习《Scala with Cats》的练习4.1.2(Getting Func-y)时,需要仅用pure和flatMap实现Monad trait的map方法。给定的代码框架如下:
import scala.language.higherKinds trait Monad[F[_]] { def pure[A](a: A): F[A] def flatMap[A, B](value: F[A])(func: A => F[B]): F[B] def map[A, B](value: F[A])(func: A => B): F[B] = ??? }
尝试用pure _ compose func实现时,Scala 2.13.8编译报错:
[error] 7:35: type mismatch; [error] A => B|scala.Nothing [error] flatMap(value)(pure _ compose func) [error] ^ [info] A => B <;: ? => Nothing? [info] false [error] 6:30: parameter value func in method map is never used [error] def map[A, B](value: F[A])(func: A => B): F[B] = [error] ^ [error] two errors found [error] (Compile / compileIncremental) Compilation failed
Scala 3.2.1中用自动eta展开的pure compose func也报错:
-- [E007] Type Mismatch Error: -------------------------------------------------------- 7 | flatMap(value)(pure compose func) | ^^^^^^^^^^^^^^^^^ | Found: A => F[Any] | Required: A => F[B] | | where: F is a type in trait Monad with bounds <: [_] =>> Any
但使用func andThen pure却能正常编译。明明f andThen g和g compose f理论上等价,为什么这里不成立?
原因:类型推断的方向约束差异
核心问题在于Scala对多态方法的eta展开类型推断方向不同,两种函数组合方式给编译器的类型约束上下文完全相反:
1. func andThen pure的正确推导
func的类型是明确的A => B,andThen操作要求后续函数必须接收B作为输入。- 编译器会基于这个约束,将
pure实例化为pure[B]: B => F[B]——因为pure需要接收B类型的参数来生成F[B],正好匹配andThen的要求。 - 最终组合结果是
A => F[B],完全符合flatMap第二个参数所需的A => F[B]类型。
2. pure _ compose func的推导失败
compose操作是将func作为前置函数,要求前面的函数(即pure的eta展开版本)必须接收func的输出类型B作为输入。但问题在于:
pure是一个多态方法,当没有明确的类型约束时,Scala无法自动推断出要将pure的类型参数设为B:- 在Scala 2中,编译器会默认将无约束的多态方法eta展开为最严格的类型,即
pure[Nothing]: Nothing => F[Nothing]。此时compose func要求B <: Nothing,这只有B是Nothing时才成立,显然不符合通用场景,因此报错。 - 在Scala 3中,自动eta展开会推断为最宽泛的类型,即
pure[Any]: Any => F[Any],组合后得到A => F[Any],但flatMap需要的是A => F[B],F[Any]无法向下转型为F[B](除非B=Any),因此类型不匹配。
- 在Scala 2中,编译器会默认将无约束的多态方法eta展开为最严格的类型,即
修复方案:明确指定
pure的类型参数 如果一定要用compose写法,只需手动指定pure的类型参数,给编译器明确的约束:
Scala 2写法:
def map[A, B](value: F[A])(func: A => B): F[B] = flatMap(value)(pure[B] _ compose func)
Scala 3写法:
def map[A, B](value: F[A])(func: A => B): F[B] = flatMap(value)(pure[B] compose func)
这样pure会被正确实例化为B => F[B],和func组合后得到符合要求的A => F[B]类型,编译即可通过。
内容的提问来源于stack exchange,提问作者Joshua Gloor
相关产品推荐
相关产品推荐

