Search code examples
f#generic-programmingdiscriminated-unionscrap-your-boilerplate

Scrap Your Boilerplate in f#


I've used the Scrap Your Boilerplate and Uniplate libraries in the Haskell programming language, and I would find that form of generic programming over discriminated unions to be really useful. Is there an equivalent library in the f# programming language?


Solution

  • Not that I know of; without support built-in to the language/compiler, I expect the only alternative is a reflection-based version. (I don't know how Uniplate is implemented - do you?)

    Here's the code for a reflection-based version based on the example from the original presentation. I have not thought deeply about its limitations, but this was much simpler to write than I would have guessed.

    type Company = C of Dept list
    and Dept = D of Name * Manager * SubUnit list
    and SubUnit = | PU of Employee | DU of Dept
    and Employee = E of Person * Salary
    and Person = P of Name * Address
    and Salary = S of float
    and Manager = Employee
    and Name = string
    and Address = string
    
    let data = C [D("Research",E(P("Fred","123 Rose"),S 10.0),
                      [PU(E(P("Bill","15 Oak"),S 5.0))])]
    printfn "%A" data
    
    open Microsoft.FSharp.Reflection 
    let everywhere<'a,'b>(f:'a->'a, src:'b) =   // '
        let ft = typeof<'a>             // '
        let rec traverse (o:obj) =
            let ot = o.GetType()
            if ft = ot then
                f (o :?> 'a) |> box    // '
            elif FSharpType.IsUnion(ot) then
                let info,vals = FSharpValue.GetUnionFields(o, ot)
                FSharpValue.MakeUnion(info, vals |> Array.map traverse)
            else 
                o
        traverse src :?> 'b       // '
    
    let incS (S x) = S(x+1.0) 
    
    let newData = everywhere(incS, data)
    printfn "%A" newData
    

    The everywhere function traverses the entire structure of an arbitrary DU and applies the function f to each node that is the type that f works on, leaving all other nodes as-is.