Search code examples
f#discriminated-union

'Anonymous type variables are not permitted in this declaration' error when adding parameters to discriminated union cases in F#


So I have some (I'm assuming rather unusual) code which is for building Function Trees. Here's it is right now:

type FunctionTree<'Function> =
    | BranchNode of seq<FunctionTree<'Function>>
    | Leaf of (a:'Function -> unit) with
        member __.Execute() = do a

The expression a:'Function -> unit is what makes the compiler throw a fit, giving me the error 'Anonymous type variables are not permitted in this declaration' and I have no idea why. I've tried adding a variable to the BranchNode, adding (yucky) double parentheses around the expression but nothing seems to have worked.


Solution

  • Answer to the compiler error question

    This does not compile...

    Leaf of (a:'Function -> unit)
    

    ...because discriminated field names can be added to the types of the DU cases, not to the types of the function types in a DU case. In contrast, this compiles...

    Leaf of a: ('Function -> unit)
    

    ...because the field name a is being used to name the type (Function -> unit).

    Additional discussion about the code

    However, there is another issue. The member Execute that you are adding is not being added to the Leaf node, as your code implies. It is being added to the entire function tree. Consequently, you will not have access to the label a inside your implementation of Execute. Think of it like this...

    type FunctionTree<'Function> =
        | BranchNode of seq<FunctionTree<'Function>>
        | Leaf of a: ('Function -> unit)
        with member __.Execute() = do a
    

    ... with the member shifted to the left to clarify that it applies to the entire union, not just the leaf case. That explains why the above code now has a different compiler error... a is not defined. The field name a is used to clarify the instantiation of a Leaf case. The field name a is not available elsewhere.

    let leaf = Leaf(a: myFunc)
    

    Consequently, the label a is not available to your Execute member. You would need to do something like this...

    with member x.Execute(input) =
        match x with
        | BranchNode(b) -> b |> Seq.iter(fun n -> n.Execute(input))
        | Leaf(f) -> f(input) |> ignore
    

    Notice in the above code that the x value is a FunctionTree.

    Alternative implementation

    We could continue going. However, I think the following may implement what you are aiming for:

    type FunctionTree<'T> =
        | BranchNode of seq<FunctionTree<'T>>
        | LeafNode of ('T -> unit)
    
    let rec evaluate input tree =
        match tree with
        | LeafNode(leaf) -> leaf(input)
        | BranchNode(branch) -> branch |> Seq.iter (evaluate input)
    
    BranchNode([
        LeafNode(printfn "%d")
        LeafNode(printfn "%A")
    ])
    |> evaluate 42