I was reading the original paper about data types a la carte and decided to try to implement the idea in Scala (I know it's already implemented in many functional libraries). Unfortunately I found the original paper is hard to comprehend and I stuck somewhere in the beginning. Then I found another paper that was easier to understand and I managed to rewrite Haskell code from the paper into Scala, you can find it here. However I still struggling to understand a few moments:
Orignal Expr
data type
data Expr = Val Int | Add Expr Expr
New type signature:
data Arith e = Val Int | Add e e
For any functor
f
, its induced recursive datatype,Fix f
, is defined as the least fixpoint off
, implemented as follows:
data Fix f = In (f (Fix f))
Now that we have tied the recursive knot of a signature,
Fix Arith
is a language equivalent to the originalExpr
datatype which allowed integer values and addition.
What does it mean exactly "we have tied the recursive knot of a signature" and what does it mean Fix Arith
is a language equivalent to the original Expr
?
The actual type of In
is In :: f (Fix f) -> Fix f
If we try to construct a value using In
construct and Val 1
variable we'll get the following result:
> :t In(Val 1)
> In(Val 1) :: Fix Arith
Scala encoding of the same data types:
sealed trait Arith[A]
case class Val[A](x: Int) extends Arith[A]
case class Add[A](a: A, b: A) extends Arith[A]
trait Fix[F[_]]
case class In[F[_]](exp: F[Fix[F]]) extends Fix[F]
fold
function
The fold
function has the following signature and implementationHaskell:
fold :: Functor f => (f a -> a) -> Fix f -> a
fold f (In t) = f (fmap (fold f) t)
Scala variant I came up with
def fold[F[_] : Functor, A](f: F[A] => A): Fix[F] => A = {
case In(t) =>
val g: F[Fix[F]] => F[A] = implicitly[Functor[F]].lift(fold(f))
f(g(t))
}
The thing that I'm curious about is that in my Scala version function g
has the following type F[Fix[F]] => F[A]
but the type of variable t
after pattern matching is LaCarte$Add
with value Add(In(Val(1)),In(Val(2)))
, how it happens that it's valid to apply function g
to LaCarte$Add
? Also, I'd very appreciate if you can help me to understand fold
function ?
Quote from the paper:
The first argument of fold is an f-algebra, which provides the behavior of each constructor associated with a given signature
f
.
What does it mean exactly “we have tied the ‘recursive knot’ of a signature”?
The original Expr
datatype is recursive, referring to itself in its own definition:
data Expr = Val Int | Add Expr Expr
The Arith
type “factors out” the recursion by replacing recursive calls with a parameter:
data Arith e = Val Int | Add e e
The original Expr
type can have any depth of nesting, which we want to support with Arith
as well, but the maximum depth depends on what type we choose for e
:
Arith Void
can’t be nested: it can only be a literal value (Val n
) because we can’t construct an Add
, because we can’t obtain a value of type Void
(it has no constructors)
Arith (Arith Void)
can have one level of nesting: the outer constructor can be an Add
, but the inner constructors can only be Lit
.
Arith (Arith (Arith Void))
can have two levels
And so on
What Fix Arith
gives us is a way to talk about the fixed point Arith (Arith (Arith …))
with no limit on the depth.
This is just like how we can replace a recursive function with a non-recursive function and recover the recursion with the fixed-point combinator:
factorial' :: (Integer -> Integer) -> Integer -> Integer
factorial' recur n = if n <= 1 then 1 else n * recur (n - 1)
factorial :: Integer -> Integer
factorial = fix factorial'
factorial 5 == 120
What does it mean
Fix Arith
is a language equivalent to the originalExpr
?
The language (grammar) that Fix Arith
represents is equivalent to the language that Expr
represents; that is, they’re isomorphic: you can write a pair of total functions Fix Arith -> Expr
and Expr -> Fix Arith
.
How it happens that it’s valid to apply function
g
toLaCarte$Add
?
I’m not very familiar with Scala, but it looks like Add
is a subtype of Arith
, so the parameter of g
of type F[Fix[F]]
can be filled with a value of type Arith[Fix[Arith]]
which you get by matching on the In
constructor to “unfold” one level of recursion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With