α가 임의의 타입이라면, α에 대한 단항 술어 p를 α→Prop 타입의 객체로 나타낼 수 있음에 주목하십시오. 이 경우, x : α가 주어지면, px는 x에 대해 p가 성립한다는 주장을 나타냅니다. 마찬가지로, 객체 r : α→α→Prop은 α에 대한 이항 관계를 나타냅니다. xy : α가 주어지면, rxy는 x가 y와 관계가 있다는 주장을 나타냅니다.
전칭 기호(universal quantifier) ∀x:α,px는 “모든 x : α에 대해 px가 성립한다”라는 주장을 나타내는 것으로 이해됩니다. 명제 연결사와 마찬가지로, 자연 연역 체계에서 “forall” 역시 도입 규칙과 소거 규칙에 의해 지배됩니다. 비형식적으로 도입 규칙은 다음과 같이 서술됩니다:
x : α가 임의로 주어진 맥락에서 px의 증명이 주어지면, ∀x:α,px의 증명을 얻습니다.
소거 규칙은 다음과 같습니다:
∀x:α,px의 증명과 임의의 항 t : α가 주어지면, pt의 증명을 얻습니다.
함의의 경우와 마찬가지로, 여기서 명제-타입 대응(propositions-as-types) 해석이 작동합니다. 의존 화살표 타입에 대한 도입 규칙과 소거 규칙을 기억해 봅시다.
x : α가 임의인 문맥에서 βx 타입을 갖는 항 t가 주어지면, (funx:α=>t):(x:α)→βx가 성립합니다.
소거 규칙은 다음과 같이 명시됩니다:
항 s : (x:α)→βx와 임의의 항 t : α가 주어지면, st:βt가 성립합니다.
px가 Prop 타입을 가지는 경우, (x:α)→βx를 ∀x:α,px로 바꾸면 이를 전칭 기호를 포함하는 증명을 구성하기 위한 올바른 규칙으로 읽을 수 있습니다.
따라서 구성 계산법(Calculus of Constructions)은 이러한 방식으로 의존 화살표 타입을 forall-표현식과 동일시합니다. p가 임의의 표현식이라면, ∀x:α,p는 (x:α)→p에 대한 대안적 표기법에 지나지 않으며, p가 명제인 경우에는 전자가 후자보다 더 자연스럽다는 발상에서 비롯된 것입니다. 일반적으로 표현식 p는 x:α에 의존하게 됩니다. 일반적인 함수 공간의 경우, α→β를 β가 x에 의존하지 않는 (x:α)→β의 특수한 경우로 해석할 수 있었다는 점을 상기하십시오. 마찬가지로, 명제 사이의 함의 p→q도 표현식 q가 x에 의존하지 않는 ∀x:p,q의 특수한 경우로 생각할 수 있습니다.
표기 관례상, 우리는 전칭 기호에 가능한 한 가장 넓은 범위를 부여하므로, 위 예시에서 x에 대한 전칭 기호를 가설로 한정하려면 괄호가 필요합니다. ∀y:α,py를 증명하는 정형적인 방법은 임의의 y를 취하여 py를 증명하는 것입니다. 이것이 도입 규칙입니다. 이제 h가 ∀x:α,px∧qx 타입을 갖는다고 할 때, 식 hy는 py ∧ qy 타입을 갖습니다. 이것이 소거 규칙입니다. 좌측 연언지를 취하면 원하는 결론인 py를 얻습니다.
묶인 변수의 이름을 바꾸는 정도로만 차이가 나는 표현은 동치로 간주된다는 점을 기억하십시오. 예를 들어, 가설과 결론 모두에서 동일한 변수 x를 사용했다가, 증명에서는 이를 다른 변수 z로 인스턴스화할 수도 있습니다.
여기서 무슨 일이 일어나고 있는지 생각해 봅시다. trans_r를 값 abc에 인스턴스화하면, 결국 rab → rbc → rac의 증명을 얻게 됩니다. 이를 “가설” hab:rab에 적용하면, 함의 rbc → rac의 증명을 얻습니다. 마지막으로, 이를 가설 hbc에 적용하면 결론 rac의 증명이 산출됩니다.
이와 같은 상황에서는 인자 habhbc로부터 추론될 수 있는 인자 abc를 매번 제공하는 것이 번거로울 수 있습니다. 그런 이유로, 이러한 인자들을 암시적으로 만드는 것이 일반적입니다.
장점은 rac의 증명으로 단순히 trans_rhabhbc라고 쓸 수 있다는 것입니다. 단점은 Lean이 trans_r와 trans_rhab 표현식에서 인자의 타입을 추론하기에 충분한 정보를 가지고 있지 않다는 것입니다. 첫 번째 #check 명령의 출력은 r ?m.1 ?m.2 → r ?m.2 ?m.3 → r ?m.1 ?m.3이며, 이는 이 경우 암시적 인자가 지정되지 않았음을 나타냅니다.
전칭 양화사를 사용하는 데 익숙해지려면, 이 절 끝에 있는 연습문제 중 몇 가지를 풀어 보아야 합니다.
Prop을 다른 타입들과 구별 짓는 것은 바로 의존 화살표 타입, 특히 전칭 한정자에 대한 타이핑 규칙입니다. α : Sorti와 β : Sortj가 있다고 가정합시다. 여기서 표현식 β는 변수 x : α에 의존할 수 있습니다. 그러면 (x:α)→β는 Sort(imaxij)의 원소이며, 여기서 imax i j는 j가 0이 아닐 경우 i와 j의 최댓값이고, 그렇지 않을 경우 0입니다.
아이디어는 다음과 같습니다. j가 0이 아니라면, (x:α)→β는 Sort(maxij)의 원소입니다. 다시 말해, α에서 β로 가는 의존 함수의 타입은 색인이 i와 j의 최댓값인 유니버스에 “존재”합니다. 그러나 β가 Sort0에 속한다고, 즉 Prop의 원소라고 가정해 봅시다. 이 경우, α가 어떤 타입 유니버스에 속하든 상관없이 (x:α)→β 또한 Sort0의 원소입니다. 다시 말해, β가 α에 의존하는 명제라면, ∀x:α,β 역시 명제입니다. 이는 Prop을 데이터가 아닌 명제들의 타입으로 해석함을 반영하며, 바로 이 점이 Prop을 impredicative하게 만듭니다.
“술어적”이라는 용어는 20세기 전환기 무렵의 기초론적 발전에서 유래합니다. 당시 푸앵카레와 러셀 같은 논리학자들은 집합론적 역설의 원인을, 정의되고 있는 바로 그 속성을 포함하는 모음에 대해 양화함으로써 어떤 속성을 정의할 때 발생하는 “악순환”에 있다고 보았습니다. α가 임의의 타입이라면, α에 대한 모든 술어의 타입인 α→Prop(“α의 거듭제곱 타입”)을 구성할 수 있다는 점에 유의하십시오. Prop의 비술어성은 α→Prop에 대해 양화하는 명제를 구성할 수 있음을 의미합니다. 특히, α에 대한 모든 술어에 대해 양화함으로써 α에 대한 술어를 정의할 수 있는데, 이는 한때 문제로 여겨졌던 바로 그 순환의 유형입니다.
이제 Lean의 라이브러리에 정의된 가장 근본적인 관계 중 하나, 즉 동치 관계로 눈을 돌려 봅시다. 귀납적 타입에 관한 장에서는 Lean의 논리적 프레임워크의 기본 요소들로부터 동치가 어떻게 정의되는지를 설명하겠습니다. 그동안 여기서는 그것을 사용하는 방법을 설명합니다.
반사성(reflexivity)은 겉보기보다 강력합니다. 구성 계산법(Calculus of Constructions)의 항들은 계산적 해석을 가지며, 논리적 프레임워크는 공통된 축약형(reduct)을 갖는 항들을 동일한 것으로 취급한다는 점을 상기하십시오. 그 결과, 일부 자명하지 않은 항등식들도 반사성으로 증명될 수 있습니다:
하지만 동치는 동치 관계보다 훨씬 더 많은 것을 담고 있습니다. 모든 단언이 이 동치를 존중한다는 중요한 성질을 가지고 있는데, 이는 진리값을 바꾸지 않고도 같은 값을 갖는 식들을 서로 치환할 수 있다는 의미입니다. 즉, h1 : a=b와 h2 : pa가 주어지면, 치환을 이용하여 pb에 대한 증명을 구성할 수 있습니다: Eq.substh1h2.
두 번째 표기법에 나오는 삼각형은 Eq.subst와 Eq.symm 위에 구축된 매크로이며, \t를 입력하여 입력할 수 있습니다.
Eq.subst 규칙은 더 명시적인 치환을 수행하는 다음 보조 규칙들을 정의하는 데 사용됩니다. 이 규칙들은 적용 항, 즉 st 형태의 항을 다루도록 설계되었습니다. 구체적으로, congrArg는 인자를 대체하는 데, congrFun은 적용되고 있는 항을 대체하는 데, congr는 둘 다를 한꺼번에 대체하는 데 사용될 수 있습니다.
Eq.subst의 두 번째 암묵적 매개변수는 치환이 일어날 문맥을 제공하는 것으로, α→Prop 타입을 가진다는 점에 주목하십시오. 따라서 이 술어를 추론하려면 고차 단일화의 인스턴스가 필요합니다. 완전히 일반적인 경우, 고차 단일자가 존재하는지 판정하는 문제는 결정 불가능하며, Lean은 기껏해야 불완전하고 근사적인 해법만을 제공할 수 있습니다. 그 결과 Eq.subst가 항상 원하는 대로 동작하지는 않습니다. 매크로 h▸e는 이 암묵적 매개변수를 계산하는 데 더 효과적인 휴리스틱을 사용하며, Eq.subst를 적용했을 때 실패하는 상황에서도 종종 성공합니다.
등식 추론은 매우 흔하고 중요하기 때문에, Lean은 이를 더 효과적으로 수행할 수 있는 여러 메커니즘을 제공합니다. 다음 절에서는 더 자연스럽고 명료한 방식으로 계산적 증명을 작성할 수 있는 구문을 소개합니다. 하지만 더 중요한 것은, 등식 추론이 항 재작성기(term rewriter), 단순화기(simplifier), 그리고 다른 종류의 자동화에 의해 지원된다는 점입니다. 항 재작성기와 단순화기는 다음 절에서 간략히 설명하고, 이어서 다음 장에서 더 자세히 설명합니다.
대신 simp 택틱은 주어진 항등식을 반복적으로, 임의의 순서로, 항 안에서 적용 가능한 모든 곳에 적용하여 목표를 다시 씁니다. 또한 이 택틱은 시스템에 미리 선언되어 있던 다른 규칙들도 사용하며, 무한 반복을 피하기 위해 교환법칙을 현명하게 적용합니다. 그 결과, 다음과 같은 방식으로도 이 정리를 증명할 수 있습니다.
위 예제는 관계에 대한 중위 표기법이 없더라도 calc를 사용할 수 있음을 분명히 보여줍니다. Lean은 나눗셈에 대한 표준 유니코드 표기법(\dvd 또는 \mid로 입력할 수 있는 ∣를 사용)을 이미 포함하고 있으므로, 위 예제에서는 충돌을 피하기 위해 일반적인 수직 막대를 사용합니다. 실제로는 match ... with 표현식에서 사용되는 ASCII |와 혼동될 위험이 있으므로, 이는 좋은 방법이 아닙니다.
Exists.intro는 암시적 인자를 가진다는 점에 유의하십시오. Lean은 결론 ∃x,px에서 술어 p : α→Prop을 추론해야 합니다. 이는 사소한 일이 아닙니다. 예를 들어, hg : g00=0이 있고 Exists.intro0hg를 작성한다면, 술어 p에 대해 가능한 값이 여러 가지 있으며, 이는 정리 ∃x,gxx=x, ∃x,gxx=0, ∃x,gx0=x 등에 대응됩니다. Lean은 맥락을 사용하여 어느 것이 적절한지 추론합니다. 이는 다음 예제에서 설명되는데, 여기서는 옵션 pp.explicit을 true로 설정하여 Lean의 예쁜 출력기가 암시적 인자를 보여 주도록 요청합니다.
Exists.intro는 증인을 단언의 본문에 숨기기 때문에, 이를 정보 은닉 연산으로 볼 수 있습니다. 존재 소거 규칙인 Exists.elim은 그 반대의 연산을 수행합니다. 이는 임의의 값 w에 대해 q가 pw로부터 따라 나옴을 보임으로써, ∃x:α,px로부터 명제 q를 증명할 수 있게 해 줍니다. 대략적으로 말하자면, px를 만족하는 x가 존재한다는 것을 알고 있으므로, 그것에 이름을 붙일 수 있는데, 이를테면 w라고 합시다. 만약 q가 w를 언급하지 않는다면, q가 pw로부터 따라 나옴을 보이는 것은 q가 그런 x의 존재로부터 따라 나옴을 보이는 것과 마찬가지입니다. 예를 하나 들어 보겠습니다.
존재 소거 규칙을 논리합 소거 규칙과 비교해 보면 도움이 될 수 있습니다. ∃x:α,px라는 주장은 a가 α의 모든 원소를 아우를 때 명제 pa들의 커다란 논리합으로 생각할 수 있습니다. 익명 생성자 표기법 ⟨w,hw.right,hw.left⟩는 중첩된 생성자 적용을 축약한 것임에 유의하십시오. 이는 ⟨w, ⟨hw.right, hw.left⟩⟩로 똑같이 쓸 수도 있었습니다.
존재 명제는 의존 타입 절에서 설명한 시그마 타입과 매우 유사하다는 점에 유의하십시오. 차이점은 존재 명제는 명제인 반면, 시그마 타입은 타입이라는 것입니다. 그 외에는 매우 유사합니다. 술어 p : α→Prop와 타입의 패밀리 β : α→Type가 주어졌을 때, h : pa와 h':βa를 만족하는 항 a : α에 대해 항 Exists.introah는 타입 (∃x:α,px):Prop을 가지며, Sigma.mkah'는 타입 (Σx:α,βx)를 가집니다. ∃와 Σ 사이의 유사성은 커리-하워드 동형사상의 또 다른 사례입니다.
Lean은 match 표현식을 사용하여 존재 한정사로부터 제거하는 더 편리한 방법을 제공합니다.
match 표현식은 복잡한 함수를 정의하는 편리하고 표현력 있는 방법을 제공하는 Lean의 함수 정의 시스템의 일부입니다. 이번에도 이 메커니즘을 증명 작성에 이용할 수 있게 해 주는 것은 커리-하워드 동형사상입니다. match 문은 존재 단언을 w와 hw 구성 요소로 “해체”하며, 이 구성 요소들은 문의 본문에서 명제를 증명하는 데 사용될 수 있습니다. 더 명확하게 하기 위해 match에서 사용되는 타입에 주석을 달 수 있습니다.
구성적 “or”가 고전적 “or”보다 더 강력한 것과 마찬가지로, 구성적 “exists” 역시 고전적 “exists”보다 더 강력합니다. 예를 들어, 다음 함의는 고전적 추론을 필요로 하는데, 구성적 관점에서는 모든 x가 ¬p를 만족하는 것은 아니라는 사실을 아는 것이 p를 만족하는 특정한 x를 갖는 것과 같지 않기 때문입니다.
두 번째 예제와 마지막 두 예제는 α 타입의 원소 a가 적어도 하나 존재한다는 가정을 필요로 한다는 점에 유의하십시오.
다음은 더 어려운 것 중 두 가지에 대한 풀이입니다:
openClassicalvariable(α:Type)(pq:α→Prop)variable(a:α)variable(r:Prop)example:(∃x,px∨qx)↔(∃x,px)∨(∃x,qx):=Iff.intro(fun⟨a,(h1:pa∨qa)⟩=>Or.elimh1(funhpa:pa=>Or.inl⟨a,hpa⟩)(funhqa:qa=>Or.inr⟨a,hqa⟩))(funh:(∃x,px)∨(∃x,qx)=>Or.elimh(fun⟨a,hpa⟩=>⟨a,(Or.inlhpa)⟩)(fun⟨a,hqa⟩=>⟨a,(Or.inrhqa)⟩))example:(∃x,px→r)↔(∀x,px)→r:=Iff.intro(fun⟨b,(hb:pb→r)⟩=>funh2:∀x,px=>showrfromhb(h2b))(funh1:(∀x,px)→r=>show∃x,px→rfrombyCases(funhap:∀x,px=>⟨a,λVariable name `h'` is not explicitly referenced.Hint: The binding can be removed (if unused) or named `_` (if used implicitly). Alternatively, prefix the name with `_` to silence this warning:[apply]_h'Note: This linter can be disabled with `set_option linter.unusedVariables false`h'=>h1hap⟩)(funhnap:¬∀x,px=>byContradiction(funhnex:¬∃x,px→r=>havehap:∀x,px:=funx=>byContradiction(funhnp:¬px=>havehex:∃x,px→r:=⟨x,(funhp=>absurdhphnp)⟩showFalsefromhnexhex)showFalsefromhnaphap)))
이는 Lean에게 assumption 택틱을 사용하도록 지시하며, 이 택틱은 로컬 맥락에서 적절한 가설을 찾아 목표를 증명합니다. assumption 택틱에 대해서는 다음 장에서 더 자세히 배우겠습니다.
또한 ‹p›라고 작성하여 Lean이 증명을 채워 넣도록 요청할 수도 있습니다. 여기서 p는 Lean이 맥락에서 찾아내기를 원하는 증명의 명제입니다. 이러한 꺾쇠 인용부호는 각각 \f<와 \f>를 입력하여 사용할 수 있습니다. 유니코드 기호가 프랑스어 인용부호로도 사용될 수 있기 때문에, 문자 “f”는 “French(프랑스어)”를 의미합니다. 실제로 이 표기법은 Lean에서 다음과 같이 정의되어 있습니다.
매개변수가 없는 경우, Prop 타입의 표현식은 단지 하나의 주장일 뿐이라는 점을 기억하십시오. 아래에 있는 prime과 Fermat_prime의 정의를 채워 넣고, 주어진 각 주장을 구성하십시오. 예를 들어, 모든 자연수 n에 대해 n보다 큰 소수가 존재한다고 주장함으로써 소수가 무한히 많다는 것을 말할 수 있습니다. 골드바흐의 약한 추측은 5보다 큰 모든 홀수가 세 소수의 합이라고 주장합니다. 필요하다면 페르마 소수의 정의나 다른 명제들을 찾아보십시오.