Come si usa la corrispondenza dei motivi nelle definizioni "let"?
-
19-08-2019 - |
Domanda
Ho appena notato che F # mi permette di usare le associazioni let con valori letterali e altri schemi come segue:
let fib 0 = 1
let exists item [] = false
let car (hd :: tl) = hd
let cdr (hd :: tl) = tl
F # interpreta correttamente queste funzioni come una specie di corrispondenza del motivo, perché mi dà i seguenti avvertimenti:
Avviso 1 Corrispondenze di schemi incomplete su questa espressione. Ad esempio, il il valore '1' non sarà abbinato
Avviso 2 Corrispondenze di schemi incomplete su questa espressione. Ad esempio, il il valore '[_]' non sarà abbinato
ecc.
Queste funzioni funzionano come previsto, ma voglio definire una funzione in questo stile con corrispondenze di pattern complete, tuttavia non riesco a trovare nulla su questa sintassi di corrispondenza di pattern alternativa nel manuale di F #.
So di poter usare let qualunque = funzione ...
e lasciare qualunque x = abbinare x con ...
per ottenere i risultati che desidero, ma io ' ho appena scoperto l'ennesima sintassi per la corrispondenza dei motivi e mi tormenterà per sempre se non capisco come usarlo.
Come posso scrivere le funzioni utilizzando la sintassi di corrispondenza dei motivi alternativa mostrata sopra?
Soluzione
JaredPar ha ragione, F # non ha la forma sintattica che Haskell ha qui.
Il modulo F # è per lo più utile per rompere i sindacati discriminati a caso singolo o per definire funzioni con corrispondenze incomplete (come l'esempio della tua "auto" che fallisce nell'elenco vuoto). È semplicemente una conseguenza del fatto che praticamente tutti i legami del nome nella lingua sono fatti tramite schemi; questa forma sintattica (definire una funzione usando schemi su argomenti) non è spesso troppo utile in pratica, per il motivo esatto che hai descritto.
Penso che Haskell abbia fatto molte cose meglio di ML quando si tratta di forme sintattiche, ma le radici di F # sono in ML. Il vantaggio è che un buon sottoinsieme di cross-compilazioni di F # con OCaml (che ha aiutato a avviare il linguaggio F # e la comunità di utenti); lo svantaggio è che F # è "bloccato" con alcuni bit di sintassi brutta / limitata.
Altri suggerimenti
AFAIK, in F # non è possibile dichiarare più associazioni let con lo stesso nome e firme di corrispondenza dei pattern differenti. Credo che il costrutto più vicino a quello che stai cercando sia un'espressione delle regole di funzione.
Prendi questo esempio per auto
let car = function
| hd::tl -> hd
| [] -> failwith "empty list"
Apparentemente, il pattern matching di F # è molto più potente di quello che usiamo nello sviluppo comune.
Innanzitutto, puoi associare più valori contemporaneamente. In genere, lo farai con List.partition
:
let data = [7;0;0;0;1;0;1;1;1;1;0;0;1;1;0]
let ones, others = data |> List.partition ((=) 1) // bind two values
Come nota a margine, puoi associare diversi identificatori allo stesso valore:
let (a & b) = 42 // a = 42; b = 42
Cominciamo con un semplice let
per semplicità.
let hd::tl = data
avviso FS0025 : il pattern incompleto corrisponde a questa espressione. Ad esempio, il valore '[]' può indicare un caso non coperto dai motivi.
Per mitigare questo, dobbiamo aggiungere un altro caso per l'elenco vuoto:
let (hd::tl) | [] = data
errore FS0018 : i due lati di questo modello 'o' associano diversi set di variabili
Ed è vero; in caso di elenco vuoto, hd
e tl
rimangono non associati. È facile associare tl
con lo stesso elenco vuoto:
let (hd::tl) | ([] as tl) = data
Tuttavia, l'errore errore FS0018 non scompare. In effetti, dobbiamo anche fornire alcuni "predefiniti" valore per hd
.
Il seguente trucco sporco lo farà.
let (hd::tl, _) | ([] as tl , hd) = data, 42
La riga sopra legherà hd
alla testa del data
, nel caso in cui l'elenco non sia vuoto, o con il supplemento valore fornito nel secondo valore di tuple
.
Nota Non ho trovato il modo di incorporare 42
nel costrutto let
.
Infine, lo stesso vale per la funzione car
:
let car ((hd::tl, _) | ([] as tl, hd)) = hd
let foo = car(data, 42) // foo = 7
let bar = car([], 42) // bar = 42
Come scrivo le funzioni usando la sintassi alternativa di corrispondenza dei modelli mostrata sopra?
Come hai fatto, ma solo quando uno schema è esaustivo. Esempi ovvi in ??cui ciò è utile includono tuple e registrazioni, nonché unioni a caso singolo e schemi attivi.
Usi questa funzione della lingua ogni volta che lo fai:
let f(a, b) = ...
Quindi questo generalizza a:
let f(a, (b, c)) = ...
Puoi anche usarlo per scegliere tra un valore predefinito o Some value
:
let def(x, None | _, Some x) = x
A proposito, lo stile che stai suggerendo è stato usato in SML prima di Haskell e SML è un ML quindi questo ovviamente non ha nulla a che fare con Haskell vs ML. In realtà preferisco lo stile OCaml / F # perché è meno ripetitivo:
fun descriptiveFunctionName [] = true
fun descriptiveFunctionName (_::_) = false
vs
let descriptiveFunctionName = function
| [] -> true
| _::_ -> false
Questo è meglio per il codice non accademico in cui c'è un valore reale è usare identificatori autocompensanti.