13

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?

TheQuickBrownFox
  • 10,544
  • 1
  • 22
  • 35
Shishkin Pavel
  • 351
  • 2
  • 19
  • 3
    well, it's just like PM, so you can write: `let unwrap (Composite (i, s)) = i, s` – FoggyFinder Jun 15 '17 at 18:30
  • 1
    I've corrected the error message in your question. You probably got the other one because you forgot to run the type definition into FSI. Good question, though! I didn't know about this edge case. – TheQuickBrownFox Jun 16 '17 at 09:25

3 Answers3

10

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.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
9

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.

scrwtp
  • 13,437
  • 2
  • 26
  • 30
5

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 l.h.s. 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 e.g. --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 l.h.s. 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

sgtz
  • 8,849
  • 9
  • 51
  • 91
  • `"These all work for me."`. The code doesn't actually compile (albeit with a different compile error than the one reported) so this doesn't actually answer the question. There is some pattern matching behaviour that I found quite unexpected which is explained in Tomas' answer. – TheQuickBrownFox Jun 16 '17 at 09:14
  • @TheQuickBrownFox: Granted. I read the question to mean decompose in a tuple-like style, hence a review of the basics. Perhaps that was the original intent? Anyway, your edit to the question makes the issue clearer. I skipped this as I assumed it'd be fine. It'd be nice to tidy this up on the compiler side. Has this been submitted as a github request yet? It'd be good to capture it is all. – sgtz Jun 16 '17 at 09:55
  • @TheQuickBrownFox: in the spirit of the original question (named DU elements), I've added an Active Pattern recogniser as a workaround. Thanks for pointing this out. – sgtz Jun 16 '17 at 10:32