A primeira ocorrência de Bad
é chamada 'negativa' porque representa um argumento de função, ou seja, está localizada à esquerda da seta da função (consulte Tipos recursivos gratuitamente por Philip Wadler). Eu acho que a origem do termo 'posição negativa' deriva da noção de contravariância ('contra' significa oposto).
Não é permitido que o tipo seja definido em posição negativa, porque é possível escrever programas não-terminativos usando-o, ou seja, uma forte normalização falha em sua presença (mais sobre isso abaixo). A propósito, esse é o motivo do nome da regra 'estrita positividade': não permitimos posições negativas.
Permitimos a segunda ocorrência de, Bad
uma vez que não causa terminação e queremos usar o tipo que está sendo definido ( Bad
) em algum momento de um tipo de dados recursivo ( antes da última seta do construtor).
É importante entender que a seguinte definição não viola a regra estrita de positividade.
data Good : Set where
good : Good → Good → Good
A regra se aplica somente aos argumentos do construtor (que são os dois Good
nesse caso) e não ao próprio construtor (consulte também " Programação Certificada com Tipos Dependentes ", de Adam Chlipala ).
Outro exemplo que viola uma estrita positividade:
data Strange : Set where
strange : ((Bool → Strange) → (ℕ → Strange)) → Strange
^^ ^
this Strange is ...this arrow
to the left of...
Você também pode verificar esta resposta sobre posições negativas.
Mais sobre não rescisão ... A página que você referenciou contém algumas explicações (junto com um exemplo em Haskell):
Declarações não estritamente positivas são rejeitadas porque é possível escrever uma função não-terminadora usando-as. Para ver como é possível escrever uma definição de loop usando o tipo de dados Ruim acima, consulte BadInHaskell .
Aqui está um exemplo análogo no Ocaml, que mostra como implementar o comportamento recursivo sem (!) Usando a recursão diretamente:
type boxed_fun =
| Box of (boxed_fun -> boxed_fun)
(* (!) in Ocaml the 'let' construct does not permit recursion;
one have to use the 'let rec' construct to bring
the name of the function under definition into scope
*)
let nonTerminating (bf:boxed_fun) : boxed_fun =
match bf with
Box f -> f bf
let loop = nonTerminating (Box nonTerminating)
A nonTerminating
função "desempacota" uma função de seu argumento e a aplica ao argumento original. O que é importante aqui é que a maioria dos sistemas de tipos não permite a passagem de funções para si mesmos; portanto, um termo como f f
não será checado tipicamente, pois não há um tipo para f
satisfazer o checador tipográfico. Um dos motivos pelos quais os sistemas de tipos foram introduzidos é desativar a autoaplicação (veja aqui ).
O agrupamento de tipos de dados como o que introduzimos acima pode ser usado para contornar esse obstáculo no caminho para a inconsistência.
Quero acrescentar que cálculos não-térmicos introduzem inconsistências nos sistemas lógicos. No caso da Agda e da Coq, o False
tipo de dados indutivo não possui construtores; portanto, você nunca pode criar um termo de prova do tipo False. Mas se cálculos não-terminantes fossem permitidos, seria possível, por exemplo, desta maneira (no Coq):
Fixpoint loop (n : nat) : False = loop n
Depois loop 0
, verificava-se a doação loop 0 : False
, portanto, sob a correspondência de Curry-Howard, isso significava que provávamos uma proposição falsa.
Resultado : a regra estrita de positividade para definições indutivas impede cálculos sem fim que são desastrosos para a lógica.
A
e explodir a pilha eventualmente (em idiomas baseados em pilha).