I'm trying to model a "heterogeneous tree", ie. a tree where the nodes have different "kinds" and each "kind" are restricted in the "kind" of children they may contain:
type id = string
type block
type inline
type _ node =
| Paragraph : id * inline node list -> block node
| Strong : id * inline node list -> inline node
| Text : id * string -> inline node
A tree can then be defined like this:
let document =
Paragraph ("p1", [
Text ("text1", "Hello ");
Strong ("strong1", [
Text ("text2", "Glorious")
]);
Text ("text3", " World!")
])
Usually this would be done using separate variants for each "kind" of node, but I'm trying to define it as a GADT in order to be able to manipulate the tree using higher-order functions that pattern match on every kind of node:
function
| Text ("text2", _) ->
Some (Text ("text2", "Dreadful"))
| _ ->
None
The problem I have is in defining the function that accepts the above higher-order function and apply it to every node. So far I have this:
let rec replaceNode (type a) (f: a node -> a node option) (node: a node): a node =
match f node with
| Some otherNode -> otherNode
| None ->
match node with
| Paragraph (id, children) ->
Paragraph (id, (List.map (replaceNode f) children))
| Strong (id, children) ->
Strong (id, (List.map (replaceNode f) children))
| Text (_, _) -> node
But the compiler gives me the following error on the highlighted line
This expression has type block node -> a node option but an expression was expected of type block node -> a node option This instance of block is ambiguous: it would escape the scope of its equation
Or if I change the type of f to 'a node -> 'a node option I get this error instead
This expression has type a node but an expression was expected of type a node The type constructor a would escape its scope
Clearly I don't fully understand locally abstract types (or GADTs really, for that matter), but from what I do understand these errors seem to arise because the type is, as the name suggests, "local", and while polymorphic on the outside, passing it on would "leak" it, I guess?
So my question is, first and foremost: Is this even possible to do (and by "this" I think I mean to pattern match on a GADT in a higher-order function, but I'm not even sure that's the actual problem)?