Arend - idioma do tipo dependente baseado no HoTT (parte 2)

Na primeira parte do artigo sobre a linguagem Arend, examinamos os tipos indutivos mais simples, funções recursivas, classes e conjuntos.

2. Classificando listas no Arend


2.1 Listas ordenadas no Arend


Definimos o tipo de lista ordenada como um par que consiste em uma lista e uma prova de sua ordem. Como já dissemos, em Arend, pares dependentes são definidos usando a palavra-chave \Sigma . Fornecemos a definição do tipo Sorted por meio de comparação com a amostra, inspirada na definição do artigo já mencionado sobre listas ordenadas.

 \func SortedList (O : LinearOrder.Dec) => \Sigma (l : List O) (Sorted l) \data Sorted {A : LinearOrder.Dec} (xs : List A) \elim xs | nil => nilSorted | :-: x nil => singletonSorted | :-: x1 (:-: x2 xs) => consSorted ((x1 = x2) || (x1 < x2)) (Sorted (x2 :-: xs)) 

Nota: Arend conseguiu inferir automaticamente que o tipo Sorted está contido no universo \Prop . Isso aconteceu porque todos os três padrões na definição Sorted são mutuamente exclusivos e o construtor consSorted possui dois parâmetros, ambos pertencentes a \Prop .
Vamos provar alguma propriedade óbvia do predicado Sorted , digamos que o final de uma lista ordenada seja ela própria uma lista ordenada (essa propriedade será útil para nós no futuro).

 \func tail-sorted {O : LinearOrder.Dec} (x : O) (xs : List O) (A : Sorted (x :-: xs)) : Sorted xs \elim xs, A | nil, _ => nilSorted | :-: _ _, consSorted _ xs-sorted => xs-sorted 

Na tail-sorted usamos a correspondência de padrões na lista xs e o predicado Sorted ao mesmo tempo; além disso, usamos o caractere de salto “_”, que pode ser substituído por variáveis ​​não utilizadas.

Alguém pode perguntar se é possível em Arend provar a propriedade de listas ordenadas, mencionadas na seção 1.3 como um exemplo de um fato que não pode ser provado na Agda sem anotações de imaterialidade. Lembre-se de que essa propriedade afirma que, para provar a igualdade de listas ordenadas definidas por meio de pares dependentes, basta verificar a igualdade dos primeiros componentes dos pares.

Argumenta-se que em Arend essa propriedade é facilmente obtida como conseqüência da construção inProp mencionada acima e da propriedade de extensionalidade para pares SigmaExt dependentes.

 \func sorted-equality {A : LinearOrder.Dec} (l1 l2 : SortedList A) (P : l1.1 = l2.1) : l1 = l2 => SigmaPropExt Sorted l1 l2 P 

A propriedade SigmaPropExt provada no módulo Paths da biblioteca padrão, e muitos outros fatos do segundo capítulo do livro HoTT , incluindo a propriedade de extensionalidade funcional , também são provados lá.

O operador .n usado no Arend para acessar o projetor do tipo sigma com o número n (no nosso caso, o tipo sigma é SortedList A , e a expressão l1.1 significa que o primeiro componente desse tipo é uma expressão do tipo List A ).

2.2 Implementação da propriedade “be permutation”


Agora vamos tentar implementar a função de classificação de lista no Arend. Naturalmente, queremos ter não uma implementação simples do algoritmo de classificação, mas uma implementação junto com uma prova de algumas propriedades.

Claramente, esse algoritmo deve ter pelo menos 2 propriedades:
1. O resultado do algoritmo deve ser uma lista ordenada.
2. A lista resultante deve ser uma permutação da lista original.

Primeiro, vamos tentar implementar a propriedade "ser permutação" das listas no Arend. Para fazer isso, adaptamos a definição retirada daqui para Arend.

 \truncated \data InsertSpec {A : \Set} (xs : List A) (a : A) (ys : List A) : \Prop \elim xs, ys | xs, :-: y ys => insertedHere (a = y) (xs = ys) | :-: x xs, :-: y ys => insertedThere (x = y) (InsertSpec xs a ys) \truncated \data Perm {A : \Set} (xs ys : List A) : \Prop | permInsert (xs' ys' : List A) (a : A) (Perm xs' ys') (InsertSpec xs' a xs) (InsertSpec ys' a ys) | permTrivial (xs = ys) 

O predicado InsertSpec introduzido por nós tem o seguinte significado intuitivo: InsertSpec xs a ys significa exatamente que a lista ys é o resultado da inserção do elemento a dentro da lista xs (em qualquer posição). Assim, InsertSpec pode ser tomado como uma especificação da função de inserção.

Claramente, o tipo de dados Perm realmente define o relacionamento "ser permutação": o construtor permInsert exatamente que xs e ys são mutuamente permutáveis ​​se xs e ys são obtidos inserindo o mesmo elemento a em algumas listas xs' e ys' comprimentos mais curtos, que já são permutações um do outro.

Para nossa definição da propriedade "ser permutação", é fácil verificar a propriedade simetria.

 \func Perm-symmetric {A : \Set} {xs ys : List A} (P : Perm xs ys) : Perm ys xs \elim P | permTrivial xs=ys => permTrivial (inv xs=ys) | permInsert perm-xs'-ys' xs-spec ys-spec => permInsert (Perm-symmetric perm-xs'-ys') ys-spec xs-spec 

A propriedade transitividade também é satisfeita para Perm , mas sua verificação é muito mais complicada. Como essa propriedade não desempenha nenhum papel na implementação de nosso algoritmo de classificação, deixamos isso para o leitor como um exercício.

 \func Perm-transitive {A : \Set} (xs ys zs : List A) (P1 : Perm xs ys) (P2 : Perm ys zs) : Perm xs zs => {?} 

2.3 Mudança nos níveis de homotopia quando comparada com a amostra


Agora vamos tentar implementar uma função que insere um elemento em uma lista ordenada para que a lista resultante permaneça ordenada. Vamos começar com a seguinte implementação ingênua.

 \func insert {O : LinearOrder.Dec} (xs : List O) (y : O) : List O \elim xs | nil => y :-: nil | :-: x xs' => \case LinearOrder.trichotomy xy \with {  | byLeft x=y => x :-: insert xs' y  | byRight (byLeft x<y) => x :-: insert xs' y  | byRight (byRight y<x) => y :-: x :-: xs' } 

A construção \case permite a correspondência com uma amostra de uma expressão arbitrária ( \elim pode ser usado apenas no nível mais alto de uma definição de função e apenas para seus parâmetros). Se você solicitar à Arend para verificar o tipo de insert , a seguinte mensagem de erro será exibida.

 [ERROR] Data type '||' is truncated to the universe \Prop  which does not fit in the universe of the eliminator type: List OE In: | byLeft x-leq-y => x :-: insert xs' y While processing: insert 

O problema é que, na classe LinearOrder.Dec definição de trichotomy fornecida usando o operador || , que, por sua vez, é determinado usando truncamento proposicional. Como já mencionado, para tipos pertencentes ao universo \Prop , a correspondência com um padrão em Arend é permitida apenas se o tipo da expressão resultante for uma asserção (para a função acima, o resultado é do tipo List OE e esse tipo é um conjunto).

Existe alguma maneira de contornar esse problema? A maneira mais fácil de resolver isso é alterar a definição da propriedade da tricotomia. Considere a seguinte definição de tricotomia, usando o tipo não truncado Or vez do truncado || :

 \func set-trichotomy {A : StrictPoset} (xy : A) => ((x = y) `Or` (x < y)) `Or` (y < x) 

Esta definição trichotomy em algo da definição original da trichotomy através de || ? Por que usamos um tipo propositalmente truncado se isso apenas complica nossa vida e nos impede de usar a correspondência de padrões?

Vamos tentar responder à primeira pergunta para começar: para pedidos estritos do StrictPoset diferença entre trichotomy e trichotomy set-trichotomy não é de todo. Observe que o tipo de set-trichotomy é uma afirmação. Esse fato decorre do fato de que todas as três alternativas na definição de tricotomia são mutuamente exclusivas devido a axiomas de ordem, e cada um dos três tipos x = y, x < y, y < x é em si uma afirmação ( x = y é uma afirmação, portanto como na definição da classe BaseSet exigimos que a mídia E um conjunto!).

 \func set-trichotomy-isProp {A : StrictPoset} (xy : A) (l1 l2 : set-trichotomy xy): l1 = l2 \elim l1, l2 | inl (inl l1), inl (inl l2) => pmap (\lam z => inl (inl z)) (Path.inProp l1 l2) | inl (inr l1), inl (inr l2) => pmap (\lam z => inl (inr z)) (Path.inProp l1 l2) | inr l1, inr l2 => pmap inr (Path.inProp l1 l2) | inl (inl l1), inl (inr l2) => absurd (lt-eq-false l1 l2) | inl (inr l1), inl (inl l2) => absurd (lt-eq-false l2 l1) | inl (inl l1), inr l2 => absurd (lt-eq-false (inv l1) l2) | inr l1, inl (inl l2) => absurd (lt-eq-false (inv l2) l1) | inl (inr l1), inr l2 => absurd (lt-lt-false l1 l2) | inr l1, inl (inr l2) => absurd (lt-lt-false l2 l1) \where {  \func lt-eq-false {A : StrictPoset} {xy : A} (l1 : x = y) (l2 : x < y) : Empty =>    A.<-irreflexive x (transport (x <) (inv l1) l2)  \func lt-lt-false {A : StrictPoset} {xy : A} (l1 : x < y) (l2 : y < x) : Empty =>   A.<-irreflexive x (A.<-transitive _ _ _ l1 l2) } 

Na lista acima, absurd é a designação para o princípio ex falso quodlibet, definido no módulo Logic . Como o tipo Empty não possui construtores na definição (consulte a seção 1.2), não é necessário analisar os casos na definição de absurd :

 \func absurd {A : \Type} (x : Empty) : A 

Como sabemos agora que set-trichotomy é uma afirmação, podemos derivar a propriedade set-trichotomy propriedade usual da trichotomy em ordens decidíveis. Para fazer isso, podemos usar a construção \return \level , que informa ao timer Arend que, neste ponto, combinar com o padrão é uma operação permitida (nesse caso, temos que mostrar a prova de que o resultado da função set-trichotomy-property é uma instrução).

 \func set-trichotomy-property {A : LinearOrder.Dec} (xy : A) : set-trichotomy xy => \case A.trichotomy xy \return \level (set-trichotomy xy) (set-trichotomy-isProp xy) \with {  | byLeft x=y => inl (inl x=y)  | byRight (byLeft x<y) => inl (inr x<y)  | byRight (byRight y<x) => inr (y<x) } 

Vamos agora tentar responder à segunda pergunta, a saber, por que é preferível usar construções não comuns, mas propositalmente truncadas, ao formular as propriedades de objetos matemáticos. Considere para isso um fragmento da definição de ordens lineares não lineares (definições completas de Lattice e TotalOrder podem ser encontradas no módulo LinearOrder ):

 \class TotalOrder \extends Lattice { | totality (xy : E) : x <= y || y <= x } 

Vamos tentar imaginar agora como o significado da classe TotalOrder mudaria se TotalOrder a definição do campo da totalidade através da construção Or não truncada.

 \class BadTotalOrder \extends Lattice { | badTotality (xy : E) : (x <= y) `Or` (y <= x) } 

Nesse caso, o tipo (x <= y) `Or` (y <= x) não (x <= y) `Or` (y <= x) mais uma declaração, porque no caso de valores iguais de y ambas as alternativas na definição de badTotality podem ser implementadas, e a escolha do ramo esquerdo ou direito na prova de badTotality absolutamente arbitrária e permanece a critério do usuário - não há razão para preferir um construtor Or a outro.

Agora está claro qual é a diferença entre TotalOrder e BadTotalOrder . Dois conjuntos ordenados O1 O2 : TotalOrder são sempre iguais quando é possível provar a igualdade dos conjuntos O1.E, O2.E e as ordens O1.<, O2.< Dados sobre eles (esta é a propriedade desejada). Por outro lado, para O1 O2 : BadTotalOrder é BadTotalOrder provar a igualdade de O1 e O2 somente quando, além de todos os elementos x de E igualdade O1.badTotality xx e O2.badTotality xx .

Assim, verifica-se que a classe BadTotalOrder intuitivamente precisa ser considerada não como um "conjunto ordenado linearmente", mas como um "conjunto ordenado linearmente junto com a escolha para cada elemento x campo E ramo esquerdo ou direito Or na implementação de badTotality xx ".

2.4 Algoritmo de classificação


Agora, implementamos o algoritmo de classificação. Vamos tentar corrigir a implementação ingênua da função de insert da seção anterior usando a propriedade comprovada set-trichotomy-property (neste caso, devido ao arranjo mais bem-sucedido de colchetes na definição de set-trichotomy de set-trichotomy , reduzimos o número de casos considerados).

 \func insert {O : LinearOrder.Dec} (xs : List O) (y : O) : List O \elim xs | nil => y :-: nil | :-: x xs' => \case set-trichotomy-property xy \with {  | inr y<x => y :-: x :-: xs'  | inl x<=y => x :-: insert xs' y } 

Agora vamos tentar implementar um análogo dessa definição para listas ordenadas. Usaremos a construção especial \let … \in , que nos permite adicionar novas variáveis ​​locais ao contexto.

 \func insert {O : LinearOrder.Dec} (xs : SortedList O) (y : O) : SortedList O \elim xs | (nil, _) => (y :-: nil, singletonSorted) | (:-: x xs', xs-sorted) => \case set-trichotomy-property xy \with {  | inr y<x => (y :-: x :-: xs', consSorted (byRight y<x) xs-sorted)  | inl x<=y => \let (result, result-sorted) => insert (xs', tail-sorted x xs' xs-sorted) y         \in (x :-: result, {?}) 

Deixamos na prova um fragmento incompleto (indicado pela expressão {?} ) No local em que você deseja mostrar que a lista x :-: result ordenada. Embora no contexto haja evidências da ordem da lista de result , resta verificar se x não excede o valor do primeiro elemento da lista de result , o que não é tão fácil de seguir a partir das premissas do contexto (para ver todas as premissas no destino atual - é assim que chamamos ramo atual dos cálculos - é necessário solicitar a verificação do tipo na função de insert ).

Acontece que a insert muito mais fácil de implementar se provarmos a ordem da lista resultante em paralelo com a prova da especificação da insert . Altere a assinatura do insert e escreva a prova desta especificação nos casos mais simples:

 \func insert {O : LinearOrder.Dec} (xs : SortedList O) (y : O) : \Sigma (ys : SortedList O) (InsertSpec xs.1 y ys.1) \elim xs | (nil, _) => ((y :-: nil, singletonSorted), insertedHere idp idp) | (:-: x xs', xs-sorted) => \case set-trichotomy-property xy \with {  | inr y<x => ((y :-: x :-: xs', consSorted (byRight y<x) xs-sorted), insertedHere idp idp)  | inl x<=y =>   \let ((result, result-sorted), result-spec) => insert (xs', tail-sorted x xs' xs-sorted) y   \in ((x :-: result, {?}), insertedThere idp result-spec) 

Para um único fragmento deixado sem prova, o Arend produzirá o seguinte valor de contexto:

 Expected type: Sorted (x :-: (insert (\this, tail-sorted x \this \this) \this).1.1) Context:  result-sorted : Sorted (insert (\this, tail-sorted \this \this \this) \this).1.1  xs-sorted : Sorted (x :-: xs')  x : O  x<=y : Or (x = y) (O.< xy)  O : Dec  result : List O  y : O  xs' : List O  result-spec : InsertSpec xs' y (insert (xs', tail-sorted \this xs' \this) y).1.1 

Para concluir a prova, teremos que usar toda a potência do operador \case : usaremos a correspondência de padrões com 5 variáveis ​​diferentes e, como os tipos de algumas variáveis ​​podem depender dos valores de outras variáveis, usaremos a correspondência de padrões dependentes.

A construção dos dois pontos indica explicitamente como o tipo de algumas variáveis ​​comparadas depende dos valores de outras variáveis ​​(portanto, no tipo de variáveis xs-sorted, result-spec result-sorted em cada um dos \case vez de xs' e result corresponderá às amostras correspondentes).

A construção \return associa as variáveis ​​usadas para combinar o padrão com o tipo de resultado esperado. Em outras palavras, no destino atual, em cada uma das cláusulas \case , a amostra correspondente será substituída pela variável de result . Sem essa construção, essa substituição não seria realizada e o objetivo de todas as cláusulas \case coincidiria com o destino no lugar da própria \case expression.

 \func insert {O : LinearOrder.Dec} (xs : SortedList O) (y : O) : \Sigma (ys : SortedList O) (InsertSpec xs.1 y ys.1) \elim xs  | (nil, _) => ((y :-: nil, singletonSorted), insertedHere idp idp)  | (:-: x xs', xs-sorted) => \case set-trichotomy-property xy \with {   | inr y<x => ((y :-: x :-: xs', consSorted (byRight y<x) xs-sorted), insertedHere idp idp)   | inl x<=y =>     \let ((result, result-sorted), result-spec) => insert (xs', tail-sorted x xs' xs-sorted) y     \in ((x :-: result,       \case result \as result, xs' \as xs', xs-sorted : Sorted (x :-: xs'), result-spec : InsertSpec xs' y result, result-sorted : Sorted result       \return Sorted (x :-: result) \with {        | nil, _, _, _, _ => singletonSorted        | :-: r rs, _, _, insertedHere y=r _, result-sorted => consSorted (transport (\lam z => (x = z) || (x < z)) y=r (Or-to-|| x<=y)) result-sorted        | :-: r rs, :-: x' _, consSorted x<=x' _, insertedThere x2=r _, result-sorted => consSorted (transport (\lam z => (x = z) || (x < z)) x2=r x<=x') result-sorted }), insertedThere idp result-spec) 

No bloco de código acima, os primeiros argumentos complexos do construtor consSorted nos dois últimos parágrafos da comparação de padrões merecem comentários adicionais. Para entender o que essas duas expressões significam, as substituímos pela expressão {?} E pedimos ao cronômetro Arend que determine os alvos em ambas as posições.

Você pode ver que, ali e ali, o destino atual é o tipo (x = r) || O.< xr (x = r) || O.< xr . Além disso, no contexto do primeiro objetivo, existem premissas

 x<=y : Or (x = y) (O.< xy) y=r : y = r 

e no contexto das segundas instalações

 x<=x' : (x = x') || O.< xx' x2=r : x' = r. 

Intuitivamente claro: para provar o primeiro objetivo, basta substituir a variável r pela instrução correta Or (x = y) (O.< xy) e depois mudar para o tipo propositalmente truncado || usando a função Or-to-|| definida na Seção 1.3 . Para provar o segundo objetivo, basta substituir em (x = x') || O.< x x' (x = x') || O.< x x' vez da variável x' variável r .

Para formalizar a operação de substituição de expressão descrita, existe uma função de transport especial na biblioteca Arend padrão. Considere a assinatura dela:

 \func transport {A : \Type} (B : A -> \Type) {aa' : A} (p : a = a') (b : B a) : B a' 

No nosso caso, em vez da variável A precisamos substituir o tipo OE (pode ser omitido explicitamente se os outros argumentos de transport forem especificados) e, em vez de B , a expressão \lam (z : O) => (x = z) || (x < z) \lam (z : O) => (x = z) || (x < z) .

A implementação do algoritmo de classificação de inserção junto com a especificação não causa mais dificuldades particulares: para classificar a lista x :-: xs' , primeiro classificamos o insertSort da lista xs' usando uma chamada recursiva para insertSort e, em seguida, inserimos o elemento x nessa lista, preservando a ordem de ajudar a acessar a função de insert já implementada.

 \func insertSort {O : LinearOrder.Dec} (xs : List O) : \Sigma (result : SortedList O) (Perm xs result.1) \elim xs | nil => ((nil, nilSorted), permTrivial idp) | :-: x xs' => \let | (ys, perm-xs'-ys) => insertSort xs'                      | (zs, zs-spec) => insert ys x                  \in (zs, permInsert perm-xs'-ys (insertedHere idp idp) zs-spec) 

Cumprimos o objetivo inicial e implementamos a classificação das listas no Arend. Todo o código Arend fornecido neste parágrafo pode ser baixado em um arquivo a partir daqui .

Pode-se perguntar como seria necessário alterar a implementação da função insert se, em vez das ordens LinearOrder.Dec estritas, LinearOrder.Dec ordens LinearOrder.Dec não estritas? Como lembramos, na definição da função de totalidade, o uso da operação truncada || foi bastante significativo, ou seja, essa definição não é equivalente a uma definição em que, em vez de || usado por Or .

A resposta a esta pergunta é a seguinte: ainda é possível construir um análogo de insert para TotalOrder , no entanto, para isso, teríamos que provar que o tipo da função de insert é uma declaração (isso nos permitiria na definição de insert corresponder à amostra de acordo com a declaração de totality xy ).

Em outras palavras, teríamos que provar que existe apenas uma lista ordenada até a igualdade, que é o resultado da inserção do elemento y na lista ordenada xs . É fácil perceber que esse é um fato verdadeiro, mas sua prova formal não é mais tão trivial. Deixamos a verificação desse fato como um exercício para o leitor interessado.

3. Observações finais


Nesta introdução, nos familiarizamos com as principais construções da linguagem Arend e também aprendemos como usar o mecanismo de classe. Conseguimos implementar o algoritmo mais simples, juntamente com a prova de sua especificação. Assim, mostramos que o Arend é bastante adequado para resolver problemas "cotidianos", como, por exemplo, verificação de programas.

Mencionamos longe de todos os recursos do Arend. Por exemplo, dissemos quase nada sobre tipos com condições que permitem “colar” vários construtores de tipos com alguns valores de parâmetros especiais para esses construtores. Por exemplo, uma implementação do tipo inteiro no Arend é fornecida usando tipos com condições da seguinte maneira:

 \data Int | pos Nat | neg Nat \with { zero => pos zero } 

Essa definição diz que números inteiros consistem em duas cópias do tipo de números naturais, nos quais são identificados zeros "positivos" e "negativos". Essa definição é muito mais conveniente do que a definição da biblioteca Coq padrão, onde a "cópia negativa" dos números naturais deve ser "deslocada em um" para que essas cópias não se cruzem (é muito mais conveniente quando a notação neg 1 significa o número -1, não -2) .

Não dissemos nada sobre o algoritmo para derivar níveis predicativos e homotópicos nas classes e suas instâncias. Também dificilmente mencionamos o tipo de intervalo I , embora ele desempenhe um papel fundamental na teoria de tipos com intervalos, que são a base lógica de Arend. Para entender como esse tipo é importante, basta mencionar que, no tipo Arend, a igualdade é definida pelo conceito de intervalo. , , , (.. ).

: , HoTT JetBrains Research.

Source: https://habr.com/ru/post/pt470632/


All Articles