Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Composing two functions to get a function that returns HList

This is probably a very naive question about shapeless:

Suppose I have functions A => M[B] and B => M[C]. How can I compose them to get a new function A => M[B::C::HNil] ?

like image 458
Michael Avatar asked Jul 23 '15 12:07

Michael


3 Answers

If you want to do this generically you can use Scalaz's 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)

Or if you want an HList instead of a tuple:

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)

Now you'd wrap your functions in a Kleisli arrow:

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)

And then:

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

You could make this a little less fussy about type inference (but also less generic) by restricting the arrow to a Kleisli arrow over your M.

like image 136
Travis Brown Avatar answered Sep 22 '22 19:09

Travis Brown


You need to define that M[_] is some form of a Monad so you can flatMap it, I chose to use the 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))
  }
like image 26
Noah Avatar answered Sep 26 '22 19:09

Noah


This should do the trick

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

which of course expands to

f(a) flatMap { b =>
  g(b) map { c => b :: c :: HNil }
}
like image 23
mfirry Avatar answered Sep 24 '22 19:09

mfirry