Funzioni ML da liste polimorfiche a liste polimorfiche


8

Sto imparando la programmazione in ML (OCaml) e in precedenza ho chiesto informazioni sulle funzioni di tipo ML'a -> 'b . Ora sto sperimentando un po 'con funzioni di tipo 'a list -> 'b list. Ci sono alcuni semplici esempi ovvi:

let rec loop l = loop l
let return_empty l = []
let rec loop_if_not_empty = function [] -> []
                                   | l -> loop_if_not_empty l

Quello che non riesco a capire è come creare una funzione che fa qualcosa di diverso da restituire l'elenco vuoto o il ciclo (senza usare alcuna funzione di libreria). Può essere fatto? C'è un modo per restituire elenchi non vuoti?

Modifica: Sì, se ho una funzione di tipo 'a -> 'b, allora posso crearne un'altra, o una funzione di tipo 'a list -> 'b list, ma quello che mi chiedo qui è come creare la prima.


1
Come per la domanda precedente, si prega di indirizzare la programmazione dell'apprendimento degli studenti CS101 nella risposta, non il teorico dei tipi a cui la risposta potrebbe ispirarlo a diventare in seguito.
Gilles 'SO- smetti di essere malvagio' il

Nota che se avessi una funzione f con questo tipo che restituisce un elenco non vuoto, allora divertente a -> List.hd (f [a]) dovrebbe digitare 'a ->' b senza essere non-terminante o sollevare un'eccezione.
gallais,

Risposte:


6

Bene, qualcosa noto come parametria ci dice che se consideriamo il sottoinsieme puro di ML (ovvero, nessuna ricorsione infinita refe tutta quella strana roba), non c'è modo di definire una funzione con questo tipo se non quella che restituisce il vuoto elenco.

Tutto è iniziato con l'articolo di Wadler “ Teoremi gratis! ”. Questo documento, in sostanza, ci dice due cose:

  1. Se consideriamo i linguaggi di programmazione che soddisfano determinate condizioni, possiamo dedurre alcuni teoremi interessanti semplicemente osservando la firma del tipo di una funzione polimorfica (questo si chiama Teorema di parametricità).
  2. ML (senza ricorsione infinita, refe tutte quelle cose strane) soddisfa queste condizioni.

Dal teorema Parametricity sappiamo che se abbiamo una funzione f : 'a list -> 'b list, allora per ogni 'a, 'b, 'c, 'de per tutte le funzioni g : 'a -> 'c, h : 'b -> 'dabbiamo:

map h ∘ f = f ∘ map g

(Nota, fa sinistra ha il tipo 'a list -> 'b liste fa destra è 'c list -> 'd list.)

Siamo liberi di scegliere quello che gci piace, quindi lascia 'a = 'ce g = id. Da allora map id = id(facile da dimostrare per induzione sulla definizione di map), abbiamo:

map h ∘ f = f

Ora lascia 'b = 'd = boole h = not. Supponiamo che per alcuni zs : bool listcapiti che succede f zs ≠ [] : bool list. E 'chiaro che map not ∘ f = fnon non regge, perché

(map not ∘ f) zs ≠ f zs

Se il primo elemento dell'elenco a destra è true, allora a sinistra il primo elemento è falsee viceversa!

Ciò significa che la nostra ipotesi è sbagliata e f zs = []. Abbiamo finito? No.

Abbiamo ipotizzato che lo 'bsia bool. Abbiamo dimostrato che quando fviene invocato con type f : 'a list -> bool listper any 'a, fdeve sempre restituire l'elenco vuoto. Può essere che quando chiamiamo fcome f : 'a list -> unit listrestituisce qualcosa di diverso? La nostra intuizione ci dice che questa è una sciocchezza: semplicemente non possiamo scrivere in puro ML una funzione che restituisce sempre la lista vuota quando vogliamo che ci dia un elenco di booleani e che altrimenti potrebbe restituire una lista non vuota! Ma questa non è una prova.

Quello che vogliamo dire è che fè uniforme : se restituisce sempre una lista vuota per bool list, allora deve restituire l'elenco vuoto per unit liste, in generale, qualsiasi 'a list. Questo è esattamente ciò di cui tratta il secondo punto dell'elenco puntato all'inizio della mia risposta.

Il documento ci dice che in ML fdeve prendere relativi valori relativi quelle. Non entrerò nei dettagli delle relazioni, è sufficiente dire che le liste sono correlate se e solo se hanno uguale lunghezza e i loro elementi sono correlati in coppia (cioè, [x_1, x_2, ..., x_m]e [y_1, y_2, ..., y_n]sono correlati se e solo se m = ne x_1sono correlati a y_1ed x_2è correlato y_2e così via). E la parte divertente è, nel nostro caso, poiché fè polimorfica, possiamo definire qualsiasi relazione sugli elementi delle liste!

Prendiamo qualsiasi 'a, 'be guardare f : 'a list -> 'b list. Ora guarda f : 'a list -> bool list; abbiamo già dimostrato che in questo caso frestituisce sempre l'elenco vuoto. Ora postuliamo che tutti gli elementi di 'asono correlati a se stessi (ricordate, possiamo scegliere qualsiasi relazione che vogliamo), questo implica che qualcuno zs : 'a listè legato a se stesso. Come sappiamo, fassume valori correlati a quelli correlati, ciò significa che f zs : 'b listè correlato f zs : bool list, ma il secondo elenco ha una lunghezza pari a zero e poiché il primo è correlato ad esso, è anche vuoto.


Per completezza, menzionerò che c'è una sezione sull'impatto della ricorsione generale (possibile non terminazione) nel documento originale di Wadler, e c'è anche un documento che esplora teoremi liberi in presenza di seq.


Ora sospetto che la prova possa essere fatta in un solo passaggio se invece di indebolire il teorema di parametricità considerando le relazioni specifiche indotte dalle funzioni ( ge hin questo caso) andare dritto con relazioni generali su misura ...
Kirelagin,

Nitpick, la parametricità non inizia con l'articolo di Wadler (che afferma di essere un riassunto degli approcci per la definizione della parametria). L'idea risale al lavoro di Reynold "Tipi, astrazione e polimorfismo parametrico". L'idea era anche presente nella prova di normalizzazione di Girard per System F, per quanto ne so.
Daniel Gratzer,

4

Torniamo agli oggetti più semplici: non è possibile creare un oggetto di tipo corretto 'aperché significherebbe che questo oggetto xpuò essere utilizzato ovunque 'asi adatti. E questo significa ovunque: come un numero intero, un array, persino una funzione. Ad esempio, ciò significherebbe che puoi fare cose come x+2, x.(1)e (x 5). I tipi esistono esattamente per impedirlo.

Questa è la stessa idea che si applica con una funzione di tipo 'a -> 'b, ma ci sono alcuni casi in cui questo tipo può esistere: quando la funzione non restituisce mai un oggetto di tipo 'b: durante il ciclo o sollevando un'eccezione.

Questo vale anche per le funzioni che restituiscono un elenco. Se la tua funzione è di tipo t -> 'b liste se costruisci un oggetto di tipo te lo applichi a questa funzione, ciò significa che se accedi correttamente a un elemento di questo elenco, accederai a un oggetto che ha tutti i tipi. Quindi non puoi accedere a nessun elemento dell'elenco: l'elenco è vuoto o ... non esiste un elenco.

Tuttavia, il tipo 'a list -> 'b listappare negli esercizi abituali ma è solo quando hai già una funzione di tipo 'a -> 'b:

let rec map (f : 'a -> 'b) =
  function
  | [] -> []
  | x :: xs -> f x :: map f xs

Ma probabilmente lo conosci.

val map : ('a -> 'b) -> 'a list -> 'b list

1
Il teorico del tipo più vecchio è meno che elettrizzato da questa risposta. Ok, un contesto variabile di tipo non vuoto è un modo per avere una funzione che è letteralmente di tipo 'a -> 'bo 'a list -> 'b list, ma non è un'osservazione così interessante. In effetti ho intenzione di modificare la domanda per chiarire che questo non è ciò di cui si chiedeva la programmazione dell'apprendimento degli studenti più giovani.
Gilles 'SO-smetti di essere malvagio' il

Ma il teorico del tipo più anziano sa che ML non è logicamente imperfetto. Se si riesce a produrre una funzione f : 'a list -> 'b liste ttale che f t <> []allora questo programma tipo-check, ma può fare modo peggiore di sollevare un'eccezione: let y = List.hd (f t) in (y y) (y + y.(0) + y.(0).(0)).
jmad

2

Teorema di parametricità dal "Teoremi gratis!" l'articolo ci dice che i termini ML hanno una proprietà molto speciale: se consideriamo il tipo di un termine come una relazione su valori di questo tipo, allora il valore di questo termine sarà correlato a se stesso. Ecco come visualizzare i tipi come relazioni:

  • Un tipo di funzione 'a -> 'bcorrisponde alla relazione definita dicendo che due funzioni sono correlate se prendono valori correlati a valori correlati (assumendo 'ae 'bcorrispondono ad alcune relazioni).
  • Un tipo di elenco 'a listcorrisponde alla relazione definita dicendo che due elenchi sono correlati se hanno la stessa lunghezza e i relativi elementi di corrispondenza sono correlati (supponendo che 'acorrisponda a qualche relazione).
  • (Ora la parte più interessante.) Di tipo A polimorfiche corrisponde alla relazione definita da dire che due valori polimorfici sono correlate se possiamo scegliere qualsiasi due tipi, qualsiasi relazione tra gli elementi di questo tipo, sostituire tutte le istanze della variabile di tipo con questo relazione e i valori risultanti saranno comunque correlati.

Ecco un esempio Supponiamo di avere un termine foo : 'a -> 'a. Il teorema di parametricità afferma che fooè legato a se stesso. Ciò significa che possiamo scegliere due tipi qualsiasi, diciamo, e , scegliere assolutamente qualsiasi relazione tra elementi di questo tipo e se prendiamo e , in modo tale che siano correlati secondo , quindi e saranno anch'essi correlati secondo :A1A2Aa1:A1a2:A2Afooa1fooa2A

a1Aa2fooa1Afooa2.

Ora se consideriamo la relazione non una relazione arbitraria, ma una funzione , quanto sopra diventa:Af:A1A2

f(a1)=a2f(fooa1)=fooa2,

o, in altre parole:

f(fooa1)=foo(f(a1)),

che è esattamente il teorema libero per la idfunzione di: f . id = id . f.


Se esegui passaggi simili per la tua funzione foo : 'a list -> 'b list, otterrai che puoi scegliere due tipi qualsiasi e , qualsiasi relazione tra i loro elementi, due tipi e , qualsiasi relazione tra i loro elementi, quindi prendono due elenchi, prima costituiti da elementi di , in secondo luogo costituiti da elementi di , applicano la vostra funzione a entrambi gli elenchi (ottenendo un elenco di nel primo caso e un elenco di nel secondo), e i risultati saranno correlati, se gli input fossero correlati.A1A2AB1B2BA1A2B1B2

Ora lo usiamo per dimostrare che per due tipi qualsiasi Ae Bla funzione foorestituisce un elenco vuoto per qualsiasi input as : A list.

  • Sia e sia la relazione di identità, quindi ogni elenco di è banalmente correlato a se stesso.A1=A2= AAA
  • Consenti a , e qualsiasi relazione tra loro (ce n'è solo una, quella vuota, ma non importa).B1= ØB2= BB
  • asè correlato a se stesso (come abbiamo scelto la relazione di identità A), quindi foo as : Ø listè correlato a foo as : B list.
  • Sappiamo che due elenchi possono essere correlati solo se le loro lunghezze sono uguali e sappiamo anche che il primo degli elenchi risultanti deve essere vuoto, poiché non possono esserci elementi del Øtipo.

Pertanto, per qualsiasi A, Be as : A listabbiamo che foo as : B listdeve essere un elenco vuoto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.