I’m attempting to implement a fold over a discriminated union. The DU is called Expr, and represents a program expression, and is often recursive. I’m attempting to write a fold that accumulates the result of an operation over the Exprs recursively. Below is my attempt to write the fold.
let rec foldProceduralExpr (folder : 's -> Expr list -> 's) (state : 's) (expr : Expr) : 's =
let children =
match expr with
| Series s -> s.SerExprs
| Lambda l -> [l.LamBody; l.LamPre; l.LamPost]
| Attempt a -> a.AttemptBody :: List.map (fun ab -> ab.ABBody) a.AttemptBranches
| Let l -> l.LetBody :: List.concat (List.map (fun lb -> match lb with LetVariable (_, expr) -> [expr] | LetFunction (_, _, body, _, pre, post, _) -> [body; pre; post]) l.LetBindings)
| Case c -> c.CaseTarget :: List.concat (List.map (fun branch -> [branch.TBBody; branch.TBTest]) c.CaseBranches)
| Condition c -> List.concat (List.map (fun branch -> [branch.TBBody; branch.TBTest]) c.CondBranches)
| List l -> l.ListElements
| Array a -> Array.toList a.ArrElements
| Composite c -> LunTrie.toValueList (LunTrie.map (fun _ mem -> mem.MemExpr) c.CompMembers)
| _ -> []
let listFolder = fun state expr -> foldProceduralExpr folder state expr
let listFolded = List.fold listFolder state children
folder state (expr :: listFolded)
The problem is that the code doesn’t work, and this is known because I get the error the construct causes code to be less generic than indicated by the type annotations. The type variable 's has been constrained to be type 'Expr list' on listFolded on the last line. This is because the definition of foldProceduralExpr is almost certainly wrong.
Now, I’d love to fix the code and consequently the type error, but I simply don’t know how. I suppose what I lack is an understanding in how folds over non-list or recursive data structures work. I recently translated a fold for a trie from OCaml to F#, and had a lot of trouble understanding how that fold worked as well.
Question 1: Is there a visible fix for this code that I can just understand slap in?
Question 2: Is there a resource for gaining the background needed to understand how to write these type of folds?
Please let me know if you need more information. I’ve not included the DU as it’s too big and complicated to keep the question clear. Hopefully suffice to say, it’s your typical Lisp-style AST data structure.
Follow up question: I’d also like to write a map with that fold once it works. Would that look like a typical map, or would that take some extra brain-power to figure out too?
EDIT: With regards to Tomas’ help, I’ve turned the last three lines into –
let exprs = expr :: children
let listFolder = fun state expr -> foldProceduralExpr folder state expr
let listFolded = List.fold listFolder state exprs
folder listFolded exprs
I hope this still makes sense.
EDIT: Here’s my final solution. It’s generalized over get children –
let rec foldRdu (getChildren : 't -> 't list) (folder : 's -> 't -> 't list -> 's) (state : 's) (parent : 't) : 's =
let children = getChildren parent
let listFolder = fun state child -> foldRdu getChildren folder state child
let listFolded = List.fold listFolder state children
folder listFolded parent children
The reason I’m taking an Expr and Expr list as the fold value it that I want to preserve the structure of the parent / children relationship to use later. This is a very domain-specific fold, in a way I suppose.
The first question is, what kind of fold do you want to implement? There is a number of options for tree-like structures. Assuming you have an n-ary tree (that is, any number of children), you can:
Exprnodes in the leafsIn your example, your folder function takes
Expr listwhich is a bit confusing to me – I would think you could just give it the currentExprand not a list of children, but calling it on children is an option too.You’re first making a recursive call and then calling the folder on current expression, so I suppose you wanted to implement the option 2. I can understand the first two lines of your folding:
However, the last line is probably wrong:
I assume you probably want to use
listFoldedas the state passed to thefolder. The folder takes a list of expressions, so you can give itchildrenas the second argument (or you can changefolderto take just a singleExprand then give it justexpr, which would IMHO make more sense). Anyway, this should make it work, so that you can run it and see what is going on: