I'm following this article on catamorphism and I'm trying to define a fold function for a recursive data type like this
type Node anyType
= Leaf Id (Maybe anyType)
| Tree Id (List (Node anyType))
What i wrote is this:
foldTree fLeaf fTree acc node =
let
recurse =
foldTree fLeaf fTree
in
case node of
Leaf id v ->
let
newAcc = fLeaf acc (id, v)
in
newAcc
Tree id l ->
let
newAcc = fTree acc id
in
l |> List.foldl recurse newAcc
If i don't infer the types for foldTree
the function compiles, but it seems to not be usable:
collectIds node =
let
fLeaf acc (id,v) = id :: acc
fTree acc id = id :: acc
in
foldTree fLeaf fTree [] node
throws the following:
TYPE MISMATCH - The 1st argument to `foldTree` is not what I expect:
173| foldTree fLeaf fTree [] node
#^^^^^#
This `fLeaf` value is a:
#List a# -> ( a, b ) -> #List a#
But `foldTree` needs the 1st argument to be:
#Node anyType# -> ( Id, Maybe anyType ) -> #Node anyType#
Auto inferring the types for foldTree
makes it not compilable and throws the following:
-- Auto Inferred
foldTree : (c -> (Id, Maybe anyType) -> a) -> (c -> Id -> b) -> c -> Node anyType -> d
TYPE MISMATCH - Something is off with the 1st branch of this `case` expression:
126| newAcc
#^^^^^^#
This `newAcc` value is a:
#a#
But the type annotation on `foldTree` says it should be:
#d#
#Hint#: Your type annotation uses `a` and `d` as separate type variables. Your
code seems to be saying they are the same though. Maybe they should be the same
in your type annotation? Maybe your code uses them in a weird way?
and if I try to follow the hint, still does not compile
foldTree : (c -> (Id, Maybe anyType) -> a) -> (c -> Id -> b) -> c -> Node anyType -> a
TYPE MISMATCH - This function cannot handle the argument sent through the (|>) pipe:
134| l |> List.foldl recurse newAcc
#^^^^^^^^^^^^^^^^^^^^^^^^^#
The argument is:
List #(Node anyType)#
But (|>) is piping it to a function that expects:
List #c#
#Hint#: Your type annotation uses type variable `c` which means ANY type of value
can flow through, but your code is saying it specifically wants a `Node` value.
Maybe change your type annotation to be more specific? Maybe change the code to
be more general?
Read <https://elm-lang.org/0.19.1/type-annotations> for more advice!Elm
TYPE MISMATCH - The 1st argument to `foldl` is not what I expect:
134| l |> List.foldl recurse newAcc
#^^^^^^^#
This `recurse` value is a:
c -> Node anyType -> #a#
But `foldl` needs the 1st argument to be:
c -> Node anyType -> #Node anyType#
#Hint#: Your type annotation uses type variable `a` which means ANY type of value
can flow through, but your code is saying it specifically wants a `Node` value.
Maybe change your type annotation to be more specific? Maybe change the code to
be more general?
I'm stuck. Following the types on the article precisely also does not seem to work. I understand that the code on the article is F# and I'm working on Elm but I thought that in this case it would have been 100% translatable.
Where am I going wrong?
Thanks in advance!
You've flipped the arguments to List.foldl
. The fold function takes the value first and the accumulator second, while your recurse
function takes the accumulator first and the value second.
The simple fix to this is to eta expand the recurse function and flip the arguments when passing it to foldTree
:
recurse v a = foldTree fLeaf fTree a v
Also, interestingly, annotating the type of recurse
will make it compile, but obviously produces the wrong result. I didn't pursue this further to understand why, as it was wrong, but the lesson you should take from this is to always annotate your top level functions. This will give you better error messages, but also prevents your code from accidentally compiling but producing the wrong result.