2016-04-08 7 views
5

ein minimales Beispiel einer Beobachtung verfolgt (diese Art von erstaunt mich):Teil Dekonstruktion in Pattern-Matching (F #)

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 

Was ist der Grund dafür, dass es unmöglich entsprechen gegen einen teilweise dekonstruiert Typ macht?

Einige Teil Dekonstruktionen scheinen in Ordnung zu sein:

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

EDIT: Ok, verstehe ich jetzt den "technischen" Grund des Verhaltens beschrieben (Vielen Dank für Ihre Antworten & Kommentare). Ich denke jedoch, dass dieses Verhalten sprachlich ein bisschen "unnatürlich" im Vergleich zum Rest der Sprache ist:

"Algebraisch", mir scheint es merkwürdig, einen Typ "t" vom Typ zu unterscheiden "(t) ". Klammern (in diesem Zusammenhang) werden verwendet, um Vorrang zu geben, wie z.B. in "(t * s) * r" vs "t * (s * r)". Auch fsi Antworten entsprechend, ob ich

type Vector = (int * int) 

oder

type Vector = int * int 

FSI schicken, die Antwort ist immer

Typ Vector = int * int

Gegeben diejenigen Beobachtungen schließt man, dass "int * int" und "(int * int)" genau die gleichen Typen und t bezeichnen hus, dass alle Vorkommen von einem in jedem Code mit dem anderen ersetzt werden könnten (ref. Transparenz) ... was, wie wir gesehen haben, nicht wahr ist.

Weiter scheint es, dass wir, um das vorliegende Verhalten zu erklären, auf "wie ein Code nach der Kompilierung aussieht" zurückgreifen müssen, anstatt auf semantische Eigenschaften der Sprache, die darauf hinweist, dass es einige gibt. Spannungen "zwischen Sprachsemantik und dem, was der Compiler tatsächlich tut.

+1

Versuchen Sie es stattdessen mit 'type Vector = V of (float * float)', damit der Compiler ein tatsächliches Tupel für den Inhalt verwendet. – kvb

+0

es funktioniert! ... aber wie könnte es einen Unterschied machen ... Ich meine Typ Vector = (V von Float) * Float wäre nicht einmal gültig? –

+1

Die Frage ist: bedeutet 'V von x * y '" V enthält ein Tupel von Elementen der Typen x und y "oder" V enthält zwei verschiedene Felder vom Typ x und y "? Wenn Sie das Tupel nicht in Klammern setzen, nimmt der Compiler das letztere an, aber wenn Sie es in Klammern setzen, bedeutet es Ersteres. Diese Unterscheidung ist hauptsächlich wichtig für Interop mit anderen .NET-Sprachen, aber wie Sie gefunden haben, kann es unter bestimmten Umständen auch Auswirkungen auf die Interaktion mit Werten eines solchen Typs in F # haben. – kvb

Antwort

1

In F #

type Vector = V of float*float 

ist nur eine degenerierte Vereinigung (man kann sehen, dass es in Visual Studio durch Schweben), so ist es gleichbedeutend mit:

type Vector = 
    | V of float*float 

Der Teil nach of erstellt zwei anonymous field s (wie in F # -Referenz beschrieben) und ein Konstruktor, der zwei Parameter vom Typ float akzeptiert.

Wenn Sie

type Vector2 = 
    | V2 of (float*float) 

gibt es nur ein anonymes Feld definieren, die ein Tupel von Schwimmern und ein Konstruktor mit einem einzelnen Parameter ist. Wie bereits im Kommentar erwähnt, können Sie Vector2 verwenden, um den gewünschten Mustervergleich durchzuführen.

Nach all das kann es unlogisch, dass folgender Code funktioniert:

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

Wenn Sie jedoch berücksichtigen, dass es ein verstecktes Pattern-Matching ist, sollte alles klar sein.

EDIT:

F# language spec (p 122) stellt klar, dass Klammer Materie in Union Definitionen:

Klammerung von Bedeutung sind in der Vereinigung Definitionen. Somit unterscheiden sich die beiden folgenden Definitionen:

type CType = C of int * int

type CType = C of (int * int)

Der Mangel an Klammern im ersten Beispiel zeigt, dass die Gewerkschaft Fall zwei Argumente übernimmt. Die Klammern im zweiten Beispiel zeigen an, dass der Union-Fall ein Argument annimmt, das ein Tupelwert erster Klasse ist.

Ich denke, dass ein solches Verhalten konsistent mit der Tatsache ist, dass Sie bei der Definition einer Vereinigung komplexere Muster definieren, zB:

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

Lage zu sein, die Vereinigung mit mehreren Argumenten verwenden auch viel machen Sinn, vor allem in Interop-Situation, wenn Tupel verwendet wird, ist umständlich.

Die andere Sache, die Sie verwendet:

type Vector = int * int 

ein type abbreviation ist, die einfach einen Namen auf eine bestimmte Art gibt. Das Platzieren von Klammern um int * int macht keinen Unterschied, da diese Klammern als gruppierende Klammern behandelt werden.

+0

Ich verstehe und stimme allem zu, was du sagst, und ich werde diese Antwort akzeptieren. Ich glaube jedoch immer noch, dass die Rolle von Klammern bei Gewerkschaftsdefinitionen eher auf der pragmatischen als auf der rein/eleganten Seite liegt;). Es kann Vereinigungsdefinitionen geben, bei denen einige der Klammern zum Gruppieren dienen, während andere wie zuvor besprochen sind ... Weitere Definitionen der Form "... = X von Y" hängen nicht nur von Y "als einem Typ" ab ... –

+0

Ich stimme zu, dass es nicht die intuitivste und sauberste Design-Wahl in Bezug auf die Syntax ist; Auf der anderen Seite ist es auch hässlich. –