11

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?

bouteillebleu
  • 2,456
  • 23
  • 32
Richard Warburton
  • 1,462
  • 1
  • 9
  • 13

1 Answers1

11

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.

Brian
  • 117,631
  • 17
  • 236
  • 300
  • This is a good suggestion, I'll have to think about the performance implications of such an approach. It probably doesn't matter for my particular usecase anyway. – Richard Warburton Aug 29 '10 at 23:16
  • re: your question about how Uniplate is implemented the source is available at http://community.haskell.org/~ndm/darcs/uniplate/ . – Richard Warburton Aug 29 '10 at 23:17
  • I've accepted your answer since I think its a good approach - but I make a change in order to get it to work correctly: I replaced 'ft = ot' with 'ot.IsSubclassOf(ft)' - otherwise it fails to match when the argument type of f, ie 'a is more generic than the specific argument being passed. – Richard Warburton Aug 30 '10 at 18:39
  • To a first approximation, Uniplate is implemented with magic. :] That said, Uniplate, SYB, and related libraries mostly exist as structured, explicit implementations of what reflection does. No special support is needed in Haskell (auto-generation of instances is merely a convenience), but I don't know if the techniques used exist in F#, and either way I expect it would be more sensible to just build on top of the existing facilities for reflection, as in your example here. – C. A. McCann Jun 16 '11 at 19:41
  • See also http://social.msdn.microsoft.com/Forums/en-US/fsharpgeneral/thread/742480f0-067c-4f2e-aae8-c41452840b09 – Brian Nov 30 '11 at 23:27