Oh wow, this is an old one! I'll start by cleaning up the code a bit and pulling it into line with current idiomatic conventions:
case class Flat[T, U](fn: T => List[U])
implicit def recFlattenFn[T, U](
implicit f: Flat[T, U] = Flat((xs: T) => List(xs))
) = Flat((xs: List[T]) => xs flatMap f.fn)
def recFlatten[T, U](xs: List[T3])(implicit f: Flat[List[T], U]) = f fn xs
Then, without further ado, break down the code. First, we have our Flat
class:
case class Flat[T, U](fn: T => List[U])
This is nothing more than a named wrapper for the function T => List[U]
, a function that will build a List[U]
when given an instance of type T
. Note that T
here could also be a List[U]
, or a U
, or a List[List[List[U]]]
, etc. Normally, such a function could be directly specified as the type of a parameter. But we're going to be using this one in implicits, so the named wrapper avoids any risk of an implicit conflict.
Then, working backwards from recFlatten
:
def recFlatten[T, U](xs: List[T])(implicit f: Flat[List[T], U]) = f fn xs
This method will take xs
(a List[T]
) and convert it to a U
. To achieve this, it locates an implicit instance of Flat[T,U]
and invokes the enclosed function, fn
Then, the real magic:
implicit def recFlattenFn[T, U](
implicit f: Flat[T, U] = Flat((xs: T) => List(xs))
) = Flat((xs: List[T]) => xs flatMap f.fn)
This satisfies the implicit parameter required by recFlatten
, it also takes another implicit paramater. Most crucially:
recFlattenFn
can act as its own implicit parameter
- it returns a Flat[List[X], X], so
recFlattenFn
will only be implicitly resolved as a Flat[T,U]
if T
is a List
- the implicit
f
can fallback to a default value if implicit resolution fails (i.e. T
is NOT a List
)
Perhaps this is best understood in the context of one of the examples:
recFlatten(List(List(1, 2, 3), List(4, 5)))
- The type
T
is inferred as List[List[Int]]
- implicit lookup is attempted for a `Flat[List[List[Int]], U]
- this is matched by a recursively defined
recFlattenFn
Broadly speaking:
recFlattenFn[List[List[Int]], U] ( f =
recFlattenFn[List[Int], U] ( f =
Flat[Int,U]((xs: T) => List(xs)) //default value
)
)
Note that recFlattenFn
will only match an implicit search for a Flat[List[X], X]
and the type params [Int,_]
fail this match because Int
is not a List
. This is what triggers the fallback to the default value.
Type inference also works backwards up that structure, resolving the U
param at each level of recursion:
recFlattenFn[List[List[Int]], Int] ( f =
recFlattenFn[List[Int], Int] ( f =
Flat[Int,Int]((xs: T) => List(xs)) //default value
)
)
Which is just a nesting of Flat
instances, each one (except the innermost) performing a flatMap
operation to unroll one level of the nested List
structure. The innermost Flat
simply wraps all the individual elements back up in a single List
.
Q.E.D.