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.