Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reduce with andThen across functions of different types

Tags:

scala

I want to programmatically compose several functions. If these functions are all of the same type, I can do the following:

def a(x: Int): Int = x+1
def b(y: Int): Int = y/2
def c(z: Int): Int = z*4
val f1 = (a _) andThen (b _) andThen (c _)
val f2 = List((a _), (b _), (c _)).reduce(_ andThen _)

At which point f1 and f2 are the same thing, and this compiles because the List that defines f2 is a List[Function1[Int,Int]]

However, if I want to chain together multiple compatible functions with different types using the same basic reduce technique, I get an error.

def d(x: Double): Int = x.toInt
def e(y: Int): String = y.toString
def f(z: String): Double = z.toDouble*4

//Works fine
val f3 = (d _) andThen (e _) andThen (f _)

//Doesn't compile
val f4 = List((d _), (e _), (f _)).reduce(_ andThen _)

The second option doesn't compile because the list that defines f4 is inferred as a List[Function1[Any,Any]], but I can't figure out if theres a clean type-safe way to take an ordered collection of functions of the form Function1[A,B],Function1[B,C],Function1[C,D],...,Function1[X,Y] and glue them together as a Function1[A,Y] like this.

Any ideas?

like image 872
evanrsparks Avatar asked Mar 05 '14 23:03

evanrsparks


1 Answers

There are two problems here. The first (as you've noted) is that the list has a single element type, which will be inferred to be the least upper bound of the types of the elements it contains, which in this case is the extremely boring and useless String with Int with Double => Any. Heterogeneous lists provide one way of addressing this part of the problem, as I'll show in a second.

The second problem is that the _ andThen _ is insufficiently polymorphic (as Bob Dalgleish points out in a comment above). The argument to reduce will be a function with a concrete input type and a concrete output type, so even if we had a heterogeneous list, there's no way we could reduce it with a Function from the Scala standard library—we'd need a polymorphic function value instead.

Fortunately (if you really want to do this kind of thing in Scala), there's a great library called Shapeless that provides nice implementations of both heterogeneous lists and polymorphic functions. For example, you could write the following:

def d(x: Double): Int = x.toInt
def e(y: Int): String = y.toString
def f(z: String): Double = z.toDouble * 4

import shapeless._

object andThen extends Poly2 {
  implicit def functions[A, B, C] = at[A => B, B => C](_ andThen _)
}

And then:

scala> val andThenned = HList((d _), (e _), (f _)).reduceLeft(andThen)
andThenned: Double => Double = <function1>

scala> andThenned(13.0)
res0: Double = 52.0

I think this is pretty neat.

like image 157
Travis Brown Avatar answered Nov 13 '22 06:11

Travis Brown