简体   繁体   中英

Unwrap F# single-case discriminated union tuple type

We can unwrap type like type Address = Address of string using unwrapping function like

let unwrapAddress (Address a) = a
let addr = Address "sdf"
let str = unwrapAddress addr

so str will be of type string , but if there is type like this approach willn't work:

type Composite = Composite of integer:int * someStr:string
let unwrap (Composite c) = c

will produce error

let unwrap (Composite c) = c;;
------------^^^^^^^^^^^
error FS0019: This constructor is applied to 1 argument(s) but expects 2

Can I somehow unwrap composite types to a simple tuple?

You defined the type as a single-case discriminated union with named fields:

type Composite = Composite of integer:int * someStr:string

When defined in this way, the fields of the union case are not a simple tuple. They get treated in a special way and, for example, the names are used as property names in compiled code. The pattern matching does not automatically turn the elements into a tuple and so you have to unwrap them separately:

let unwrap (Composite(i, s)) = i, s

However, you can also define single-case union where the field is an ordinary tuple. (Note that you need the parentheses around the tuple type - otherwise, it also ends up being treated in a special way, except that the items will be compiled as Item1 and Item2 .)

type Composite = Composite of (int * string)

With this definition, your unwrap function will work fine and extract the tuple value:

let unwrap (Composite c) = c

You can also use a nested pattern to get the number and the string like in the previous case:

let unwrap (Composite(i, s)) = i, s

The fact that this behaves differently depending on whether you write A of (T1 * T2) or whether you write A of T1 * T2 is a bit subtle - the two probably need to be distinguished just so that the compiler knows whether to compile the fields as two separate fields or as one field of type System.Tuple<T1, T2> . I cannot quite imagine any other case where the difference would matter.

These all work for me. It's your matching syntax, that most often you'll find used with match statements, but it's on the lhs of an assignment. Possibly, this makes the most sense, initially, for tuples, but you can use this with any structure.

let (a,b) = (1,2)

let (x,_) = (4,5)

Two other interesting things to try:

let (head::tail) = [1;2;3;4]

FSI responds warning FS0025: Incomplete pattern matches on this expression. For example, the value '[]' may indicate a case not covered by the pattern(s).

"That's true," you reason aloud. "I should express it as a match and include an empty list as a possibility". It's better to bubble these kinds of warnings into fully bonafide errors (see: warn as error eg --warnaserror+:25 ). Don't ignore them. Resolve them through habit or the compiler enforced method. There's zero ambiguity for the single case, so code-on.

More useful + interesting is the match syntax on the lhs of a function assignment. This is pretty cool. For pithy functions, you can unpack the stuff inside, and then do an operation on the internals in one step.

let f (Composite(x,y)) = sprintf "Composite(%i,%s)" x y

f (Composite(1,"one"))

> val it : string = "Composite(1,one)"

About your code:

type Address = Address of string //using unwrapping function like

let unwrapAddress (Address a) = a
let addr = Address "sdf"
let str = unwrapAddress addr

type Composite = Composite of integer:int * someStr:string
let unwrap (Composite(c,_)) = c
let cval = Composite(1,"blah")
unwrap cval

Workaround:

let xy = Composite(1,"abc") |> function (Composite(x,y))->(x,y)

... but the nicer way, assuming you want to keep the named elements of your single case DU would be...

let (|Composite|) = function | Composite(x,y)->(x,y)

let unwrap (Composite(x)) = x

let unwrap2 (Composite(x,y)) = (x,y)

... not strictly decomposing through a single case DU, but decomposing through a single-case Active Pattern

lastly, you could attach a method to the Composite structure...

module Composite = 
  let unwrap = function | Composite(x,y)->(x,y)

One of the best discussions about using this technique is over here

Also, check out the signature that unwrap gives us: a function that takes a Composite (in italics), and returns an int (in bold)

Signature -- val unwrap : Composite -> int

In your case, you can write:

type Composite = Composite of int * string 

let unwrap (Composite (a, b)) = a, b

which corresponds to:

let unwrap x = 
    match x with
    | Composite (a, b) -> a, b

What's happening here is that F# allows you to deconstruct function arguments inline using arbitrarily complex pattern matching. This is often mentioned when introducing single case DU's, but it's rarely followed to the conclusion, which leads people to believe single case DU's are somehow special that way.

In fact, you can use it when you have multiple cases (as long as each case binds the same set of variables):

type Composite = Composite of int * string | JustString of string

let unwrapString (Composite (_, s) | JustString s) = s

But most of the time, you'd pattern match on simpler types, like tuples:

let f (a, b, c) = ...

or even more curiously:

let f () = ...

Here () is a pattern match on the lone value of unit type - rather than some kind of "visual marker for a parameterless function", as it's often described.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM