2

I've been playing around with shapeless for a bit now. But, yesterday I got stuck when trying to compose tupled functions.

What I was looking into specifically is composing two unary functions f1: T => R and f2: R => U => S into f: TU => S where T is a TupleN and TU := (t1, ... , tn, u)

  import shapeless.ops.tuple._

  implicit class Composable[T <: Product, R](val f1: T => R) extends AnyVal{
    def compose2[U, S](f2: R => U => S)(implicit p: Prepend[T, Tuple1[U]]): (p.Out => S) = {
      // how to provide the two required implicits for Last[p.Out] and Init[p.Out]?
      tu => f1.andThen(f2)(tu.init)(tu.last)
    }
  }

  val f1: ((Int, Int)) => Int = x => x._1 * x._2
  val f2: ((Int, Int, Int)) => Int = f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _

What I've been struggling with is providing the implicit proof for the tuple operations last and init, so the above code won't compile!

From a logical perspective it feels trivial as result of Prepend, but I couldn't figure out a way. So any idea is welcome :)

Using shapeless's facilities to abstract over arity I got somehow closer:

  import shapeless.ops.function.{FnFromProduct, FnToProduct}
  import shapeless.{::, HList}

  implicit class Composable[F](val f: F) extends AnyVal{
    // the new param U is appended upfront
    def compose2[I <: HList, R, U, S](f2: R => U => S)
                                     (implicit ftp: FnToProduct.Aux[F, I => R], ffp: FnFromProduct[U :: I => S]): ffp.Out = {
      ffp(list => f2.compose(ftp(f))(list.tail)(list.head))
    }
  }
  val f1: (Int, Int) => Int = (x1,x2) => x1 * x2
  val f2: (Int, Int, Int) => Int = f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _

This works, but then again I was really looking for compose2 to work on unary tupled Function1s. Also, this results in f: (U, t1, ..., tn) => S rather than f: TU => S with TU := (t1, ... , tn, u).

Moritz
  • 895
  • 4
  • 8
  • 1
    Actually, the solution is probably the Undo of Prepend Travis was suggesting here: http://stackoverflow.com/questions/32366527/splitting-an-hlist-that-was-concatenated-using-prependa-b – Moritz Sep 13 '15 at 11:04

1 Answers1

2

As Miles says, this would be more convenient with an undo for Prepend, but since the length of the second part is fixed, an approach similar to the one in my other answer isn't too bad at all:

import shapeless.ops.tuple._

implicit class Composable[T <: Product, R](val f1: T => R) extends AnyVal {
  def compose2[U, S, TU](f2: R => U => S)(implicit
    p: Prepend.Aux[T, Tuple1[U], TU],
    i: Init.Aux[TU, T],
    l: Last.Aux[TU, U]
  ): (p.Out => S) =
    tu => f1.andThen(f2)(i(tu))(l(tu))
}

And then:

scala> val f1: ((Int, Int)) => Int = x => x._1 * x._2
f1: ((Int, Int)) => Int = <function1>

scala> val f2: ((Int, Int, Int)) => Int =
     |   f1.compose2((y: Int) => (x3: Int) => x3 + y).apply _
f2: ((Int, Int, Int)) => Int = <function1>

scala> f2((2, 3, 4))
res1: Int = 10

The trick is adding the output of Prepend to the type parameter list for compose2—which will generally be inferred—and then using Prepend.Aux to make sure that it's inferred appropriately. You'll often find in Shapeless that you need to refer to the output type of a type class in other type class instances in the same implicit parameter list in this way, and the Aux type members make doing so a little more convenient.

Community
  • 1
  • 1
Travis Brown
  • 138,631
  • 12
  • 375
  • 680