Generic extraction from a constructor

别说谁变了你拦得住时间么 提交于 2019-12-11 08:48:49

问题


In F# and OCaml I wind up writing a lot of code like

type C = Blah of Whatever  
let d = Blah (createWhatever ())  // so d is type C  
...  
let x = match d with | Blah b -> b

What I'd like is this

...  
let x = peel d

Where peel would work for any constructor/discriminator.
Surely I'm not the only one annoyed by this.
edit: Good answers, but I don't have the rep to vote on them. How about this situation?

member self.Length = match self with | L lab -> lab.Length

回答1:


It is not possible to do that safely : if peel was a function, what would be its type ? It cannot be typed and therefore cannot be a "good guy" in the language.

You may :

  • use reflection (in F#) or type-breaking functions (in OCaml it's the Obj module), but you will get something unsafe with an imprecise type, so it's rather ugly and "use at your own risk"

  • use metaprogramming to generate different versions of peel at each type for you. For example, using the type-conv OCaml tool, you may have type blah = Blah of something define a function peel_blah implicitly, and type foo = Foo of something define peel_foo.

The better solution imho is... not to need such a peel in the first place. I see two possibilities:

  • You may use clever patterns instead of a function : by using let (Blah whatever) = f x, or fun (Blah whatever) -> ..., you don't need an unpacking function anymore.

  • Or you may, instead of writing type blah = Blah of what, write

    type blah = (blah_tag * whatever) and blah_tag = Blah

    This way, you don't have a sum type but a product type (you write (Blah, whatever)), and your peel is just snd. You still have a different (incompatible) type for each blah, foo etc, but a uniform access interface.




回答2:


As mentioned, the let is convenient to do a pattern matching. If you want to access the value in the middle of an expression, where patterns are not allowed, I suggest adding a member to the types:

type C = Blah of int
with member c.Value = match c with Blah x -> x

let x = Blah 5
let y = Blah 2
let sum = x.Value + y.Value



回答3:


I would write this instead:

type C = Blah of Whatever  
let d = Blah (createWhatever ())  // so d is type C  
...
let (Blah x) = d

For your second situation, I like Laurent's member x.Value = match x with Blah v -> v.




回答4:


Works for DUs...will need tweaking to work with class constructors:

open Microsoft.FSharp.Reflection

let peel d = 
    if obj.ReferenceEquals(d, null) then nullArg "d"
    let ty = d.GetType()
    if FSharpType.IsUnion(ty) then
        match FSharpValue.GetUnionFields(d, ty) with
        | _, [| value |] -> unbox value
        | _ -> failwith "more than one field"
    else failwith "not a union type"

By the way: I wouldn't typically do something like this, but since you asked...



来源:https://stackoverflow.com/questions/5681490/generic-extraction-from-a-constructor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!