Park the bus

(typically of an away team) play in a very defensive way.

Agda tutorial を読んでみた: Sets (2)

今回は Constant Definitions から Parametric Sets まで。

Constant Definitions

nine : ℕ
nine = suc (suc (suc (suc (suc (suc (suc (suc (suc zero))))))))

ten : ℕ
ten = suc nine

(定数名) = (式) の形式で定数を定義することができます。 定義の前行に (定数名) : (型) の形式で定数の型を明示しています。 この型は曖昧にならない範囲で省略することができます。

ten, suc nine, そして suc (suc (suc (suc (suc (suc (suc (suc (suc zero)))))))) はどれも自然数10を表現しています。 この中で suc (suc (suc (suc (suc (suc (suc (suc (suc zero)))))))) のようにコンストラクタだけで表現されたものを 標準形 (normal form) と呼びます。

Decimal Notation for Natural Numbers

zerosuc を使った自然数の表現だと、3 あたりから値がいくつなのか把握するのが難しくなってきます。 かといって毎回値に名前をつけるのも面倒です。 幸いなことに、標準ライブラリで定義されている自然数 ℕ では値を十進数で表記することができます。

標準ライブラリの Data.Nat モジュールには自然数を表す型 ℕ 、およびそのコンストラクタ zerosuc が定義されています。 Data.Nat モジュールから ℕ, zero, suc を import することで、型とコンストラクタを利用できるようになります。

open import Data.Nat public using (ℕ; zero; suc)

three : ℕ
three = 3

three' : ℕ
three' = suc (suc (suc zero))

3suc (suc (suc zero)) の短縮形となります。 これ以降自然数 ℕ は、自分で定義した自然数ではなく、 Data.Nat モジュールで定義されたものを使用します。

Infix Notation

ここまでの例では、コンストラクタは前置の演算子として定義されていました。 agda では中置のコンストラクタを定義することも可能です。 もっと言えば後置や3つ以上の項を取る演算子も定義可能です。

data BinTree' : Set where
  x : BinTree'
  _+_ : BinTree' → BinTree' → BinTree'
  
infixr 3 _+_

BinTree' は内部ノードがラベルを持たない二分木を表す型です。 x は葉を、 _+_ は節を表すコンストラクタです。 _+_ のアンダースコアは + の前と後に1つずつ値をとることを表すプレースホルダです。 infixr 3 _+_+ が右結合で、結合の優先度が3 (高いほど優先して結合される)であることを宣言しています。 もし左結合にする場合は infixr の代わりに infixl を使います。 なお、これまでの例で出てきたアンダースコアを含まないコンストラクタは、前置の演算子として扱われます。

BinTree BinTree'
leaf x
node leaf leaf x + x
node (node leaf leaf) leaf (x + x) + x
node leaf (node leaf leaf) x + (x + x)
node (node leaf leaf) (node leaf leaf) (x + x) + (x + x)

Mutually Recursive Sets

型の定義を行う前に型の宣言を先行して行うことで、相互再帰的な型を定義することができます。

data L : Set
data M : Set

data L where
  nil : L
  _∷_ : ℕ → M → L

data M where
  _∷_ : Bool → L → M
  
L₁ : L
L₁ = nil 

R₁ : R
R₁ = true ∷ L₁

L₂ : L
L₂ = (suc zero) ∷ R₁

ちなみに \:: で入力できます。 あと -> は等価です。

Exercise: 各ノードが有限個の子を持つ木を定義せよ

木自体はデータ型 Tree で表すとします。 Tree のコンストラクタ node はある(部分)木の根のノードを表します。 nodeChildren データ型で表現される子の列を取ります。 一方 ChildrenTree の要素をもつリストです。 2つのコンストラクタ nil_,_ を持ち、 その要素は t1 , t2 , ... , tn , nil の形式で表されます (t1tnTree の要素)。

data Tree : Set
data Children : Set


data Tree where
  node : Children -> Tree

data Children where
  nil : Children
  _,_ : Tree -> Children -> Children

infixr 5 _,_

root : Tree
root = node nil

tree1 : Tree
tree1 = node (node nil , node nil , nil)

tree2 : Tree
tree2 = node (tree1 , node nil , tree1 , nil)

Parametric Sets

Definition of List

Agda のデータ型はパラメータを取ることができます。 データ型の宣言で、データ型名の後、コロンの前にパラメータを指定できます。

data List (A : Set) : Set where
  []  : List A
  _∷_ : A → List A → List A

infixr 5 _∷_

A および List A は Set の要素で、 AList A のパラメータです。 List AA 型の要素を持つリストとみなすことができます。 例えば List Bool の要素は true ∷ []false ∷ false ∷ [] などのリストです。 それぞれのリストは Bool 型の値を要素としています。

Cartesian Product

data _×_ (A B : Set) : Set where
  _,_ : A → B → A × B

infixr 4 _,_
infixr 2 _×_

A × B は Set AB の直積集合を表します。 a , bA × B の要素です (aA かつ bB)。 例えば Bool × Bool の要素は以下の4つになります。

true , true
true , false
false , true
false , false

なお _×_ が2つのパラメータ AB を取ることを data _×_ (A B : Set) の形式で宣言していますが、 data _×_ (A : Set) (B : Set) と等価です。

Mutually recursive sets

もちろん相互再帰で定義される型もパラメータを持つことができます。

data List₁ (A B : Set) : Set
data List₂ (A B : Set) : Set

data List₁ (A B : Set) where
  []  :                 List₁ A B
  _∷_ : A → List₂ A B → List₁ A B

data List₂ (A B : Set) where
  _∷_ : B → List₁ A B → List₂ A B

ちなみに以下のような非正規再帰型 (non-regular recursive type) でも同様に定義することができます。

data AlterList (A B : Set) : Set  where
  []  :                     AlterList A B
  _∷_ : A → AlterList B A → AlterList A B

Exercise: List₁ ⊤ Bool の最も小さい最初の5要素を示せ

「最も小さい」の定義は、構成する項数が最も少ないもの、と解釈。

[]
true :: tt :: []
false :: tt :: []
true :: tt :: true :: tt :: []
true :: tt :: false :: tt :: []

Agda tutorial を読んでみた: Sets (1)

Agda Tutorial を読んで、自分なりにまとめたメモ。 今回は Modules から Recursive Sets まで。

Modules

すべての Agda module は以下の形式のヘッダから定義が始まります。

module Modules.Basic where
  • module と where は Agda の keyword (予約語)
  • モジュール名とファイルシステム上でのファイル名は一致させる必要があります。先の例ではファイル名を Modules/Basic.agda のようにします
  • EmacsAgda module を開いて agda2-load (C-c C-l) を実行すると、正しく読み込めた場合は Syntax Highlight されます

Enumerated Sets

The Bool set

data Bool : Set where
  true  : Bool
  false : Bool

上記定義の意味するところは

  • Bool の型は Set (任意の集合を要素に持つ集合)
  • true, false は Bool の要素でそれぞれ異なる
  • true, false 以外には Bool の要素は存在しない

true や false は Bool データ型のコンストラクタ(constructor) と呼ばれます。

Agda では Block 構造をインデントで表します (off-side rule)。 Bool のコンストラクタなどは data … の行よりも一段深く字下げしてから記述します。 なお、: の前後にはスペースが必要です。 Bool: Set のように省略してしまうと Bool: を1識別子として解釈してしまいます。

Isomorphisms

data Bool : Set where
  true  : Bool
  false : Bool

data Bool' : Set where
  true'  : Bool'
  false' : Bool'

Bool と Bool' は異なるデータ型ですが、 true と true'、false と false' の間に一対一の対応関係があるので同型(isomorphic)です。

Representation and interpretation

解釈 (interpretation) と表現 (representation)は逆の関係です

  • Bool は「真理値(Boolean)の集合」と解釈することができます
  • 真理値の集合の一つの表現が Bool です
  • 真理値の集合の Bool とは異なる表現の一つが Bool' です

Special finite sets

Agda では要素数が n = 0, 1, 2, … の集合を定義することができます。 その中で、 n = 0, 1 の場合の特別な集合に ⊥, ⊤ があります。

data ⊥ : Set where

data ⊤ : Set where
  tt : ⊤

余談ですが ⊥ および ⊤ は input-method を Agda に設定したあと、それぞれ \bot, \top で入力できます。Adga input method で入力できる記号の一覧は こちら

Types vs. sets

型(types) と集合(sets) には次の基本的な違いがあります。

  • 要素の型はユニークだが、要素は異なる集合の要素になることができる (例えば true は同時に2つの異なる型の要素にはなれない)
  • 型はその要素のコレクションではないが、集合はその要素によって特徴づけられる (例えば異なる空集合が存在する)

data は型を定義するもので、集合を定義するものではありません

いくつかの理由から集合より型を好んで使う、とあるのですが自分は今のところその理由は理解できていません。そのうちわかるでしょう、きっと。

Recursive Sets

Definition of ℕ

ペアノの公理に基づいて自然数 ℕ を定義します。

data ℕ : Set where
  zero :      ℕ
  suc  : ℕ → ℕ

zero は 0 を、suc は自然数 n の後者(successor)を表すコンストラクタとなります。 suc : ℕ → ℕ は ℕ 型の要素をとって ℕ 型の要素を返すコンストラクタを表しています。

Agda 上の項と数値表現の対応は以下のようになります。

数値表現
zero 0
suc zero 1
suc (suc zero) 2
suc (suc (suc zero)) 3

なお、Emacs の agda2-infer-type-maybe-toplevel (C-c C-d) コマンドを使うと、与えた項の型を確認することができます。 上の表の項 zero, suc zero, … の型が ℕ であることが確認できます。

自然数には、以下のような ℕ とは別の表現を考えることもできます。

data ℕ⁺ : Set where
  one      :      ℕ⁺
  double   : ℕ⁺ → ℕ⁺
  double+1 : ℕ⁺ → ℕ⁺

data ℕ₂ : Set where
  zero :      ℕ₂
  id   : ℕ⁺ → ℕ₂

ℕ₂ でもコンストラクタ zero が定義されています。 曖昧にならない場合に限り、同じ名前のコンストラクタを複数の型で定義することができます(どういう場合に曖昧になるんですかね?)。

ℕ と ℕ₂ は同型で、以下のような対応関係があります。

ℕ₂
zero zero
suc zero id one
suc (suc zero) id (double one)
suc (suc (suc zero)) id (double+1 one)

Rationale behind different representations

ℕ と ℕ₂ はどちらも自然数を定義する型です。 どちらか一方があれば良いようにも思えますが、使い分けることで扱う問題によっては簡潔な表現ができる場合があります。 例えばある自然数 n に対して n * 2 を計算する場合、 ℕ₂ であれば単に double コンストラクタを使うことで表現できます (実際は ℕ₂ を剥がして ℕ⁺ の値を取り出す必要はありますが)。 一方 ℕ の要素 n に対しては、何らかの手段で ℕ 上の積演算を定義する必要があります。

Binary trees

節点が値を持たない二分木は以下のように定義できます。

data BinTree : Set where
  leaf : BinTree
  node : BinTree → BinTree → BinTree

f:id:htk16:20170507164212p:plain

上図の二分木は左から順に下記の項で表現されます。

leaf
node leaf leaf
node (node leaf leaf) (node leaf leaf)
node (node (node leaf leaf) leaf) leaf

また、葉のみが自然数をラベルに持つ二分木は以下のように定義できます。

data BinTreeℕ : Set where
  leaf : ℕ -> BinTreeℕ
  node : BinTreeℕ -> BinTreeℕ -> BinTreeℕ

Ubuntu 16.04 に Agda をインストールする

Ubuntu 16.04 に Agda をインストールする

Ubuntu 16.04 上に Agda と推奨開発環境の Emacs をインストールします。 なお Emacs の設定を簡単に済ませるために Spacemacs を使用します。 Spacemacs を使用しない場合は Agda 2 Readme にしたがって Emacs の設定を行ってください。

The Haskell tool stack

公式ドキュメント の手順にしたがって stack をインストールします。

$ curl -sSL https://get.haskellstack.org/ | sh
$ stack update
$ stack setup
$ stack install ghc-mod
$ stack install apply-refact hlint stylish-haskell hoogle

Agda

stack を使って、Adga 本体と必要なライブラリをインストールします。

$ sudo apt-get install libtinfo-dev
$ stack install Agda
$ stack install alex happy cpphs

2017/03/01 の時点では Agda version 2.5.2 がインストールされました。

agda-stdlib

標準ライブラリである agda-stdlib は自前でインストールする必要があります。 ダウンロードページ から version 0.13 (agda-stdlib-0.13.tar.gz) をダウンロードして、以下の手順でインストールします。

$ cd ~
$ mkdir -p .agda/lib
$ cd ~/.agda/lib
$ mv ~/Downloads/agda-stdlib-0.13.tar.gz .
$ tar zxfv ~/Downloads/agda-stdlib-0.13.tar.gz
$ echo "$HOME/.agda/lib/agda-stdlib-0.13/standard-library.agda-lib" > ~/.agda/libraries

Emacs & Spacemacs

$ sudo apt-get install emacs
$ git clone https://github.com/syl20bnr/spacemacs ~/.emacs.d

前述の設定を行ったあとに Emacs を起動すると、 Spacemacs の初期設定が行われます。 まず最初に、キーバインドEmacs 本来のものにするか、 Vim like にするか確認されるので、お好みに応じて選択してください。 それ以降の質問に対しては初期選択されている項目を選択しておけば問題ないです。

Agda layer

Spacemacs では Emacs Lisp パッケージ群とその設定が configuration layer という単位でまとめられています。 Agda layer が用意されているので、これを利用します。 Agda layer を有効にするために、~/.spacemacs の dotspacemacs/layers で定義されている dotspacemacs-configuration-layers へ agda を追加します。

(defun dotspacemacs/layers ()
  (setq-default
   dotspacemacs-configuration-layers
   '(
     agda   ;; 追加
     ...
     )
   ))

設定後 Emacs を再起動すると、 Adga layer に関連するパッケージがインストールされます。 Layer 設定の詳細は Spacemacs Documentation を参照してください。