2016-04-08 42 views
5

Seguendo un esempio minimo di osservazione (che tipo di mi stupito):destrutturazione parziale in corrispondenza del pattern (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 

Qual è la ragione che rende impossibile la corrispondenza con un tipo parzialmente eliminata?

Alcuni decostruzioni parziali sembrano essere ok:

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

EDIT: Ok, ora capisco il motivo "tecnico" del comportamento descritto (Grazie per le vostre risposte & commenti). Tuttavia, penso che questo linguaggio sia un po '"innaturale" rispetto al resto della lingua:

"Algebricamente", a me sembra strano distinguere un tipo "t" dal tipo "(t)". Le parentesi (in questo contesto) sono usate per dare la precedenza come per es. in "(t * s) * r" vs "t * (s * r)". risponde anche FSI di conseguenza, se io mando

type Vector = (int * int) 

o

type Vector = int * int 

alla FSI, la risposta è sempre

tipo di vettore = int * int

Dato quelli osservazioni, si conclude che "int * int" e "(int * int)" denotano esattamente gli stessi tipi e t che tutte le occorrenze di uno potrebbero essere sostituite con qualsiasi altro codice (rif. trasparenza) ... che come abbiamo visto non è vero.

Inoltre sembra significativo che, per spiegare il comportamento in questione, abbiamo dovuto ricorrere a "come appare un codice dopo la compilazione" piuttosto che alle proprietà semantiche del linguaggio che indica che ce ne sono alcune " tensioni "tra semantica del linguaggio e ciò che il compilatore effettivamente fa.

+1

Provalo con 'type Vector = V of (float * float)' per fare in modo che il compilatore usi una tupla effettiva per il contenuto. – kvb

+0

funziona! ... ma come potrebbe fare la differenza ... Voglio dire type Vector = (V of float) * float non sarebbe nemmeno valido? –

+1

La domanda è: "V di x * y" significa "V contiene una tupla di elementi di tipi xey" o "V contiene due campi distinti di tipo xey"? Se non si parentesi la tupla, il compilatore assume quest'ultimo, ma se lo si fa parentesi, significa il primo. Questa distinzione è importante soprattutto per l'interoperabilità con altri linguaggi .NET, ma come hai scoperto può anche influire sul modo in cui interagisci con valori di tale tipo all'interno di F # in alcune circostanze. – kvb

risposta

1

In F #

type Vector = V of float*float 

è solo un'unione degenerata (si può vedere che, in bilico in Visual Studio), quindi è equivalente a:

type Vector = 
    | V of float*float 

La parte dopo of crea due anonymous field s (come descritto nel riferimento F #) e un costruttore che accetta due parametri di tipo float.

Se si definisce

type Vector2 = 
    | V2 of (float*float) 

c'è solo un campo anonimo, che è una tupla di carri e di un costruttore con un singolo parametro. Come è stato sottolineato nel commento, è possibile utilizzare Vector2 per eseguire la corrispondenza del modello desiderata.

Dopo tutto questo, può sembrare illogico che seguente codice funziona:

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

Tuttavia, se si tiene conto del fatto che c'è un pattern matching nascosto, tutto dovrebbe essere chiaro.

EDIT:

F# language spec (p 122) afferma chiaramente che la materia parentesi nelle definizioni sindacali:

parentesi sono significativi nelle definizioni sindacali. Così, le due seguenti definizioni differiscono:

type CType = C of int * int

type CType = C of (int * int)

La mancanza di parentesi nel primo esempio indica che il caso unione prende due argomenti. Le parentesi nel secondo esempio indicano che il caso unione prende un argomento che è un valore di tupla di prima classe.

Penso che tale comportamento è coerente con il fatto che è possibile definire modelli più complessi alla definizione di un'unione, per esempio:

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

Essere in grado di utilizzare l'unione con più argomenti rende anche molto più il senso, specialmente nella situazione di interpolazione, quando usare le tuple è complicato.

L'altra cosa che si è utilizzato:

type Vector = int * int 

è un type abbreviation che dà semplicemente un nome di un certo tipo. Posizionare la parentesi intorno a int * int non fa differenza perché quelle parentesi verranno considerate come una parentesi di raggruppamento.

+0

Io (capisco e) sono d'accordo con tutto ciò che dici e accetterò quella risposta. Tuttavia, penso ancora che il design saggia il ruolo delle parentesi nelle definizioni dei sindacati sia più sul lato pragmatico che su quello pulito/elegante;). Ci possono essere definizioni di unione in cui alcune delle parentesi sono per il raggruppamento mentre altre sono come discusso prima ... Ulteriori definizioni del modulo "... = X di Y" non dipendono solo da Y "come un tipo" ... –

+0

Concordo sul fatto che non è la scelta di design più intuitiva e pulita in termini di sintassi; d'altra parte è brutto. –