Funções ML de listas polimórficas para listas polimórficas

8

Estou aprendendo programação em ML (OCaml), e anteriormente perguntei sobre as funções de ML do tipo'a -> 'b . Agora eu tenho experimentado um pouco com funções do tipo 'a list -> 'b list. Existem alguns exemplos simples óbvios:

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

O que não consigo descobrir é como criar uma função que faça algo diferente de retornar a lista ou loop vazio (sem usar nenhuma função de biblioteca). Isso pode ser feito? Existe uma maneira de retornar listas não vazias?

Edit: Sim, se eu tiver uma função do tipo 'a -> 'b, então eu posso fazer outra, ou uma função do tipo 'a list -> 'b list, mas o que eu quero saber aqui é como fazer a primeira.

Gilles 'SO- parar de ser mau'
fonte
1
Como na pergunta anterior, direcione a programação de aprendizado do aluno CS101 em sua resposta, não o teórico do tipo que sua resposta possa inspirá-lo a se tornar mais tarde.
Gilles 'SO- stop be evil'
Observe que, se você tivesse uma função f com esse tipo retornando uma lista não vazia, o divertido a -> List.hd (f [a]) teria o tipo 'a ->' b sem ser finalizador ou gerar uma exceção.
Gallais

Respostas:

6

Bem, algo conhecido como parametridade nos diz que, se considerarmos o subconjunto puro de ML (ou seja, nenhuma recursão infinita refe todas essas coisas estranhas), não há como definir uma função com esse tipo além da que retorna o valor vazio. Lista.

Tudo isso começou com o artigo de Wadler, “ Teoremas de graça! ”. Este artigo, basicamente, nos diz duas coisas:

  1. Se considerarmos linguagens de programação que satisfazem certas condições, podemos deduzir alguns teoremas interessantes apenas observando a assinatura de tipo de uma função polimórfica (isso é chamado de Teorema da Parametricidade).
  2. ML (sem recursão infinita refe todas essas coisas estranhas) satisfaz essas condições.

Do Parametricity Teorema nós sabemos que se nós temos uma função f : 'a list -> 'b list, em seguida, para todos 'a, 'b, 'c, 'de para todas as funções g : 'a -> 'c, h : 'b -> 'dtemos:

map h ∘ f = f ∘ map g

(Observe, fà esquerda tem o tipo 'a list -> 'b liste fà direita é 'c list -> 'd list.)

Somos livres para escolher o que gquisermos, então vamos 'a = 'ce g = id. Agora desde map id = id(fácil de provar por indução na definição de map), temos:

map h ∘ f = f

Agora vamos 'b = 'd = boole h = not. Vamos supor que, para alguns, zs : bool listisso acontece f zs ≠ [] : bool list. É claro que map not ∘ f = fnão se sustenta, porque

(map not ∘ f) zs ≠ f zs

Se o primeiro elemento da lista à direita for true, à esquerda o primeiro elemento é falsee vice-versa!

Isso significa que nossa suposição está errada e f zs = []. Nós terminamos? Não.

Assumimos que 'bé bool. Mostramos que quando fé chamado com type f : 'a list -> bool listpara any 'a, fsempre deve retornar a lista vazia. Pode ser que quando chamamos f, f : 'a list -> unit listpois retorna algo diferente? Nossa intuição nos diz que isso não faz sentido: simplesmente não podemos escrever em ML pura uma função que sempre retorna a lista vazia quando queremos que ela nos forneça uma lista de booleanos e, caso contrário, pode retornar uma lista não vazia! Mas isso não é uma prova.

O que queremos dizer é que fé uniforme : se ela sempre retorna a lista vazia para bool list, deve retornar a lista vazia para unit liste, em geral, qualquer 'a list. É exatamente sobre isso que trata o segundo ponto da lista de marcadores no início da minha resposta.

O documento diz-nos que no ML fdeve tomar relacionados valores relacionados queridos. Não vou entrar em detalhes sobre relações, basta dizer que as listas estão relacionadas se, e somente se, tiverem comprimentos iguais e seus elementos estiverem relacionados em pares (isto é, [x_1, x_2, ..., x_m]e [y_1, y_2, ..., y_n]estão relacionados se e somente se m = ne x_1estiverem relacionados a y_1e x_2está relacionado y_2e assim por diante). E a parte divertida é, no nosso caso, uma vez que fé polimórfico, podemos definir qualquer relação nos elementos das listas!

Vamos escolher um 'a, 'be olhar f : 'a list -> 'b list. Agora olhe f : 'a list -> bool list; já mostramos que, nesse caso, fsempre retorna a lista vazia. Agora postulamos que todos os elementos de 'aestão relacionados a si mesmos (lembre-se, podemos escolher qualquer relação que desejamos), isso implica que qualquer um zs : 'a listesteja relacionado a si mesmo. Como sabemos, fleva valores relacionados aos relacionados, isso significa que f zs : 'b listestá relacionado f zs : bool list, mas a segunda lista tem comprimento igual a zero e, como a primeira está relacionada a ela, ela também está vazia.


Para completar, mencionarei que há uma seção sobre o impacto da recursão geral (possível não terminação) no artigo original de Wadler, e também há um artigo explorando teoremas livres na presença de seq.

Kirelagin
fonte
Agora eu suspeito que a prova pode ser feito em uma única etapa, se em vez de enfraquecer o teorema parametricity considerando relações específicas induzidas por funções ( ge h, neste caso) ir direto com as relações gerais feitos sob medida ...
kirelagin
Não, a parametricidade não começa com o artigo de Wadler (que afirma ser um resumo das abordagens para definir a parametridade). A idéia remonta ao artigo de Reynold "Tipos, abstração e polimorfismo paramétrico". A idéia também estava presente na prova de normalização de Girard para o Sistema F, até onde eu sei.
Daniel Gratzer
4

Vamos voltar aos objetos mais simples: você não pode construir um objeto apropriado do tipo, 'aporque isso significa que esse objeto xpode ser usado onde quer 'aque caiba. E isso significa em toda parte: como um número inteiro, uma matriz e até uma função. Por exemplo, isso significa que você pode fazer coisas como x+2, x.(1)e (x 5). Existem tipos exatamente para evitar isso.

É a mesma idéia que se aplica a uma função do tipo 'a -> 'b, mas há alguns casos em que esse tipo pode existir: quando a função nunca retorna um objeto do tipo 'b: ao repetir ou gerar uma exceção.

Isso também se aplica a funções que retornam uma lista. Se sua função é do tipo t -> 'b liste você cria um objeto do tipo te o aplica a essa função, isso significa que, se você acessar com êxito um elemento desta lista, terá acesso a um objeto que possui todos os tipos. Portanto, você não pode acessar nenhum elemento da lista: a lista está vazia ou ... não há lista.

No entanto, o tipo 'a list -> 'b listaparece nos exercícios habituais, mas é apenas quando você já tem uma função do tipo 'a -> 'b:

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

Mas você provavelmente conhece este.

val map : ('a -> 'b) -> 'a list -> 'b list
jmad
fonte
1
O teórico do tipo mais antigo não se entusiasma com esta resposta. Ok, um contexto de variável de tipo não vazio é uma maneira de ter uma função que é literalmente do tipo'a -> 'b ou 'a list -> 'b list, mas isso não é uma observação tão interessante. Na verdade, vou editar a pergunta para deixar claro que não é disso que o aluno mais jovem estava aprendendo a programar.
Gilles 'SO- stop be evil'
Mas o teórico do tipo mais antigo sabe que ML não é logicamente defeituoso. Se você pode produzir uma função f : 'a list -> 'b liste ttal quef t <> [] , em seguida, este programa irá escrever-check, mas pode fazer maneira pior do que levantar uma exceção: let y = List.hd (f t) in (y y) (y + y.(0) + y.(0).(0)).
Jmad
2

Teorema da Parametricidade dos "Teoremas de graça!" O artigo nos diz que os termos de ML têm uma propriedade muito especial: se considerarmos o tipo de um termo como uma relação em valores desse tipo, o valor desse termo será relacionado a ele mesmo. Aqui está como visualizar tipos como relações:

  • Um tipo de função 'a -> 'bcorresponde à relação definida dizendo que duas funções estão relacionadas se elas levarem valores relacionados a valores relacionados (assumindo 'ae 'bcorrespondendo a algumas relações).
  • Um tipo de lista 'a listcorresponde à relação definida dizendo que duas listas são relacionadas se tiverem o mesmo comprimento e seus elementos correspondentes estiverem relacionados (supondo que 'acorresponda a alguma relação).
  • (Agora a parte mais interessante.) Um tipo polimórfico corresponde à relação definida por dizer que dois valores polimórficos estão relacionados, se podemos escolher qualquer dois tipos, qualquer relação entre os elementos desses tipos, substitua todas as instâncias do tipo variável com este relação, e os valores resultantes ainda serão relacionados.

Aqui está um exemplo. Suponha que tenhamos um termo foo : 'a -> 'a. O Teorema da Parametricidade diz que fooestá relacionado a si mesmo. O que isso significa é que podemos escolher dois tipos, digamos,UMA1 e UMA2, escolha absolutamente qualquer relação UMA entre elementos desse tipo e, se tomarmos alguma uma1:UMA1 e uma2:UMA2, de modo que estejam relacionados de acordo com UMA, então foouma1 e foouma2 também será relacionado de acordo com UMA:

uma1UMAuma2foouma1UMAfoouma2.

Agora, se tomarmos a relação UMA não ser uma relação arbitrária, mas uma função f:UMA1UMA2, o acima se torna:

f(uma1)=uma2f(foouma1)=foouma2,

ou, em outras palavras:

f(foouma1)=foo(f(uma1)),

que é exatamente o teorema livre para a idfunção: f . id = id . f.


Se você executar etapas semelhantes para sua função foo : 'a list -> 'b list, poderá escolher dois tiposUMA1 e UMA2qualquer relação UMA entre seus elementos, dois tipos B1 e B2qualquer relação B entre seus elementos e, em seguida, faça duas listas, primeiro feitas de elementos de UMA1, segundo feito de elementos de UMA2, aplique sua função às duas listas (obtendo uma lista de B1 no primeiro caso e uma lista de B2 no segundo), e os resultados serão relacionados, se as entradas foram relacionadas.

Agora vamos usar isso para provar que para quaisquer dois tipos Ae Ba função fooretorna uma lista vazia para qualquer entrada as : A list.

  • Deixei UMA1=UMA2= A e deixar UMA seja a relação de identidade, portanto, qualquer lista de UMA é trivialmente relacionado a si mesmo.
  • Deixei B1= Ø, B2= B e B qualquer relação entre eles (existe apenas um, o vazio, mas isso não importa).
  • asestá relacionado a si mesmo (como escolhemos a relação de identidade A), portanto, foo as : Ø listestá relacionado a foo as : B list.
  • Sabemos que duas listas podem ser relacionadas apenas se seus comprimentos forem iguais e também sabemos que a primeira das listas resultantes deve estar vazia, pois não pode haver elementos do Øtipo.

Portanto, para qualquer A, Be as : A listtemos que foo as : B listtem que ser uma lista vazia.

Kirelagin
fonte