Partial deconstruction versus pattern (F #)

Following a minimal observation example (it struck me):

type Vector = V of float*float // complete unfolding of type is OK let projX (V (a,_)) = a // also works let projX' x = match x with | V (a, _) -> a // BUT: // partial unfolding is not Ok let projX'' (V x) = fst x // consequently also doesn't work let projX''' x = match x with | V y -> fst y 

What is the reason for the impossibility of matching with a partially deconstructed type?

Some partial deconstructs look fine:

 // Works let f (x,y) = fst y 

EDIT: Okay, now I understand the “technical” reason for the described behavior (Thanks for your answers and comments). However, I think the language is wise, this behavior seems a bit “unnatural” compared to the rest of the language:

"Algebraically," it seems strange to me to distinguish between type "t" and type "(t)". Brackets (in this context) are used to provide priority, for example, in "(t * s) * r" vs "t * (s * r)". Also fsi answers accordingly if I send

 type Vector = (int * int) 

or

 type Vector = int * int 

in fsi, the answer is always

type Vector = int * int

Given these observations, it is concluded that "int * int" and "(int * int)" mean exactly the same types and, therefore, all occurrences of one can be replaced with any piece of code by another (ref.) ... which, as we have seen, is wrong.

Further, it seems important that to explain the behavior at hand, we had to resort to talking about “what some kind of code looks like after compilation”, and not about the semantic properties of the language, which indicates that there are some “tensions” between the semantics of the language and what the compiler does.

+5
source share
1 answer

In f #

 type Vector = V of float*float 

is just a degenerate union (you can see that by hanging it in Visual Studio), so it is equivalent:

 type Vector = | V of float*float 

The part after of creates two anonymous fields (as described in the F # link), and the constructor accepts two parameters of type float.

If you define

 type Vector2 = | V2 of (float*float) 

there is only one anonymous field, which is a float tuple and a constructor with one parameter. As stated in the comment, you can use Vector2 to perform the desired pattern matching.

After all this, it may seem counterintuitive that the following code works:

 let argsTuple = (1., 1.) let v1 = V argsTuple 

However, given that there is a hidden pattern, everything should be clear.

EDIT:

The F # language specification (p. 122) clearly indicates what is indicated in brackets in the definitions of associations:

Parentheses are important in connection definitions. Thus, the following two definitions differ:

type CType = C of int * int

type CType = C of (int * int)

The absence of parentheses in the first example indicates that the union case takes two arguments. The brackets in the second example show that the union case takes one argument, which is the first-class value of the tuple.

I think this behavior is consistent with the fact that you can define more complex patterns when defining a join, for example:

  type Move = | M of (int * int) * (int * int) 

The possibility of using a union with several arguments also makes a lot of sense, especially in an interaction situation when using tuples is cumbersome.

Other that you used:

 type Vector = int * int 

is an abbreviation of type which simply gives a name to a specific type. Placing parentheses around int * int does not matter, because these brackets will be considered as a grouping of brackets.

+1
source

Source: https://habr.com/ru/post/1246732/