├── .gitignore ├── 1-기초-입문 ├── 3-1.png ├── 3-2.png ├── 3-3.png ├── 3-4.png ├── 3-5.png ├── 4-1.png ├── 4-2.png ├── 9.준비 완료.md ├── 2.Agda 설치 및 기본.md ├── 1.Agda 같은 언어가 필요한 이유.md ├── 7.Universe Levels.md ├── WellOrderingPrinciple.agda ├── 6.Equality.md ├── 3.값, 타입, 함수.md ├── 8.고전적 수학.md ├── 4.타입+.md └── 5.명제와 집합.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | _/*.* 2 | *.agdai 3 | 2-수학 -------------------------------------------------------------------------------- /1-기초-입문/3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lunuy/agda-introduction-korean/HEAD/1-기초-입문/3-1.png -------------------------------------------------------------------------------- /1-기초-입문/3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lunuy/agda-introduction-korean/HEAD/1-기초-입문/3-2.png -------------------------------------------------------------------------------- /1-기초-입문/3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lunuy/agda-introduction-korean/HEAD/1-기초-입문/3-3.png -------------------------------------------------------------------------------- /1-기초-입문/3-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lunuy/agda-introduction-korean/HEAD/1-기초-입문/3-4.png -------------------------------------------------------------------------------- /1-기초-입문/3-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lunuy/agda-introduction-korean/HEAD/1-기초-입문/3-5.png -------------------------------------------------------------------------------- /1-기초-입문/4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lunuy/agda-introduction-korean/HEAD/1-기초-입문/4-1.png -------------------------------------------------------------------------------- /1-기초-입문/4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lunuy/agda-introduction-korean/HEAD/1-기초-입문/4-2.png -------------------------------------------------------------------------------- /1-기초-입문/9.준비 완료.md: -------------------------------------------------------------------------------- 1 | # 준비 완료! 2 | 이제 Agda로 수학을 하기 위한 도구들이 모두 갖춰졌다. 이제 Agda를 활용해서 수학기초론부터 사칙연산, 미적분, 위상수학 등 모든 수학을 형식화할 수 있다. 원한다면 물리학, 컴퓨터 과학 등 다른 학문의 연역적인 부분들을 형식화할 수도 있다. 3 | 4 | 기본적인 틀은 이미 모두 다뤘으므로, 나머지 모르는 개념들은 필요할 때에 검색해 보면 어느정도 이해할 수 있을 것이다. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # agda-introduction-korean 2 | 수학 증명언어 Agda 입문을 위한 가이드. 3 | 4 | 이 입문 가이드는 Agda라는 언어를 곧바로 쉽게 쓸 수 있게끔 하는 것이 목표이다. 5 | 따라서 복잡한 이론적 배경에 대해 다루는 대신, 익숙한 대상들로 구성된 예제들을 많이 다루는 방향으로 진행된다. 6 | 글을 읽다가 의문이 드는 것이 있다면 Agda 공식 사이트나 다른 안내서를 참고하는 것이 도움이 될 수 있다. -------------------------------------------------------------------------------- /1-기초-입문/2.Agda 설치 및 기본.md: -------------------------------------------------------------------------------- 1 | 2 | # Haskell 설치 3 | [ghcup](https://www.haskell.org/ghcup/)으로 haskell을 먼저 설치해준다. Powershell을 열고 사이트 중앙에 있는 명령을 붙여넣어 실행하면 된다. 4 | 5 | # Agda 설치 6 | ``` 7 | cabal update 8 | cabal install Agda 9 | ``` 10 | cmd에 명령을 실행한다. 11 | 12 | ``` 13 | C:\cabal\bin 14 | ``` 15 | 환경변수 PATH에 위 경로를 추가한다. 16 | 17 | # VSCode 및 VSCode 플러그인 설치 18 | [VSCode](https://code.visualstudio.com/)를 설치한다. 19 | VSCode에서 왼쪽 extensions 탭에서 agda-mode 라는 플러그인을 검색해 설치한다. 20 | 21 | # 코드 작성법 22 | agda 파일은, 파일이 (파일명).agda 라고 하면 23 | ```agda 24 | module (파일명) where 25 | 26 | -- 여기에 코드 27 | ``` 28 | 이렇게 작성하면 된다. 29 | 30 | 왜 이렇게 해야하는지 궁금하다면 Agda의 모듈 시스템에 대해 알아보기 바란다. 31 | 32 | # Agda 관련 조작법 33 | ## 특수문자 입력 34 | agda 파일에서, \ 키를 누르고 [Agda Emacs Symbols](https://people.inf.elte.hu/divip/AgdaTutorial/Symbols.html)에 있는 특수문자 입력을 위한 글자들을 입력하면 특수문자를 입력할 수 있다. 예를 들어 →를 입력하고 싶으면 \r을 입력하면 된다. 35 | 36 | ## Agda 기능 37 | ``` 38 | Ctrl+C, Ctrl+L: 현재 열린 Agda 파일 로드(증명의 유효성을 검증해주고 하이라이팅 해줌) 39 | Ctrl+C, Ctrl+C: Case split 40 | Ctrl+C, Ctrl+A: Auto 41 | ``` 42 | 코드를 작성하고 Ctrl+C, Ctrl+L을 연달아 눌러서 Agda 파일을 로드하는 것만 반드시 하면 된다. 43 | 44 | agda-mode 플러그인의 안내 페이지에 가보면 더 자세히 있다. -------------------------------------------------------------------------------- /1-기초-입문/1.Agda 같은 언어가 필요한 이유.md: -------------------------------------------------------------------------------- 1 | # Agda 2 | Agda는 수학적 명제를 증명하는데 사용될 수 있는 프로그래밍 언어이다. 유저가 수학 증명을 Agda 언어 코드 형태로 입력하면, Agda는 그 증명이 올바른지 검증해줄 수 있다. 3 | 4 | # Agda 같은 언어가 왜 필요할까? 5 | 이미 수학책에 올바른 증명과정들이 들어있음에도 불구하고, Agda 같은 컴퓨터 언어로 증명을 나타내는 일이 필요한 이유(장점)는 다음과 같다. 6 | 7 | - 인간은 실수할 수 있는데 반해, 컴퓨터는 실수를 하지 않는다. 8 | - 누구나 증명과정에 대한 유효성을 검증해볼 수 있다. 9 | - 수학 용어의 뜻을 명확하게 알 수 있다. 10 | 11 | ## 인간은 실수할 수 있는데 반해, 컴퓨터는 실수를 하지 않는다. 12 | 수학책도 틀릴 수 있다는 것이다. 반면 컴퓨터는 틀리지 않는다. 13 | 14 | ## 누구나 증명과정에 대한 유효성을 검증해볼 수 있다. 15 | 수학은 연역적인 추론 과정만으로 이루어지고, 따라서 모든 사람이 늘 같은 결론을 얻을 수 있다. 하지만 수학자가 아닌 많은 사람들은 수학의 증명 과정 하나하나에 신경을 쏟을 수 없다. 그렇기 때문에 책이나 강의의 권위에 의존해 수학적 결과를 받아들인다. 하지만 문제는, 수학은 이런 식으로 권위에 의존하는 학문이 아니라는 것이다. Agda는 이런 문제를 해결할 수 있다. Agda를 사용하는 경우, 누구나 Agda 코드를 컴파일/타입 체크하는 것만으로 증명과정에 대한 유효성을 검증해볼 수 있다. 권위들에 의존할 필요가 없이, 온전히 수학적 사실들이 유효하다는 사실을 받아들일 수 있다. 16 | 17 | 다만, Agda에 의지해 증명의 유효성을 따지는 경우에도 Agda라는 프로그램의 권위에 의존해야 하는 것은 사실이다. 하지만 책이나 사람의 권위에 의지하는 것보다 훨씬 적고 명확한 대상에 의존하는 것이므로 훨씬 더 낫다. 18 | 19 | ## 수학 용어의 뜻을 명확하게 알 수 있다. 20 | 수학적 표기가 사용된 글을 보면 표기가 정확히 어떤 대상을 나타내는 것인지 알기 힘든 때가 있는데, 컴퓨터 언어로 수학적 표현을 작성하면 모든 표현들이 정확히 무엇을 나타내는 것인지 알 수 있다. 21 | 22 | 이 세 가지 장점들의 공통점은 수학에 대한 혼란을 덜어 준다는 점이다. 증명의 유효성이 보장되고, 권위에 대한 고려 없이 그 자체로 증명의 유효성을 보장받을 수 있고, 수학 표기가 무엇인지 명확히 알 수 있는 환경은 기존의 환경보다 훨씬 좋다. 이런 환경 속에서라면 많은 사람들이 수학을 더 쉽게 접할 수 있을 것이다. 또 수학이 정말 '절대로 틀리지 않는 진리'에 대한 학문으로 보일 수 있을 것이다. 23 | 24 | 25 | p.s. 아마 '절대로 틀리지 않는 진리'라는 표현을 보고, 비유클리드 기하학 같은 것을 떠올리며 '진리는 없는데?' 하는 생각을 한 사람들이 있을 것이다. 하지만 그런 주제들의 등장이 오히려 수학이 진정한 진리를 향하도록 만들어주었다. 더 알아보고 싶다면 수학기초론에 대해 찾아보길 바란다. -------------------------------------------------------------------------------- /1-기초-입문/7.Universe Levels.md: -------------------------------------------------------------------------------- 1 | 2 | # 러셀의 역설 3 | $$P=\lbrace 자기자신을\space포함하지\space않는\space집합\rbrace$$ 4 | 5 | $$P=\lbrace x|x \notin x, x \space is \space set\rbrace$$ 6 | 7 | 자기자신을 포함하지 않는 집합들을 원소로 갖는 집합 P가 있다고 해 보자. P는 P의 원소일까? 8 | 9 | - P ∈ P 라면: 그러면 P는 자기자신을 포함하는 집합이므로, P의 원소가 아니다. 즉 P ∉ P이다. 이는 P ∈ P라는 전제와 모순이다. 10 | - P ∉ P 라면: 그러면 P는 자기자신을 포함하지 않는 집합이므로, P의 원소이다. 즉 P ∈ P이다. 이는 P ∉ P라는 전제와 모순이다. 11 | 12 | 그래서 P는 P의 원소인 것도 아니고, P의 원소가 아닌 것도 아니다. 모순이다! 13 | 14 | 이를 "러셀의 역설"이라고 부르며, 현대 수학은 집합을 정의하는 방법을 제한함으로서 P 같은 집합을 정의할 수 없도록 해 러셀의 역설을 해결한다. (즉 현대 수학에서 P와 같은 집합은 존재하지 않는다. 따라서 모순이 발생하지 않는다.) 15 | 16 | ## Agda에서 러셀의 역설? 17 | Agda에서 타입이 집합 역할을 할 수 있음을 생각해보면, Agda에서도 러셀의 역설이 발생할 수 있지 않을까 상상해볼 수 있을 것이다. 18 | 19 | 하지만 Agda에서 러셀의 역설을 일으키는 타입은 정의할 수 없다. Agda도 현대 수학과 마찬가지로 이런 모순이 발생되는 것을 막기 때문이다. 20 | 21 | # Universe Levels 22 | 자기자신을 포함하는 집합이 정의될 수 없다면 러셀의 역설은 발생하지 않을 것이다. Agda에서는 타입들에 level을 부여해, 모든 타입의 값은 그 타입보다 level이 낮은 타입만을 포함할 수 있도록 제한해 자기자신을 포함하는 집합 같은 것을 만들 수 없도록 한다. 이로서 모순을 막는다. 23 | 24 | ```agda 25 | -- Level 0인 타입들 26 | -- 지금까지 Set이라고 쓰던 것은 Set₀ 과 같다. 27 | NatSet : Set₀ 28 | NatSet = Nat 29 | 30 | NatNatProduct : Set₀ 31 | NatNatProduct = Nat × Nat 32 | 33 | NatNatFunction : Set₀ 34 | NatNatFunction = Nat → Nat 35 | ``` 36 | 지금까지 본 타입들은 모두 Level 0인 타입들이다. Level이 0인 타입의 값은, Level 0 타입의 값을 포함할 수 있다. 또는 값을 입력받아 값을 출력하는 함수를 포함할 수 있다. 37 | 38 | ```agda 39 | record Magma : Set₁ where 40 | field 41 | Carrier : Set 42 | _∙_ : Carrier → Carrier → Carrier 43 | 44 | DoubleNegationElimination : Set₁ 45 | DoubleNegationElimination = (A : Set) → (¬¬ A) → A 46 | 47 | Type-of-Set-is-Set₁ : Set₁ 48 | Type-of-Set-is-Set₁ = Set 49 | 50 | -- Magma를 이용하면, 타입과 그 타입에 관한 이항연산을 저장할 수 있다. 51 | -- Magma 값 안에는 Level 0 타입이 저장되기 때문에, Magma는 Level 1 타입이다. 52 | Nat-add-magma : Magma 53 | Nat-add-magma = record 54 | { Carrier = Nat 55 | ; _∙_ = add 56 | } 57 | ``` 58 | Level 1인 타입의 값은, Level 0 타입을 포함할 수 있다. 또는 Level 0 타입을 입력받거나 출력하는 함수를 포함할 수 있다. 59 | 60 | ```agda 61 | Type-of-Set₁-is-Set₂ : Set₂ 62 | Type-of-Set₁-is-Set₂ = Set₁ 63 | 64 | Type-of-Set₂-is-Set₃ : Set₃ 65 | Type-of-Set₂-is-Set₃ = Set₂ 66 | 67 | Type-of-Set₃-is-Set₄ : Set₄ 68 | Type-of-Set₃-is-Set₄ = Set₃ 69 | ``` 70 | 마찬가지로 Level n 타입이 존재한다. 71 | 72 | # Level 산술 73 | ```agda 74 | lzero : Level 75 | lsuc : (n : Level) → Level 76 | _⊔_ : (n m : Level) → Level 77 | ``` 78 | `Agda.Primitive` 모듈에 있는 세 가지 연산을 이용해 Level을 연산할 수 있다. `lzero`는 0 level을 나타내고, `lsuc`으로는 level을 1 증가시킬 수 있다. `_⊔_`로는 두 level 중 더 큰 level을 얻을 수 있다. 79 | 80 | ## 예시: `_×_` 81 | ```agda 82 | record _×_ (A : Set) (B : Set) : Set where 83 | field 84 | fst : A 85 | snd : B 86 | ``` 87 | 앞서 본 `_×_` 타입 안에는 값만 담을 수 있다. 88 | 89 | ```agda 90 | two-sets : Set × Set 91 | two-sets = record 92 | { fst = Nat 93 | ; snd = Int 94 | } 95 | ``` 96 | 타입을 담을 수가 없다. 97 | 98 | ```agda 99 | open import Agda.Primitive using (Level; _⊔_) 100 | 101 | record _×_ {l₁ l₂ : Level} (A : Set l₁) (B : Set l₂) : Set (l₁ ⊔ l₂) where 102 | field 103 | fst : A 104 | snd : B 105 | ``` 106 | `_×_`가 임의 Level의 타입의 값을 내부에 저장할 수 있도록 정의를 바꾸어주면 `_×_` 안에 타입을 담을 수 있다. 107 | - `_×_`는 먼저 `l₁`과 `l₂` 두 Level을 입력받는다. 108 | - 그리고 `l₁` Level의 타입 A, `l₂` Level의 타입 B를 입력받는다. 109 | - `_×_` 내부에는 A 타입과 B 타입의 값이 저장되므로, `A × B` 타입의 Level은 A의 Level과 B의 Level 중 더 큰 Level이어야 한다. `_⊔_`를 이용해 `l₁`과 `l₂` 중 더 큰 값을 계산해 Level을 설정한다. 110 | 111 | ```agda 112 | NatNatProduct : Set₀ 113 | NatNatProduct = Nat × Nat 114 | 115 | a : NatNatProduct 116 | a = record 117 | { fst = zero 118 | ; snd = zero 119 | } 120 | 121 | NatSetProduct : Set₁ 122 | NatSetProduct = Nat × Set 123 | 124 | b : NatSetProduct 125 | b = record 126 | { fst = zero 127 | ; snd = Int 128 | } 129 | 130 | SetSetProduct : Set₁ 131 | SetSetProduct = Set × Set 132 | 133 | c : SetSetProduct 134 | c = record 135 | { fst = Int 136 | ; snd = Nat 137 | } 138 | ``` 139 | 140 | ## 예시: `_⊎_` 141 | ```agda 142 | data _⊎_ {l₁ l₂ : Level} (A : Set l₁) (B : Set l₂) : Set (l₁ ⊔ l₂) where 143 | inj₁ : A → A ⊎ B 144 | inj₂ : B → A ⊎ B 145 | ``` 146 | `_⊎_`도 유사하게 확장해 정의할 수 있다. 147 | 148 | ## 예시: Principle of explosion 149 | ```agda 150 | explode : {l : Level} {A : Set l} → ⊥ → A 151 | explode () 152 | ``` 153 | `⊥`로 임의 레벨 타입의 값을 만들어낼 수 있다. 154 | 155 | # 참고 156 | - [Universe Levels](https://agda.readthedocs.io/en/v2.6.3/language/universe-levels.html) 157 | - Agda의 Level 기능을 꺼서, Agda에서 러셀의 역설을 구현하는 법: https://gist.github.com/jhrr/ec2254d3c9c61e706f68 158 | - {-# OPTIONS --type-in-type #-} 구문으로 Agda의 Level 체크 기능을 꺼서, 러셀의 역설을 구현한다. 159 | - Level이 왜 필요한지 알 수 있다. -------------------------------------------------------------------------------- /1-기초-입문/WellOrderingPrinciple.agda: -------------------------------------------------------------------------------- 1 | 2 | module WellOrderingPrinciple where 3 | 4 | data Nat : Set where 5 | zero : Nat 6 | suc : Nat → Nat 7 | 8 | data _⊎_ (A : Set) (B : Set) : Set where 9 | inj₁ : A → A ⊎ B 10 | inj₂ : B → A ⊎ B 11 | 12 | record Σ (A : Set) (B : A → Set) : Set where 13 | field 14 | fst : A 15 | snd : B fst 16 | 17 | data ⊥ : Set where 18 | 19 | explode : {A : Set} → ⊥ → A 20 | explode () 21 | 22 | -- Negation 23 | ¬_ : Set → Set 24 | ¬ P = P → ⊥ 25 | 26 | ¬¬_ : Set → Set 27 | ¬¬ P = (P → ⊥) → ⊥ 28 | 29 | ¬¬¬-elim : {A : Set} → ¬¬ (¬ A) → ¬ A 30 | ¬¬¬-elim a = λ z₁ → a (λ z₂ → z₂ z₁) 31 | 32 | ¬¬⊥-elim : ¬¬ ⊥ → ⊥ 33 | ¬¬⊥-elim a = a (λ z → z) 34 | 35 | law-of-excluded-middle : (P : Set) → ¬¬ (P ⊎ (¬ P)) 36 | law-of-excluded-middle P a = a (inj₂ (λ x → a (inj₁ x))) 37 | 38 | _>>=_ : {A B : Set} → ¬¬ A → (A → ¬¬ B) → ¬¬ B 39 | a >>= f = λ z₁ → a (λ z₂ → f z₂ z₁) 40 | 41 | pure : {A : Set} → A → ¬¬ A 42 | pure a = λ f → f a 43 | 44 | 45 | -- Util 46 | case_of_ : {A : Set} {B : Set} → A → (A → B) → B 47 | case x of f = f x 48 | 49 | 50 | -- Equality 51 | data _≡_ {A : Set} (a : A) : A → Set where 52 | refl : a ≡ a 53 | 54 | sym : {A : Set} → {a b : A} → a ≡ b → b ≡ a 55 | sym refl = refl 56 | 57 | trans : {A : Set} → {a b c : A} → a ≡ b → b ≡ c → a ≡ c 58 | trans refl x = x 59 | 60 | cong : {A B : Set} {a b : A} → (f : A → B) → a ≡ b → f a ≡ f b 61 | cong f refl = refl 62 | 63 | 64 | -- NotEmpty P 는 "자연수의 부분 집합 P는 공집합이 아니다"라는 명제를 나타내는 타입이다. 65 | NotEmpty : (Nat → Set) → Set 66 | NotEmpty P = Σ Nat P 67 | 68 | 69 | -- a ≤ b라는 명제를 나타내는 타입 70 | data _≤_ : Nat → Nat → Set where 71 | ≤-zero : {n : Nat} → zero ≤ n 72 | ≤-suc : {n m : Nat} → n ≤ m → (suc n) ≤ (suc m) 73 | 74 | -- a < b라는 명제를 나타내는 타입 75 | data _<_ : Nat → Nat → Set where 76 | <-zero : {n : Nat} → zero < (suc n) 77 | <-suc : {n m : Nat} → n < m → (suc n) < (suc m) 78 | 79 | -- a ≤ b가 아니라면 b < a이다. 80 | ¬≤-to-< : {a b : Nat} → ¬ (a ≤ b) → b < a 81 | ¬≤-to-< {zero} {zero} f = explode (f ≤-zero) 82 | ¬≤-to-< {zero} {suc b} f = explode (f ≤-zero) 83 | ¬≤-to-< {suc a} {zero} f = <-zero 84 | ¬≤-to-< {suc a} {suc b} f = <-suc (¬≤-to-< (λ z → f (≤-suc z))) 85 | 86 | -- a < 1 + b라면 a < b 또는 a = b이다. 87 | <-right-suc-split : {a b : Nat} → a < (suc b) → (a < b) ⊎ (a ≡ b) 88 | <-right-suc-split {zero} {zero} _ = inj₂ refl 89 | <-right-suc-split {zero} {suc _} _ = inj₁ <-zero 90 | <-right-suc-split {suc a} {suc b} (<-suc c) = f (<-right-suc-split c) 91 | where 92 | f : (a < b) ⊎ (a ≡ b) → (suc a < suc b) ⊎ (suc a ≡ suc b) 93 | f (inj₁ x) = inj₁ (<-suc x) 94 | f (inj₂ x) = inj₂ (cong suc x) 95 | 96 | -- a는 임의의 b에 대해 a < b거나 b ≤ a이다. 97 | <-≤-split : (a b : Nat) → (a < b) ⊎ (b ≤ a) 98 | <-≤-split a zero = inj₂ ≤-zero 99 | <-≤-split zero (suc b) = inj₁ <-zero 100 | <-≤-split (suc a) (suc b) = f (<-≤-split a b) 101 | where 102 | f : (a < b) ⊎ (b ≤ a) → (suc a < suc b) ⊎ (suc b ≤ suc a) 103 | f (inj₁ x) = inj₁ (<-suc x) 104 | f (inj₂ x) = inj₂ (≤-suc x) 105 | 106 | 107 | -- 강한 수학적 귀납법 108 | strong-nat-induction : {P : Nat → Set} → ((n : Nat) → ((m : Nat) → m < n → P m) → P n) → ((n : Nat) → P n) 109 | strong-nat-induction {P} f n = con (suc n) n (n-<-suc-n n) 110 | where 111 | con : (n : Nat) → ((m : Nat) → m < n → P m) 112 | con zero m () 113 | con (suc n) m a = g (<-right-suc-split a) 114 | where 115 | g : (m < n) ⊎ (m ≡ n) → P m 116 | g (inj₁ x) = con n m x 117 | g (inj₂ refl) = f n (con n) 118 | 119 | n-<-suc-n : (n : Nat) → n < suc n 120 | n-<-suc-n zero = <-zero 121 | n-<-suc-n (suc n) = <-suc (n-<-suc-n n) 122 | 123 | 124 | -- Min P n은 "P의 최소값이 n이다"라는 명제를 나타내는 타입 125 | record Min (P : Nat → Set) (n : Nat) : Set where 126 | field 127 | min : (m : Nat) → P m → (n ≤ m) 128 | in-P : P n 129 | 130 | ------------------------------------------ Double negation version 131 | 132 | -- Well-ordering principle (Double negation version) 133 | -- 공집합이 아닌 집합 P는 최소값을 갖는다. 134 | well-ordering-principle : (P : Nat → Set) → NotEmpty P → ¬¬ (Σ Nat (Min P)) 135 | well-ordering-principle P not-empty-P = λ a → well-ordering-principle-contrapositive a not-empty-P 136 | where 137 | h : (n : Nat) → ((m : Nat) → m < n → ¬ P m) → (P n) → Min P n 138 | h n f p-n = record 139 | { min = min 140 | ; in-P = p-n 141 | } 142 | where 143 | min : (m : Nat) → P m → n ≤ m 144 | min m p-m = case (<-≤-split m n) of 145 | λ { (inj₁ m>=_ : {A B : Set} → ¬¬ A → (A → ¬¬ B) → ¬¬ B 88 | a >>= f = λ z₁ → a (λ z₂ → f z₂ z₁) 89 | 90 | pure : {A : Set} → A → ¬¬ A 91 | pure a = λ f → f a 92 | ``` 93 | 94 | `>>=` 함수를 이용하면 A를 입력받아 ¬¬ B를 만드는 함수(A → ¬¬ B)를 작성하고, ¬¬ A를 넣으면 ¬¬ B를 얻을 수 있다. 95 | 96 | ### 예시: (A면 B, B면 C)라면 (A면 C)이다. 97 | ```agda 98 | f : {A B C : Set} → (A → ¬¬ B) → (B → ¬¬ C) → (A → ¬¬ C) 99 | f a-to-b b-to-c a = 100 | a-to-b a >>= λ b → 101 | b-to-c b 102 | ``` 103 | `a-to-b a`로 `¬¬ B`를 만들고, 뒤에 `B → ¬¬ C`함수를 넣어서 `¬¬ C`를 얻는다. 104 | 105 | ### 예시: (A면 B, B면 C, C면 D, D면 E)라면 (A면 E)이다. 106 | ```agda 107 | f : {A B C D E : Set} → (A → ¬¬ B) → (B → ¬¬ C) → (C → ¬¬ D) → (D → ¬¬ E) → (A → ¬¬ E) 108 | f a-to-b b-to-c c-to-d d-to-e a = 109 | a-to-b a >>= λ b → 110 | b-to-c b >>= λ c → 111 | c-to-d c >>= λ d → 112 | d-to-e d 113 | ``` 114 | 연속해서 사용하면 위와 같이 사용할 수 있다. 115 | 116 | ## Do notation 117 | Agda는 [Do-notation](https://agda.readthedocs.io/en/v2.6.3/language/syntactic-sugar.html#do-notation)이라는 편의기능을 제공해 >>=를 활용한 표현을 간결하게 쓸 수 있도록 해 준다. 118 | 119 | ```agda 120 | do x ← m 121 | m' 122 | ``` 123 | 위와 같은 구문을 작성하면 124 | ``` 125 | m >>= λ x → 126 | m' 127 | ``` 128 | 이런 구문으로 자동으로 Agda가 해석한다. 129 | 130 | ### 예시: (A면 B, B면 C)라면 (A면 C)이다. 131 | ```agda 132 | f : {A B C : Set} → (A → ¬¬ B) → (B → ¬¬ C) → (A → ¬¬ C) 133 | f a-to-b b-to-c a = do 134 | b ← a-to-b a 135 | b-to-c b 136 | ``` 137 | 138 | ### 예시: (A면 B, B면 C, C면 D, D면 E)라면 (A면 E)이다. 139 | ```agda 140 | f : {A B C D E : Set} → (A → ¬¬ B) → (B → ¬¬ C) → (C → ¬¬ D) → (D → ¬¬ E) → (A → ¬¬ E) 141 | f a-to-b b-to-c c-to-d d-to-e a = do 142 | b ← a-to-b a 143 | c ← b-to-c b 144 | d ← c-to-d c 145 | d-to-e d 146 | ``` 147 | 구문의 생김새를 보면 `←` 표시가 ¬¬을 제거하는 역할을 하는 것 처럼 보인다.(그냥 생김새가 그래보인다는 것이다) 148 | 149 | ## Proof by case + Law of excluded middle 150 | ```agda 151 | tf-case : {A B : Set} → (A → ¬¬ B) → (¬ A → ¬¬ B) → ¬¬ B 152 | tf-case {A} {B} f g = do 153 | a-or-¬a ← law-of-excluded-middle A 154 | h a-or-¬a 155 | where 156 | h : (A ⊎ (¬ A)) → ¬¬ B 157 | h (inj₁ a) = f a 158 | h (inj₂ ¬a) = g ¬a 159 | ``` 160 | A → B이고, ¬ A → B라면, B이다. 161 | 162 | (A를 가정했을 때 B가 참임)과 (not A를 가정했을 때 B가 참임)을 보이면 B가 참임을 보일 수 있다. 163 | 164 | ## Triple negation ellimination 165 | ```agda 166 | ¬¬¬-elim : {A : Set} → ¬¬ (¬ A) → ¬ A 167 | ¬¬¬-elim a = λ z₁ → a (λ z₂ → z₂ z₁) 168 | ``` 169 | 어떤 명제의 부정을 증명하고자 할 때 유용하게 쓰일 수 있다. 170 | 171 | # 함수의 Double negation 172 | Double negation이 고전적 수학의 불완전한 흉내인 이유가 여기에 있다. 173 | 174 | 고전적 수학에서의 (a : A) → B a ((a : A)면 B a이다)라는 명제는 두 가지 타입으로 표현할 수 있다. 175 | 176 | ```agda 177 | f : ¬¬ ((a : A) → B a) 178 | g : ((a : A) → ¬¬ (B a)) 179 | ``` 180 | 두 타입은 고전적 수학의 관점에서 모두 같다. 181 | 182 | 하지만 Agda에서 ¬¬로 고전적 수학을 불완전하게 **흉내**낸 관점에서 이들이 갖는 의미는 다르다. `¬¬ ((a : A) → B a)`가 `((a : A) → ¬¬ (B a))`보다 강력하다. (즉 타입 A, B에 대해 f일때 g임은 증명할 수 있지만 g 일때 f임은 증명할 수 없다) 강력한 명제는 더 증명이 **어렵다**. 183 | 184 | 그렇다면 늘 더 쉬운 형태(g)로 증명하면 되는 것 아닌가? 문제는 데이터타입 같은 곳 안에 함수가 들어있는 경우이다. 데이터타입 같은 곳 안에 함수가 들어있는 경우, 아무 생각 없이 데이터타입에 ¬¬를 붙이고 ¬¬ ((a : A) → B a)를 증명하려고 삽질을 하기 쉽다. 185 | 186 | ```agda 187 | data _≤_ : Nat → Nat → Set where 188 | ≤-zero : {n : Nat} → zero ≤ n 189 | ≤-suc : {n m : Nat} → n ≤ m → (suc n) ≤ (suc m) 190 | 191 | record Min (P : Nat → Set) (n : Nat) : Set where 192 | field 193 | min : (m : Nat) → P m → n ≤ m 194 | in-P : P n 195 | ``` 196 | 예를 들어, "P가 n을 최솟값으로 갖는다" 라는 명제를 나타내는 Min P n 타입을 생각해보자. 197 | 198 | ```agda 199 | ¬¬ (Min P n) 200 | ``` 201 | Min P n을 고전적 수학으로 증명하려고 할 때 어떻게 해야 할까? 무심코 위와 같은 타입을 증명하고자 할 수 있다. 하지만 이 타입의 값을 만드는 과정은 `¬¬ ((m : Nat) → P m → n ≤ m)` 타입의 증명을 요구할 것이다. 그리고 이런 타입의 증명은 앞에서 말했듯 **어렵다**. 202 | 203 | 그래서 만약 Min을 고전적 수학으로 증명하고자 한다면 Min의 ¬¬ 버전 타입을 따로 만들어줄 필요가 있다. 204 | 205 | ```agda 206 | record ¬¬Min (P : Nat → Set) (n : Nat) : Set where 207 | field 208 | min : (m : Nat) → P m → ¬¬ (n ≤ m) 209 | in-P : ¬¬ (P n) 210 | ``` 211 | 이런 타입을 만들고, `¬¬Min P n`을 증명하면 된다. 고전적 수학의 관점에서는 `¬¬ (Min P n)`을 증명하는 것과 `¬¬Min P n`을 증명하는 것은 같다. 하지만 Agda에서는 둘의 난이도가 다르기 때문에 더 난이도가 낮은 `¬¬Min P n`을 증명한다. 212 | 213 | # [두 번째 방법] LEM(Law of Excluded Middle)을 가정 하에 나온 값을 증명으로 보기 214 | Double negation을 이용하면 큰 공리 없이 고전적 수학을 Agda에서 표현할 수 있다는 장점이 있지만, 위의 `Min`과 `¬¬Min`의 예시에서 알 수 있듯이 단순히 어떤 타입에 `¬¬`를 붙이는 것으로 '고전적으로 증명가능함'의 표현이 불가능해서 매번 알맞는 형태의 타입을 만들어줘야하는 귀찮음이 있다. 215 | 216 | 대신, "모든 타입 `P`에 대해, 반드시 `P` 타입의 값 또는 `¬ P` 타입의 값을 얻을 수 있다"라는 가정, 즉 Law of excluded middle의 가정 하에 명제를 증명하는 함수를 증명으로 받아들이면 이런 귀찮음 없이 고전적 수학을 흉내낼 수 있다. 또 >>= 같은 것이 필요없고 자유롭다. 217 | 218 | ```agda 219 | data Dec (P : Set) : Set where 220 | yes : ( p : P) → Dec P 221 | no : (¬p : ¬ P) → Dec P 222 | 223 | LEM : Set₁ 224 | LEM = (P : Set) → Dec P 225 | ``` 226 | LEM은 "모든 타입 `P`에 대해, 반드시 `P` 타입의 값 또는 `¬ P` 타입의 값을 얻을 수 있다"라는 명제를 나타낸다. (Dec 타입 정의 참고) 227 | 228 | |Agda|수학| 229 | |---|---| 230 | |타입 P|명제 P| 231 | |P 타입의 값|P의 증명| 232 | |LEM → P 타입의 값|P의 증명| 233 | 234 | LEM 가정 하에 명제를 증명하는 함수를 증명으로 보겠다는 것은, 위와 같은 대응을 받아들이겠다는 것이다. 235 | 236 | ## Double negation elimination 237 | ```agda 238 | LEM-¬¬-elim : LEM → {P : Set} → (¬¬ P) → P 239 | LEM-¬¬-elim lem {P} = g (lem P) 240 | where 241 | g : Dec P → (¬¬ P) → P 242 | g (yes p) _ = p 243 | g (no ¬p) p = explode (p ¬p) 244 | ``` 245 | LEM은 Double negation elimination과 동치이다. LEM 가정 하에서는, Double negation elimination이 가능하다. 따라서 LEM 가정 하에서 어떤 명제 `P`를 증명하고자 하면, 자유롭게 Double negation을 제거하며 `P`를 증명할 수 있다. 246 | 247 | ## 예시: "Nat → Bool 함수는 true를 출력하는 점을 갖거나, 늘 false를 출력한다." 248 | ```agda 249 | case_of_ : {A : Set} {B : Set} → A → (A → B) → B 250 | case x of f = f x 251 | 252 | g : LEM → (f : Nat → Bool) → ((n : Nat) → f n ≡ false) ⊎ (Σ Nat (λ n → f n ≡ true)) 253 | g lem f = case (lem (Σ Nat (λ n → f n ≡ true))) of 254 | λ { (yes p) → inj₂ p 255 | ; (no ¬p) → inj₁ λ n → 256 | case (lem (f n ≡ true)) of 257 | λ { (yes q) → explode (¬p (record { fst = n ; snd = q })) 258 | ; (no ¬q) → h ¬q 259 | } 260 | } 261 | where 262 | h : {a : Bool} → ¬ (a ≡ true) → a ≡ false 263 | h {true} b = explode (b refl) 264 | h {false} b = refl 265 | ``` 266 | 분기 처리를 편리하게 하기 위한 `case_of_`라는 함수를 만들어서 활용했다. 267 | 268 | lem을 활용해서 "true를 출력하는 점을 갖는다"와, "true를 출력하는 점이 없다" 둘 중 하나의 값을 얻는다. true를 출력하는 점을 갖는 경우, 그 점을 그대로 출력한다.(`inj₂ p`) "true를 출력하는 점이 없다" 인 경우, 모든 n에 대해서 f n ≡ false를 출력하는 함수를 만든다. 269 | 270 | ```agda 271 | (f : Nat → Bool) → ((n : Nat) → f n ≡ false) ⊎ (Σ Nat (λ n → f n ≡ true)) 272 | ``` 273 | 원래 위와 같은 타입의 함수는 못 만든다. 왜냐하면 f가 모든 점에서 false를 출력하는지, 또는 함숫값이 true인 어떤 점을 갖는지 유한한 계산을 통해 알아낼 수 없기 때문이다. 하지만 LEM 가정 하에서는 이런 함수를 만들 수 있다. 274 | 275 | # Agda 내 고전적 수학 표현의 활용 예시: 자연수의 Well-ordering principle 276 | Well-ordering principle이란 공집합이 아닌 임의의 자연수 부분집합 P에 대해, 최솟값인 자연수가 들어있다는 정리이다. 예를 들어.. 277 | - {3, 4, 7}에서는 최솟값 3이 있다. 278 | - {10, 2, 6}에서는 최솟값 2가 있다. 279 | - {7}에서는 최솟값 7이 있다. 280 | 281 | 이 정리에 따르면 수학에서는 자연수 부분집합을 입력받아 그 집합 안에 들어있는 최솟값을 출력하는 함수(함수관계)를 만들 수 있다. Agda에서는 최솟값을 출력하는 함수를 만들수는 없지만, 최솟값이 존재한다는 증명을 출력하는 함수를 만들 수 있다. 282 | 283 | 구현은 WellOrderingPrinciple.agda를 참고하라. 284 | 285 | # 참고 286 | - [Classical Mathematics for a Constructive World](https://arxiv.org/ftp/arxiv/papers/1008/1008.1213.pdf) -------------------------------------------------------------------------------- /1-기초-입문/4.타입+.md: -------------------------------------------------------------------------------- 1 | # 참고 2 | - https://agda.readthedocs.io/en/v2.6.3/language/data-types.html 3 | 4 | # Parametrized datatypes 5 | Parametrized datatype은 뭔가를 입력받아서 동적으로 타입을 만들어내는 타입이다. 여기서 입력받는 뭔가를 "parameter"라고 한다. 6 | 7 | ## 예시: Product type 8 | 9 | 수학에서 집합 A와 집합 B의 Cartesian product는, 집합 A의 원소 a와 집합 B의 원소 b로 만들 수 있는 모든 (a, b) 쌍의 집합이다. 그리고 A × B로 나타낸다. 두 자연수의 Cartesian product는 ℕ × ℕ로, 두 정수의 Cartesian product는 ℤ × ℤ로 표기한다. 그럼 여기서, Cartesian product가 집합에 대해 하는 일을 타입으로 옮겨온다면 어떻게 표현할 수 있을까? 10 | 11 | 집합의 Cartesian product ×가 하는 일은 이렇다고 볼 수 있다. 12 | - 두 개의 집합 A, B를 입력받는다. 13 | - A의 원소 a와 B의 원소 b로 만들 수 있는 모든 (a, b)쌍의 집합을 출력한다. 14 | 15 | 이걸 타입으로 옮기면, 타입의 Cartesian product ×가 하는 일은 다음과 같을 것이다. 16 | - 두 개의 타입 A, B를 입력받는다. 17 | - A의 값 a와 B의 값 b로 만들 수 있는 모든 (a, b)쌍을 만들 방법을 제공한다. 18 | 19 | 여기서 Product 타입은 두 개의 타입 A, B를 입력받아 동적으로 결과 타입을 만들어내는 타입이 된다. 20 | 21 | ```agda 22 | record _×_ (A : Set) (B : Set) : Set where 23 | field 24 | fst : A 25 | snd : B 26 | ``` 27 | 두 개의 타입 A와 B를 입력받아서, Cartesian product (A, B) 같은 결과 타입을 만들어낸다. 28 | 29 | ```agda 30 | -- (1, 0) 31 | a : Nat × Nat 32 | a = record 33 | { fst = suc zero 34 | ; snd = zero 35 | } 36 | 37 | -- (2, -3) 38 | b : Nat × Int 39 | b = record 40 | { fst = suc (suc zero) 41 | ; snd = neg-1 (suc (suc zero)) 42 | } 43 | ``` 44 | `_×_`는 양쪽 _ 부분에 입력 타입을 넣어 사용할 수 있다. 45 | - (자연수, 자연수) 타입을 만들기 위해서는 `Nat × Nat`을 사용한다. 46 | - (자연수, 정수) 타입을 만들기 위해서는 `Nat × Int`를 사용한다. 47 | - (A, B) 타입을 만들기 위해서는 `A × B`를 사용한다. 48 | 49 | ## 예시: Disjoint union type 50 | ```agda 51 | data _⊎_ (A : Set) (B : Set) : Set where 52 | inj₁ : A → A ⊎ B 53 | inj₂ : B → A ⊎ B 54 | ``` 55 | 타입 A와 B를 입력받아 두 타입 A와 B를 합친(?) 것 같은 타입을 만들어낸다. (수학에서 정확히 이 개념에 대응되는 개념이 없음) 56 | 57 | ```agda 58 | -- Nat 담기 59 | a : Nat ⊎ Int 60 | a = inj₁ (suc zero) 61 | 62 | -- Int 담기 63 | b : Nat ⊎ Int 64 | b = inj₂ (neg-1 (suc zero)) 65 | ``` 66 | - `inj₁`을 사용하면 `Nat ⊎ Int`에 Nat 타입의 값을 담을 수 있다. 67 | - `inj₂`를 사용하면 `Nat ⊎ Int`에 Int 타입의 값을 담을 수 있다. 68 | - `inj₁`을 사용하면 `A ⊎ B`에 A 타입의 값을 담을 수 있다. 69 | - `inj₂`를 사용하면 `A ⊎ B`에 B 타입의 값을 담을 수 있다. 70 | 71 | `A ⊎ B` 타입의 값은, `inj₁`로 A 타입의 값을 담고 있거나, `inj₂`로 B 타입의 값을 담고 있다. 72 | 73 | ```agda 74 | is-nat : Nat ⊎ Int → Bool 75 | is-nat (inj₁ _) = true 76 | is-nat (inj₂ _) = false 77 | ``` 78 | `Nat ⊎ Int` 타입의 값을 입력받아 Nat 타입의 값을 담고 있는지 판별하는 함수를 작성할 수도 있다. 79 | 80 | ## 예시: List 81 | ```agda 82 | data List (A : Set) : Set where 83 | [] : List A 84 | _∷_ : A → List A → List A 85 | ``` 86 | 타입 A의 값을 갖는 유한수열(또는 배열)을 위처럼 표현할 수 있다. 87 | - []는 빈 배열을 표현한다. 88 | - \_∷\_는 배열의 맨 앞 요소와 나머지 배열을 포함해 배열에 아이템이 추가되는 것을 표현한다. 89 | ```agda 90 | -- [ 1, 0, 2 ] 91 | a : List Nat 92 | a = (suc zero) ∷ (zero ∷ ((suc (suc zero)) ∷ [])) 93 | 94 | -- [ 0, 1, 0, 1 ] 95 | b : List Nat 96 | b = (zero) ∷ ((suc zero) ∷ (zero ∷ ((suc zero) ∷ []))) 97 | ``` 98 | ![List들](4-2.png) 99 | 100 | ## 예시: Full binary tree 101 | ```agda 102 | data BinTree (T : Set) : Set where 103 | leaf : T → BinTree T 104 | non-leaf : T → BinTree T → BinTree T → BinTree T 105 | ``` 106 | 타입 T의 값을 안에 저장하는 Full binary tree를 위와같이 표현할 수 있다. 107 | - leaf는 T 타입의 값을 포함한다. 108 | - non-leaf는 T 타입의 값, 왼쪽의 BinTree 값, 오른쪽의 BinTree 값을 포함한다. 109 | 110 | ``` 111 | a : BinTree Nat 112 | a = non-leaf (suc (suc (suc (suc zero)))) 113 | ( 114 | non-leaf (zero) 115 | ( 116 | leaf (suc (suc zero)) 117 | ) 118 | ( 119 | leaf (suc (suc (suc zero))) 120 | ) 121 | ) 122 | ( 123 | leaf (suc zero) 124 | ) 125 | ``` 126 | ![BinTree 예시](4-1.png) 127 | 128 | # 타입을 입력받는 함수 129 | 타입을 입력받아 타입을 만들어내는 타입에 대해 다뤘다. 이번에는 타입을 입력받는 함수에 대해 알아보자. 130 | 131 | ## 예시: Compose function 132 | 수학에서 두 함수 f : B -> C와 g : A -> B의 합성은 f ∘ g로 표현된다. 그럼 여기서 ∘를 함수로 생각하면, 이 함수가 하는 일은 두 함수를 입력받아 두 함수를 합성한 결과 함수를 출력하는 것이라고 볼 수 있다. 133 | 134 | Agda에서도 ∘와 유사한 함수를 만들 수 있을 것이다. 하지만 Agda에서는 다짜고짜 "B -> C"와 "A -> B"타입의 값을 입력받을 수는 없다. A, B, C가 뭔지 모르기 때문이다. 따라서 Agda에서는 A, B, C 타입을 미리 입력받고, 그 후 A -> B와 B -> C 타입의 값을 입력받는 식으로 합성함수를 만들어내는 함수를 구현한다. 135 | 136 | ```agda 137 | compose : (A B C : Set) → (B → C) → (A → B) → (A → C) 138 | compose _ _ _ f g = λ x → f (g x) 139 | ``` 140 | 타입 A, B, C를 먼저 입력받고, 두 함수를 입력받아 두 함수의 합성함수를 출력한다. (함수의 본문에서는 타입 A, B, C를 사용하지 않기 때문에 _로 무시한다) 141 | ```agda 142 | -- 절댓값 함수 143 | g : Int → Nat 144 | g (pos n) = n 145 | g (neg-1 n) = suc n 146 | 147 | -- 2의 배수 여부를 출력하는 함수 148 | f : Nat → Bool 149 | f zero = true 150 | f (suc zero) = false 151 | f (suc (suc n)) = f n 152 | 153 | -- 둘의 합성 154 | h : Int → Bool 155 | h = compose Int Nat Bool f g 156 | ``` 157 | 타입과 함수를 넣어서 합성함수를 만들 수 있다. 158 | 159 | ```agda 160 | compose : {A B C : Set} → (B → C) → (A → B) → (A → C) 161 | compose f g = λ x → f (g x) 162 | 163 | h : Int → Bool 164 | h = compose f g 165 | ``` 166 | Agda에서는 { ... } (Implicit argument) 표현으로 Agda가 다른 인자로부터 값을 추론하도록 지정할 수 있다. 이렇게 하면 입력받은 두 함수로부터 A, B, C를 Agda가 추론해 자동으로 넣어주기 때문에 compose 함수를 사용할 때 합성하고자 하는 두 함수만 넣으면 된다. (추론이 실패하는 경우, 직접 넣어줘야 할 수도 있다) 167 | ```agda 168 | _∘_ : {A B C : Set} → (B → C) → (A → B) → (A → C) 169 | f ∘ g = λ x → f (g x) 170 | 171 | h : Int → Bool 172 | h = f ∘ g 173 | ``` 174 | 위처럼 정의하면 수학에서의 함수 합성 기호처럼 사용할 수 있다. 175 | 176 | ## 예시: Cartesian product mapping function 177 | ```agda 178 | map : {A B C D : Set} → (A → C) → (B → D) → A × B → C × D 179 | map f g z = record 180 | { fst = f (_×_.fst z) 181 | ; snd = g (_×_.snd z) 182 | } 183 | ``` 184 | A -> C, B -> D, (A, B) 타입의 값을 입력받아 각각을 매핑한 (C, D) 타입의 값을 출력해주는 함수다. 185 | ```agda 186 | f : Nat → Bool 187 | f zero = false 188 | f (suc zero) = true 189 | f (suc (suc n)) = f n 190 | 191 | g : Int → Nat 192 | g (pos n) = n 193 | g (neg-1 n) = suc n 194 | 195 | z : Nat × Int 196 | z = record 197 | { fst = zero 198 | ; snd = neg-1 (suc zero) 199 | } 200 | 201 | w : Bool × Nat 202 | w = map f g z 203 | ``` 204 | `Nat × Int`의 Nat 부분은 f로, Int 부분은 g로 매핑해 `Bool × Nat`를 얻을 수 있다. 205 | 206 | ## 예시: Disjoint union mapping function 207 | ```agda 208 | map : {A B C D : Set} → (A → C) → (B → D) → A ⊎ B → C ⊎ D 209 | map f g (inj₁ a) = inj₁ (f a) 210 | map f g (inj₂ b) = inj₂ (g b) 211 | ``` 212 | Disjoint union에도 유사한 매핑 함수를 만들 수 있다. 213 | 214 | # Dependent type 215 | 지금까지는 타입이 타입에게, 값이 값에게 영향을 미치는 경우들만을 봤다. (타입이 값을 제한하기 때문에 타입이 값에게 영향을 미칠 수 있다고 볼 수도 있겠다.) 그렇담 만약 값이 타입에게 영향을 미칠 수 있다면? 그게 바로 Dependent type의 개념이다. 216 | 217 | Dependent type은 값에 의해서 영향을 받는(값에 의해 결정되는) 타입이다. Dependent type을 활용하면 "false를 입력받았을 때에는 Nat 값을 출력하고, true를 입력받았을 때에는 Int 값을 출력하는 함수" 따위를 만들 수 있다. 또 Nat 타입의 값 n을 입력받아 길이가 n인 배열들의 타입을 출력하는 타입을 만들 수도 있다. 218 | 219 | Dependent type 개념은 Agda의 모든 곳에서 언급 없이 당연하게 쓰이는데, 여기서는 dependent type 개념이 많이 사용되는 Indexed datatype과 Dependent function에 대해 알아본다. 이 둘을 보고 나면 Dependent type을 다른 곳에서도 쉽게 사용할 수 있을 것이다. 220 | 221 | # Indexed datatypes 222 | Indexed datatype은 뭔가를 입력받아서 서로 다른 타입을 만들어내는 타입이다. 여기서 입력받는 뭔가를 "index"라고 한다. Parameter는 입력에 따라 서로 전혀 연관이 없는 서로 다른 타입들을 만들어내는데에 사용되는것과 달리, index는 서로 연관이 있는 타입들을 구분하고, 값에 대한 정보를 타입에 담는 용도로 사용된다. 223 | 224 | ## 예시: Vector (길이가 있는 List) 225 | ```agda 226 | data Vector (A : Set) : Nat → Set where 227 | [] : Vector A zero 228 | _∷_ : {n : Nat} → A → Vector A n → Vector A (suc n) 229 | ``` 230 | - `Nat → Set`은 Vector 타입이 Nat 타입의 값 한 개를 index로 입력받음을 의미한다. 231 | - `[] : Vector A zero`는 []로 Vector A zero 타입의 값을 만들 수 있음을 의미한다. 232 | - `_∷_ : {n : Nat} → A → Vector A n → Vector A (suc n)`는 \_∷\_와 A 타입의 값, Vector A n 타입의 값으로 Vector A (suc n) 타입의 값을 만들 수 있음을 의미한다. 233 | 234 | constructor에서 index의 값을 자유롭게 정하는 것을 볼 수 있다. 235 | - []는 결과 타입의 index 값이 zero 236 | - \_∷\_는 결과 타입의 index 값이 suc n 237 | 238 | index 값이 위 규칙에 의해 정해지기 때문에, Vector의 index 값은 배열의 길이를 나타내게 된다. 239 | 240 | ```agda 241 | -- [] 242 | a : Vector Nat zero 243 | a = [] 244 | 245 | -- [0] 246 | b : Vector Nat (suc zero) 247 | b = (zero) ∷ [] 248 | 249 | -- [3, 1, 2] 250 | c : Vector Nat (suc (suc (suc zero))) 251 | c = (suc (suc (suc zero))) ∷ ((suc zero) ∷ ((suc (suc zero)) ∷ [])) 252 | ``` 253 | 배열 값들을 만들어보면, index는 실제로 배열의 길이값임을 확인할 수 있다. 254 | 255 | ## 예시: Even 256 | ```agda 257 | data Even : Nat → Set where 258 | even-zero : Even zero 259 | even-plus2 : {n : Nat} → Even n → Even (suc (suc n)) 260 | ``` 261 | - `even-zero`와 `even-plus2` constructor들로부터 만들어진 값의 타입의 index는 늘 짝수이다. 262 | - 홀수 인덱스를 갖는 Even 타입의 값을 만들 수는 없다. 263 | ```agda 264 | -- Even 2 265 | a : Even (suc (suc zero)) 266 | a = even-plus2 even-zero 267 | 268 | -- Even 6 269 | b : Even (suc (suc (suc (suc (suc (suc zero)))))) 270 | b = even-plus2 (even-plus2 (even-plus2 even-zero)) 271 | 272 | -- Even 1은 못 만들어! 273 | c : Even (suc zero) 274 | c = ? 275 | ``` 276 | 그래서 `Even n` 타입의 값의 존재는, n이 짝수라는 것을 뜻한다. 277 | 278 | ## 예시: NonZero 279 | ```agda 280 | data NonZero : Nat → Set where 281 | nonzero-one : NonZero (suc zero) 282 | nonzero-plus1 : {n : Nat} → NonZero n → NonZero (suc n) 283 | ``` 284 | - `nonzero-one`과 `nonzero-plus1` constructor들로부터 만들어진 값의 타입의 index는 늘 1보다 크거나 같다. 285 | - 인덱스가 0인 NonZero 타입의 값을 만들 수는 없다. 286 | ```agda 287 | -- NonZero 2 288 | a : NonZero (suc (suc zero)) 289 | a = nonzero-plus1 nonzero-one 290 | 291 | -- NonZero 3 292 | b : NonZero (suc (suc (suc zero))) 293 | b = nonzero-plus1 (nonzero-plus1 nonzero-one) 294 | 295 | -- NonZero 0은 못 만들어! 296 | c : NonZero zero 297 | c = ? 298 | ``` 299 | 그래서 `NonZero n` 타입의 값의 존재는, n이 0이 아니라는 것을 뜻한다. 300 | 301 | # Dependent function 302 | Dependent function은 함수의 입력 값에 함수의 출력 타입이 영향을 받는 함수다. (다른 입력 타입에 영향을 미치는 경우도 포함됨) 303 | 304 | ## 예시: Nat Vector 덧셈 함수 305 | ```agda 306 | add-nat-vector : {l : Nat} → Vector Nat l → Vector Nat l → Vector Nat l 307 | add-nat-vector [] [] = [] 308 | add-nat-vector (a ∷ as) (b ∷ bs) = (add a b) ∷ (add-nat-vector as bs) 309 | ``` 310 | 자연수 유한수열 두 개를 입력받아 원소를 각각 덧셈한 결과 유한수열을 출력하는 함수. 311 | - 첫 번째 implicit 입력으로 받은 자연수 l 값에 따라 함수의 출력 타입이 변한다. 312 | 313 | ## 예시: 입력이 true면 Nat 타입의 값을, false면 Int 타입의 값을 반환하는 함수 314 | ```agda 315 | f : Bool → Set 316 | f true = Nat 317 | f false = Int 318 | 319 | g : (x : Bool) → f x 320 | g true = suc zero 321 | g false = neg-1 zero 322 | ``` 323 | - f는 true일 때 Nat을, false일때 Int를 출력하는 함수다. 324 | - 이 함수를 이용해서 g의 반환 타입이 입력이 true일때는 Nat이 되고, false일때에는 Int가 되도록 정의한다. 325 | - pattern matching된 true, false 분기에 각각 Nat 타입과 Int 타입의 값이 있다. 326 | 327 | ``` 328 | a : Nat 329 | a = g true 330 | 331 | b : Int 332 | b = g false 333 | ``` 334 | true랑 false를 넣어 보면 실제로 서로 다른 타입의 값이 나온다. 335 | 336 | ## 예시: 특정 원소로 차있는 길이 l짜리 Vector를 만들어주는 함수 337 | ```agda 338 | fill : {A : Set} → A → (l : Nat) → Vector A l 339 | fill a zero = [] 340 | fill a (suc l) = a ∷ fill a l 341 | ``` 342 | 이 함수는 두 번째 입력으로 받은 자연수 l에 따라 출력 타입이 변한다. 343 | 344 | ```agda 345 | -- [1, 1, 1] 346 | a : Vector Nat (suc (suc (suc zero))) 347 | a = fill (suc zero) (suc (suc (suc zero))) 348 | 349 | -- [2] 350 | b : Vector Nat (suc zero) 351 | b = fill (suc (suc zero)) (suc zero) 352 | 353 | -- [-1, -1] 354 | c : Vector Int (suc (suc zero)) 355 | c = fill (neg-1 zero) (suc (suc zero)) 356 | ``` -------------------------------------------------------------------------------- /1-기초-입문/5.명제와 집합.md: -------------------------------------------------------------------------------- 1 | # 타입이 명제 2 | ``` 3 | data Even : Nat → Set where 4 | even-zero : Even zero 5 | even-plus2 : {n : Nat} → Even n → Even (suc (suc n)) 6 | ``` 7 | 앞서 본 Even 타입에 대해서 이런 말을 했었다. "Even n 타입의 값의 존재는, n이 짝수라는 것을 의미한다". 8 | ```agda 9 | data NonZero : Nat → Set where 10 | nonzero-one : NonZero (suc zero) 11 | nonzero-plus1 : {n : Nat} → NonZero n → NonZero (suc n) 12 | ``` 13 | 또 NonZero 타입에 대해서는 이런 말을 했었다. "NonZero n 타입의 값의 존재는, n이 0이 아님을 의미한다". 14 | 15 | 공통점이 보인다. 어떤 타입의 값의 존재는, 어떤 명제가 참임을 의미한다. 16 | 17 | |타입|대응되는 명제| 18 | |---|---| 19 | |Even n|n이 짝수다| 20 | |NonZero n|n이 0이 아니다| 21 | 22 | 위 두 예제에서는 위와 같이 명제가 대응된다. 그리고 각 타입의 값의 존재는 각 명제가 참임을 의미한다. 23 | 24 | ## 타입 = 명제, 값 = 증명 25 | |Agda|수학| 26 | |---|---| 27 | |타입|명제| 28 | |값|증명| 29 | 30 | 타입과 값이 각각 대응되는 수학적 대상을 표로 나타내면 위와 같다. Agda의 타입은 수학에서의 명제에 대응되고, Agda의 값은 수학에서의 증명에 대응된다. 값이 증명이기 때문에, 값이 존재하면 명제가 참인 것이다. 31 | 32 | ## 값이 증명인 이유 33 | 값 자체가 어떻게 증명이 될 수 있는지에 대해서 의문이 들 수 있다. 값이 만들어지는 과정을 보면 실제로 값이 증명임을 알 수 있다. 34 | ```agda 35 | 6-is-even : Even (suc (suc (suc (suc (suc (suc zero)))))) 36 | 6-is-even = even-plus2 (even-plus2 (even-plus2 even-zero)) 37 | ``` 38 | 위 코드는 "6이 짝수다"라는 명제를 나타내는 타입의 값이다. 이 값이 만들어지는 과정을 보자. 39 | - constructor들: even-zero, even-plus2 40 | - even-zero로 "0이 짝수다"라는 명제를 나타내는 타입의 값을 얻는다. 41 | - even-plus2로 "0이 짝수다"라는 명제를 나타내는 타입의 값으로부터 "2가 짝수다" 라는 명제를 나타내는 타입의 값을 얻는다. 42 | - even-plus2로 "2가 짝수다"라는 명제를 나타내는 타입의 값으로부터 "4가 짝수다" 라는 명제를 나타내는 타입의 값을 얻는다. 43 | - even-plus2로 "4가 짝수다"라는 명제를 나타내는 타입의 값으로부터 "6이 짝수다" 라는 명제를 나타내는 타입의 값을 얻는다. 44 | 45 | 이건 다음과 굉장히 비슷하다. 46 | 47 | - 공리들: 0은 짝수다. (n이 짝수면 2 + n도 짝수다) 48 | - 0은 짝수다. 49 | - (n이 짝수면 2 + n도 짝수다) 이고 0은 짝수이므로 2는 짝수다. 50 | - (n이 짝수면 2 + n도 짝수다) 이고 2는 짝수이므로 4는 짝수다. 51 | - (n이 짝수면 2 + n도 짝수다) 이고 4는 짝수이므로 6은 짝수다. 52 | 53 | 이는 "0은 짝수다.", "n이 짝수면 2 + n도 짝수다" 라는 가정이 있을 때 "6은 짝수다"의 증명이다. 54 | 55 | 값을 만들어내는 과정, 즉 값의 구조가 증명과정을 나타내는 것이다. 따라서 값 자체가 증명을 의미한다. 56 | 57 | # A x → B x 타입의 명제: "A x이면 B x이다" 58 | 타입이 명제를 의미하고 값이 증명을 의미한다면, A x → B x는 명제 A x의 증명을 입력받아 명제 B x의 증명을 출력하는 함수의 타입으로 생각할 수 있다. 이 타입은 "A x면 B x이다"라는 명제에 해당하며, 이 타입의 값(함수)는 "A x면 B x이다"에 대한 증명이다. 59 | 60 | ## 예시: 모든 자연수 n에 대해, n이 짝수이면 4 + n도 짝수다 61 | ```agda 62 | f : {n : Nat} → Even n → Even (suc (suc (suc (suc n)))) 63 | f e = even-plus2 (even-plus2 e) 64 | ``` 65 | 임의의 자연수 n에 대해, Even n 타입의 값이 들어오는 경우, Even (suc (suc (suc (suc n)))) 타입의 값을 출력한다. 66 | 67 | ```agda 68 | -- 2는 짝수다. f에 따르면, 6도 짝수다. 69 | a : Even (suc (suc (suc (suc (suc (suc zero)))))) 70 | a = f (even-plus2 even-zero) 71 | 72 | -- 4는 짝수다. f에 따르면, 8도 짝수다. 73 | b : Even (suc (suc (suc (suc (suc (suc (suc (suc zero)))))))) 74 | b = f (even-plus2 (even-plus2 even-zero)) 75 | ``` 76 | 수학에서 정리를 사용하는 것 처럼, 이 함수를 이용해 다른 명제를 증명할 수도 있다. 77 | 78 | ## 예시: 모든 자연수 n과 m에 대해, n과 m이 짝수이면 n + m도 짝수다 79 | ```agda 80 | f : {n m : Nat} → Even n → Even m → Even (add n m) 81 | f even-zero even-m = even-m 82 | f (even-plus2 even-k) even-m = even-plus2 (f even-k even-m) 83 | ``` 84 | 수학적 귀납법 같은 재귀적 증명이다. 85 | ```agda 86 | [가정] 87 | n이 0이다. 88 | m이 짝수다. 89 | [결론] 90 | n + m = 0 + m = m이므로 n + m도 짝수다. 91 | ================================ 92 | [가정] 93 | n = 2 + k이고 k와 n은 짝수다. 94 | m이 짝수다. 95 | [결론] 96 | k가 짝수이고 m이 짝수이므로 k + m도 짝수다. (재귀호출, k가 n보다 작기 때문에 가능) 97 | 2 + (k + m)은 짝수다. (짝수에 대한 공리에 의해) 98 | 그런데 2 + (k + m) = (2 + k) + m = n + m 이므로 (정의에 의해) 99 | n + m은 짝수다. 100 | ================================ 101 | n, m이 짝수일 때 가능한 모든 경우에 대해 증명했으므로, 102 | 모든 짝수 n, m에 대해 n + m은 짝수다. 103 | ``` 104 | 수학 증명으로 보면 위와 같다. 105 | 106 | # OR 107 | ```agda 108 | data _⊎_ (A : Set) (B : Set) : Set where 109 | inj₁ : A → A ⊎ B 110 | inj₂ : B → A ⊎ B 111 | ``` 112 | Disjoint union 타입은 명제의 "또는" 을 나타내는 데에 사용할 수 있다. A와 B 중 원하는 타입의 값을 넣어서 A ⊎ B 타입의 값을 만들어낼 수 있다는 점을 이용하는 것이다. 113 | 114 | ## 예시: 모든 자연수 n에 대해, "n은 짝수이다" 또는 "n은 홀수이다" 이다. 115 | ```agda 116 | data Even : Nat → Set where 117 | even-zero : Even zero 118 | even-plus2 : {n : Nat} → Even n → Even (suc (suc n)) 119 | 120 | data Odd : Nat → Set where 121 | odd-one : Odd (suc zero) 122 | odd-plus2 : {n : Nat} → Odd n → Odd (suc (suc n)) 123 | ``` 124 | 먼저 Odd 타입을 추가로 정의해주자. 125 | ```agda 126 | f : (n : Nat) → (Even n) ⊎ (Odd n) 127 | f zero = inj₁ even-zero 128 | f (suc zero) = inj₂ odd-one 129 | f (suc (suc k)) = g (f k) 130 | where 131 | g : (Even k) ⊎ (Odd k) → (Even (suc (suc k))) ⊎ (Odd (suc (suc k))) 132 | g (inj₁ x) = inj₁ (even-plus2 x) 133 | g (inj₂ x) = inj₂ (odd-plus2 x) 134 | ``` 135 | n = 0, n = 1의 경우에 대해서 정의한 후, 2 이상의 수에 대해서는 재귀적으로 정의한다. `where`라는 새로운 구문이 등장했는데, 이 구문은 지역적으로 상수나 함수를 정의해놓고 그걸 사용할 수 있게 해 준다. 위 경우 "k가 짝수이거나 홀수이면, 2 + k도 짝수이거나 홀수이다" 를 의미하는 g 함수를 지역적으로 정의해놓고, f의 정의에 활용한다. 136 | 137 | ```agda 138 | [가정] 139 | n = 0 140 | [결론] 141 | 정의에 의해 n은 짝수이다. 따라서 "n은 짝수이다" 또는 "n은 홀수이다" 이다. 142 | ================================ 143 | [가정] 144 | n = 1 145 | [결론] 146 | 정의에 의해 n은 홀수이다. 따라서 "n은 짝수이다" 또는 "n은 홀수이다" 이다. 147 | ================================ 148 | [가정] 149 | n = 2 + k 150 | [결론] 151 | "k는 짝수이다" 또는 "k는 홀수이다" 이다. (재귀호출, k가 n보다 작기 때문에 가능) 152 | g를 사용하면, "2 + k는 짝수이다" 또는 "2 + k"는 홀수이다" 이다. 153 | 2 + k = n이므로 "n은 짝수이다" 또는 "n은 홀수이다" 이다. 154 | [g] 155 | [가정] 156 | "k는 짝수이다" 또는 "k는 홀수이다" 이다. 157 | k는 짝수이다. 158 | [결론] 159 | 2 + k는 짝수이다. (정의에 따라) 160 | 따라서 "2 + k는 짝수이다" 또는 "2 + k는 홀수이다" 이다. 161 | ================================ 162 | [가정] 163 | "k는 짝수이다" 또는 "k는 홀수이다" 이다. 164 | k는 홀수이다. 165 | [결론] 166 | 2 + k는 홀수이다. (정의에 따라) 167 | 따라서 "2 + k는 짝수이다" 또는 "2 + k는 홀수이다" 이다. 168 | ================================ 169 | "k는 짝수이다" 또는 "k는 홀수이다" 일 때의 모든 경우에 대해 증명했으므로, 170 | "k는 짝수이다" 또는 "k는 홀수이다" 일 때 "2 + k는 짝수이다" 또는 "2 + k는 홀수이다" 이다. 171 | ================================ 172 | n이 자연수일 때 모든 경우에 대해 증명했으므로, 173 | n이 자연수일 때 "n은 짝수이다" 또는 "n은 홀수이다" 이다. 174 | ``` 175 | 수학적 증명은 위와 같다. 176 | 177 | # AND 178 | ```agda 179 | record _×_ (A : Set) (B : Set) : Set where 180 | field 181 | fst : A 182 | snd : B 183 | ``` 184 | Product 타입은 명제의 "그리고" 를 나타내는 데에 사용할 수 있다. A와 B 타입의 값 모두가 있어야 A × B 타입의 값을 만들어낼 수 있다는 점을 이용하는 것이다. 185 | 186 | ## 예시: 모든 자연수 n에 대해, n이 8의 배수라면, "n은 4의 배수이다" 그리고 "n은 2의 배수이다" 이다. 187 | ```agda 188 | data Multiple-of-8 : Nat → Set where 189 | eight : Multiple-of-8 (suc (suc (suc (suc (suc (suc (suc (suc zero)))))))) 190 | plus-eight : {n : Nat} → Multiple-of-8 n → Multiple-of-8 (suc (suc (suc (suc (suc (suc (suc (suc n)))))))) 191 | 192 | data Multiple-of-4 : Nat → Set where 193 | four : Multiple-of-4 (suc (suc (suc (suc zero)))) 194 | plus-four : {n : Nat} → Multiple-of-4 n → Multiple-of-4 (suc (suc (suc (suc n)))) 195 | 196 | data Multiple-of-2 : Nat → Set where 197 | eight : Multiple-of-2 (suc (suc zero)) 198 | plus-eight : {n : Nat} → Multiple-of-2 n → Multiple-of-2 (suc (suc (suc n))) 199 | ``` 200 | 먼저 "n은 8의 배수다", "n은 4의 배수다", "n은 2의 배수다" 명제를 나타내는 타입을 정의한다. 201 | ```agda 202 | f : {n : Nat} → Multiple-of-8 n → (Multiple-of-4 n) × (Multiple-of-2 n) 203 | f eight = record 204 | { fst = plus-four four 205 | ; snd = plus-two (plus-two (plus-two two)) 206 | } 207 | f { suc (suc (suc (suc (suc (suc (suc (suc k))))))) } (plus-eight k-is-multiple-of-8) = record 208 | { fst = plus-four (plus-four prev-four) 209 | ; snd = plus-two (plus-two (plus-two (plus-two prev-two))) 210 | } 211 | where 212 | prev : (Multiple-of-4 k) × (Multiple-of-2 k) 213 | prev = f k-is-multiple-of-8 214 | 215 | prev-four : Multiple-of-4 k 216 | prev-four = _×_.fst prev 217 | 218 | prev-two : Multiple-of-2 k 219 | prev-two = _×_.snd prev 220 | ``` 221 | eight의 경우를 증명하고, 16보다 큰 경우에 대해서는 재귀적으로 증명한다. (위 코드에서는 함수의 implicit 입력에 대해서 패턴매칭을 하고 있다.) 222 | ```agda 223 | [가정] 224 | n = 8이다. 225 | n은 8의 배수이다. 226 | [결론] 227 | 4는 4의 배수이다. 4 + 4 = 8은 4의 배수이다. 228 | 2는 2의 배수이다. 2 + 2 = 4는 2의 배수이다. 229 | 2 + 4 = 6은 2의 배수이다. 230 | 2 + 6 = 8은 2의 배수이다. 231 | 8 = n이다. 232 | 따라서 "n은 4의 배수이다" 그리고 "n은 2의 배수이다" 이다. 233 | ================================ 234 | [가정] 235 | n = 8 + k이다. 236 | n은 8의 배수이다. 237 | k는 8의 배수이다. 238 | [결론] 239 | prev-four 에 따라 k는 4의 배수이다. 240 | 4 + k는 4의 배수이다. 241 | 4 + (4 + k) = 8 + k는 4의 배수이다. 242 | n = 8 + k 이므로 n은 4의 배수이다. 243 | prev-two 에 따라 k는 2의 배수이다. 244 | 2 + k는 2의 배수이다. 245 | 2 + (2 + k) = 4 + k는 2의 배수이다. 246 | 2 + (4 + k) = 6 + k는 2의 배수이다. 247 | 2 + (6 + k) = 8 + k는 2의 배수이다. 248 | n = 8 + k 이므로 n은 2의 배수이다. 249 | 따라서 "n은 4의 배수이다" 그리고 "n은 2의 배수이다" 이다. 250 | [prev] 251 | "k는 4의 배수이다" 그리고 "k은 2의 배수이다" 이다. (재귀호출, k가 n보다 작기에 가능) 252 | [prev-four] 253 | (prev 로부터) 254 | k는 4의 배수이다. 255 | [prev-two] 256 | (prev 로부터) 257 | k는 2의 배수이다. 258 | ================================ 259 | n이 8의 배수일 때 가능한 모든 경우에 대해 증명했으므로 260 | n이 8이 배수일 때, "n은 4의 배수이다" 그리고 "n은 2의 배수이다" 이다. 261 | ``` 262 | 수학적 증명은 위와 같다. 263 | 264 | # NOT과 0-type 265 | ```agda 266 | -- 이 타입은 constructor가 없다. 267 | data ⊥ : Set where 268 | ``` 269 | 이 타입은 0-type이라고 불리는 타입이다. 이 타입은 constructor가 없고, 따라서 이 타입의 값은 만들어낼 수 없다. 값을 만들어낼 수 없는 이 타입은 쓸모없어보이지만, 명제의 부정을 나타내는 데에 유용하게 쓰일 수 있다. 예를 들어 어떤 타입 P에 대해 다음과 같은 타입을 생각해보자. 270 | ```agda 271 | P → ⊥ 272 | ``` 273 | 이 타입의 값(즉 P 타입을 입력받아 ⊥ 타입의 값을 출력하는 함수)이 존재한다는 것은 무엇을 뜻할까? 그건 P도 ⊥와 같이 값을 만들어낼 수 없는 타입이라는 것이다. 만약 P 타입의 값이 존재한다면, 함수는 이 P 타입의 값에 대응되는 ⊥ 타입의 출력값을 내뱉어야 하는데, ⊥ 타입의 값은 만들어낼 방법이 없으므로 P → ⊥ 타입의 함수는 존재할 수 없기 때문이다. 274 | 275 | 그런데 한편, 값이 증명에 대응됨을 생각해보면, 값이 존재하지 않는다는 것은 증명이 존재하지 않는다는 것을 의미한다. 증명이 존재하지 않는다는 것을 명제가 거짓임으로 보면 "타입 P의 값을 만들어낼 수 없다"를 "P는 거짓이다"로 대응시킬수 있다. 즉 P → ⊥ 타입의 값의 존재는 P가 거짓임을 의미한다. 276 | 277 | ## 예시: 1은 짝수가 아니다. 278 | ```agda 279 | f : (Even (suc zero)) → ⊥ 280 | f () 281 | ``` 282 | 함수 정의가 없다. 가능한 입력이 없기 때문이다. 283 | 284 | 어떤 원리로 이런 정의가 될 수 있을까? (Even (suc zero)) 타입의 pattern matching을 생각해보자. 이 타입의 값을 만드는 방법은.. 없다! Agda도 (Even (suc zero))라는 타입으로부터 이 사실을 알아낼 수 있으며, pattern matching에서 가능한 분기가 0개이므로 함수 본문이 한 개도 없이 함수를 정의할 수 있게 해 준다. 285 | 286 | ## 예시: 모든 자연수 n에 대해, n이 짝수라면 n은 홀수가 아니다. 287 | ```agda 288 | f : {n : Nat} → Even n → Odd n → ⊥ 289 | f even-zero () 290 | f (even-plus2 a) (odd-plus2 b) = f a b 291 | ``` 292 | - n이 zero인 경우에는 Odd zero를 만들 방법이 없으므로 함수 본문이 없다. 293 | - n이 zero가 아닌 경우에는 n = 2 + k이다. f k로 재귀호출한다. 294 | 295 | ## 예시: P가 참이라면 Not P는 참이 아니다. 296 | ```agda 297 | f : {P : Set} → P → (P → ⊥) → ⊥ 298 | f p z = z p 299 | ``` 300 | 301 | ## ¬ 기호로 예쁘게 쓰기 302 | ```agda 303 | ¬_ : Set → Set 304 | ¬ P = P → ⊥ 305 | ``` 306 | 위와 같은 타입을 입력받아 타입을 출력하는 함수를 정의해두면, ¬ 기호로 부정을 예쁘게 쓸 수 있다. 307 | 308 | ```agda 309 | f : ¬ Even (suc zero) 310 | f () 311 | ``` 312 | ```agda 313 | f : {n : Nat} → Even n → ¬ Odd n 314 | f even-zero () 315 | f (even-plus2 a) (odd-plus2 b) = f a b 316 | ``` 317 | ```agda 318 | f : {P : Set} → P → ¬ (¬ P) 319 | f p z = z p 320 | ``` 321 | 322 | ## Principle of explosion 323 | ```agda 324 | explode : {A : Set} → ⊥ → A 325 | explode () 326 | ``` 327 | 앞선 증명들에서, 가능한 입력이 없는 경우 함수 정의를 생략할 수 있음을 보았다. 이를 활용하면 위와 같이 ⊥ 타입의 값을 입력받아서 임의 타입의 값을 출력하는 함수를 작성할 수 있다. ⊥ 타입의 값은 만들어낼 수 없으므로, 함수 정의를 생략할 수 있다. 328 | 329 | 이 함수의 존재는 '모순(⊥ 타입의 값을 만들어낼 수 있는 이상한 상황)이 발생하면 모든 명제가 참이 된다'를 뜻한다. 이는 논리학에서의 Principle of explosion에 해당한다. 330 | 331 | # Σ type 332 | ```agda 333 | record Σ (A : Set) (B : A → Set) : Set where 334 | field 335 | fst : A 336 | snd : B fst 337 | ``` 338 | Σ 타입은 값과 그 값에 대한 증명을 같이 담아두는 용도로 사용한다. B는 A 타입의 값을 입력받아 타입을 출력하는 함수다. snd의 타입은 B fst로, fst의 값에 따라 바뀐다. 339 | 340 | ## 예시: Even인 자연수 2 341 | ```agda 342 | a : Σ Nat (λ n → Even n) 343 | a = record 344 | { fst = suc (suc zero) 345 | ; snd = even-plus2 even-zero 346 | } 347 | ``` 348 | fst에 들어가는 값이 2이므로, snd의 타입은 (λ n → Even n) 2의 값인 Even 2가 된다. 따라서 Even 2 값을 만들어서 snd에 넣는다. 349 | 350 | a의 타입 `Σ Nat (λ n → Even n)`은 "짝수인 자연수"를 나타내는 타입으로 해석될 수 있다. 이 타입의 값에는, fst에는 자연수가 들어가 있고 snd에는 이 자연수가 짝수임에 대한 증명이 들어가 있다. 351 | 352 | ## 예시: Even인 자연수 0 353 | ```agda 354 | a : Σ Nat (λ n → Even n) 355 | a = record 356 | { fst = zero 357 | ; snd = even-zero 358 | } 359 | ``` 360 | fst에 저장되는 값이 2이므로, snd의 타입은 (λ n → Even n) 0의 값인 Even 0가 된다. 361 | 362 | a의 타입 `Σ Nat (λ n → Even n)`은 "짝수인 자연수"를 나타내는 타입으로 해석될 수 있다. 이 타입의 값에는, fst에는 자연수가 들어가 있고 snd에는 이 자연수가 짝수임에 대한 증명이 들어가 있다. 363 | 364 | ## 예시: 합이 짝수인 두 자연수 365 | ```agda 366 | a : Σ Nat (λ n → Σ Nat (λ m → Even (add n m))) 367 | a = record 368 | { fst = suc zero 369 | ; snd = record 370 | { fst = suc (suc (suc zero)) 371 | ; snd = even-plus2 (even-plus2 even-zero) 372 | } 373 | } 374 | ``` 375 | Σ 여러개를 중첩해서 쓰면 여러 값을 저장하고, 여러 값에 대한 명제를 저장할 수 있다. 376 | 377 | ```agda 378 | record Nat-pair-sum-even : Set where 379 | field 380 | fst : Nat 381 | snd : Nat 382 | sum-even : Even (add fst snd) 383 | 384 | a : Nat-pair-sum-even 385 | a = record 386 | { fst = suc zero 387 | ; snd = suc (suc (suc zero)) 388 | ; sum-even = even-plus2 (even-plus2 even-zero) 389 | } 390 | ``` 391 | 하지만 이렇게 표현하는 것이 더 깔끔하다. 392 | 393 | Σ 타입은 간단한 경우에 사용하고, 보통 값과 명제의 개수가 많은 경우에는 이렇게 record를 따로 만든다. 394 | 395 | ## 예시: 모든 자연수 n에 대해, n이 짝수라면 m * 2 = n을 만족하는 짝수 m을 찾을 수 있다. 396 | ```agda 397 | -- MulTwo a b는 "a * 2 = b" 명제를 의미한다. 398 | data MulTwo : Nat → Nat → Set where 399 | multwo-zero : MulTwo zero zero 400 | multwo-suc : {n m : Nat} → MulTwo n m → MulTwo (suc n) (suc (suc m)) 401 | ``` 402 | 먼저 "a * 2 = b" 명제를 의미하는 타입을 정의한다. 403 | ```agda 404 | f : {n : Nat} → Even n → Σ Nat (λ m → MulTwo m n) 405 | f {zero} even-zero = record 406 | { fst = zero 407 | ; snd = multwo-zero 408 | } 409 | f {suc (suc n)} (even-plus2 even-n) = record 410 | { fst = suc (Σ.fst a) 411 | ; snd = multwo-suc (Σ.snd a) 412 | } 413 | where 414 | a : Σ Nat (λ m → MulTwo m n) 415 | a = f even-n 416 | ``` 417 | Even 타입의 값을 한 겹씩 까면서, fst 값을 1씩 증가시키는 방식으로 증명할 수 있다. (재귀적) 418 | 419 | 420 | # ∀와 ∃로 타입 해석하기 421 | 앞서 `(a : A) → P a` 형태의 함수 타입과, `Σ A P`형태 타입을 알아보았다. 이 두 타입은 각각 "모든 a에 대해 P(a)다" 와, "P(a)를 만족하는 a가 있다"로 해석될 수 있는데, 이는 각각 논리학에서의 ∀과 ∃에 대응된다. 422 | 423 | |Agda|논리학적 의미| 424 | |---|---| 425 | |(a : A) → P a|∀ a ∈ A, P(a)| 426 | |Σ A P|∃ a ∈ A, P(a)| 427 | 428 | 단, ∃는 고전적 수학에서 "조건을 만족하는 값이 존재한다(구체적인 값을 찾을 수 없을 수도 있음)"를 의미하지만, Agda에서 Σ는 "조건을 만족하는 구체적인 값을 찾을 수 있다"를 의미한다. 429 | 430 | # 타입이 집합 431 | |타입|대응되는 명제| 432 | |---|---| 433 | |Even n|n이 짝수다| 434 | |NonZero n|n이 0이 아니다| 435 | 436 | 위에서 이런 표를 봤다. 437 | 438 | |명제|진리집합|진리집합에 관한 표현으로 표현한 명제| 439 | |---|---|---| 440 | |n이 짝수다|짝수인 자연수의 집합|n이 (짝수인 자연수의 집합)의 원소다| 441 | |n이 0이 아니다|0이 아닌 자연수의 집합|n이 (0이 아닌 자연수의 집합)의 원소다| 442 | 443 | 위 표에 있던 명제들에 대한 진리집합을 생각해보자. 그리고 진리집합에 대한 명제로 다시 바꾸어 써 보자. 444 | 445 | |타입|대응되는 명제|대응되는 집합론적 명제| 446 | |---|---|---| 447 | |Even n|n이 짝수다|n이 (짝수인 자연수의 집합)의 원소다| 448 | |NonZero n|n이 0이 아니다|n이 (0이 아닌 자연수의 집합)의 원소다| 449 | 450 | 이제 원래 테이블에 연결을 해 보자. 451 | 452 | |타입|대응되는 집합| 453 | |---|---| 454 | |Even|짝수인 자연수의 집합| 455 | |NonZero|0이 아닌 자연수의 집합| 456 | 457 | 타입을 집합처럼 생각할 수 있다. 458 | 459 | 일반적으로, indexed datatype은 집합처럼 생각할 수 있다. 460 | 461 | ## 예시: 짝수인 자연수의 집합 462 | Even은 짝수인 자연수의 집합으로 볼 수 있다. 463 | ```agda 464 | -- 2는 (짝수의 집합)의 원소다. 465 | a : Even (suc (suc zero)) 466 | a = even-plus2 even-zero 467 | 468 | -- 0은 (짝수의 집합)의 원소다. 469 | b : Even zero 470 | b = even-zero 471 | ``` 472 | 473 | ## 예시: 홀수인 자연수의 집합 474 | Odd는 홀수인 자연수의 집합으로 볼 수 있다. 475 | ```agda 476 | -- 3은 (홀수의 집합)의 원소다. 477 | a : Odd (suc (suc (suc zero))) 478 | a = odd-plus2 odd-one 479 | 480 | -- 1은 (홀수의 집합)의 원소다. 481 | b : Odd (suc zero) 482 | b = odd-one 483 | ``` 484 | 485 | ## 예시: (n, 2 + n)의 집합 486 | ```agda 487 | -- Plus2는 (n, 2 + n)의 집합이다. 488 | -- Plus2 = { (n, 2 + n) | n ∈ ℕ } 489 | data Plus2 : Nat → Nat → Set where 490 | plus2 : {n : Nat} → Plus2 n (suc (suc n)) 491 | 492 | -- (0, 2)는 ((n, 2 + n)의 집합)의 원소다. 493 | a : Plus2 zero (suc (suc zero)) 494 | a = plus2 495 | 496 | -- (1, 3)은 ((n, 2 + n)의 집합)의 원소다. 497 | b : Plus2 (suc zero) (suc (suc (suc zero))) 498 | b = plus2 499 | 500 | -- (a, b)가 ((n, 2 + n)의 집합)의 원소이면, (1 + a, 1 + b)도 ((n, 2 + n)의 집합)의 원소이다. 501 | f : {a b : Nat} → Plus2 a b → Plus2 (suc a) (suc b) 502 | f {a} {suc (suc a)} plus2 = plus2 503 | ``` --------------------------------------------------------------------------------