组合两个函数得到一个 returns HList 的函数

Composing two functions to get a function that returns HList

这可能是一个关于 shapeless 的非常幼稚的问题:

假设我有函数 A => M[B]B => M[C]。我如何组合它们以获得新功能 A => M[B::C::HNil] ?

这应该可以解决问题

for {
  b <- f(a)
  c <- g(b)
} yield b :: c :: HNil

这当然会扩展到

f(a) flatMap { b =>
  g(b) map { c => b :: c :: HNil }
}

你需要定义 M[_] 是某种形式的 Monad 所以你可以 flatMap 它,我选择使用 scalaz Monad :

  import scalaz._, Scalaz._
  import shapeless._
  def compose[M[_] : Monad, A, B, C](f: A => M[B], g: B => M[C]): A => M[B :: C :: HNil] = {
    a => f(a).flatMap(b => g(b).map(c => b :: c :: HNil))
  }

如果您想通用地执行此操作,您可以使用 Scalaz 的 Arrow:

import scalaz._, Scalaz._

def andThenButKeep[Arr[_, _]: Arrow, A, B, C](
  f: Arr[A, B],
  g: Arr[B, C]
): Arr[A, (B, C)] = f >>> (Category[Arr].id &&& g)

或者如果你想要一个 HList 而不是一个元组:

import scalaz._, Scalaz._
import shapeless._, shapeless.syntax.std.tuple._

def andThenButKeep[Arr[_, _], A, B, C](
  f: Arr[A, B],
  g: Arr[B, C]
)(implicit Arr: Arrow[Arr]): Arr[A, B :: C :: HNil] =
  f >>> (Arr.id &&& g) >>> Arr.arr((_: (B, C)).productElements)

现在您可以将函数包装在 Kleisli 箭头中:

type OptionFunc[A, B] = Kleisli[Option, A, B]

val f: OptionFunc[Int, String] = Kleisli(i => Some("a" * i))
val g: OptionFunc[String, Int] = Kleisli(s => Some(s.length))

val c = andThenButKeep(f, g)

然后:

scala> println(c.run(10))
Some(aaaaaaaaaa :: 10 :: HNil)

通过将箭头限制为 M.

上的 Kleisli 箭头,您可以使类型推断不那么挑剔(但也不那么通用)