Hindley-Milner 유형 시스템 만 사용하여 목록 정의


10

나는 Hindley-Milner 유형의 추론 시스템작동 하는 작은 람다 미적분학 컴파일러에서 일하고 있으며 이제는 링크 된 코드가 아닌 재귀 렛을 지원 하므로 Turing을 완성하기에 충분해야합니다 .

이제 문제는 목록을 지원하는 방법 또는 이미 목록을 지원하는지 여부를 알지 못하므로 인코딩 방법을 찾아야한다는 것입니다. 형식 시스템에 새 규칙을 추가하지 않고도 규칙을 정의하고 싶습니다.

내가 목록을 생각할 수있는 가장 쉬운 방법 xnull(또는 빈 목록) 또는 x의 목록과 목록이 모두 포함 된 쌍 입니다 x. 그러나 이렇게하려면 제품 및 합계 유형이라고 생각하는 쌍 및 / 또는을 정의 할 수 있어야합니다.

이 방법으로 쌍을 정의 할 수있는 것 같습니다 :

pair = λabf.fab
first = λp.p(λab.a)
second = λp.p(λab.b)

때문에 pair유형을 할 것이다 a -> (b -> ((a -> (b -> x)) -> x)), 통과 후, 말을 int하고는 string,이 유형 뭔가를 얻을 것 (int -> (string -> x)) -> x한 쌍의 표현이 될 것이다, int하고 string. 여기서 나를 귀찮게하는 것은 그것이 쌍을 나타내는 경우 논리적으로 동등하지 않거나 제안을 암시한다는 것 int and string입니다. 그러나 (((int and string) -> x) -> x)제품 유형을 함수의 매개 변수로만 사용할 수있는 것처럼와 같습니다. 이 답변이 문제를 해결하는 것 같지만 그가 사용하는 기호가 무엇을 의미하는지 전혀 모른다. 또한 이것이 실제로 제품 유형을 인코딩하지 않으면 위의 쌍에 대한 내 정의로 수행 할 수없는 제품 유형으로 할 수있는 일이 있습니까 (n- 튜플도 같은 방식으로 정의 할 수 있음을 고려할 때)? 그렇지 않다면, 이것은 당신이 의미 만 사용하여 (AFAIK) 결합을 표현할 수 없다는 사실과 모순되지 않습니까?

또한 합계 유형은 어떻습니까? 어떻게 든 함수 유형을 사용하여 인코딩 할 수 있습니까? 그렇다면 목록을 정의하기에 충분합니까? 아니면 내 유형 시스템을 확장하지 않고도 목록을 정의하는 다른 방법이 있습니까? 그렇지 않은 경우 가능한 한 간단하게 유지하려면 어떤 변경을해야합니까?

나는 컴퓨터 프로그래머이지만 컴퓨터 과학 자나 수학자가 아니며 수학 표기법을 읽는 데 상당히 나쁘다는 것을 명심하십시오.

편집 : 지금까지 구현 한 기술적 이름이 무엇인지 잘 모르겠지만 기본적으로 위에 링크 한 코드 만 사용합니다.이 코드는 응용 프로그램, 추상화 및 변수에 대한 규칙을 사용하는 제약 조건 생성 알고리즘입니다. Hinley-Milner 알고리즘과 기본 유형을 가져 오는 통합 알고리즘에서 예를 들어, 표현식 \a.a은 type을 생성 a -> a하고 표현식 \a.(a a)은 발생 확인 오류를 발생시킵니다. 이 외에도 정확히 let규칙이 아니라 의사 코드와 같은 재귀 전역 함수를 정의 할 수있는 동일한 효과가있는 함수가 있습니다.

GetTypeOfGlobalFunction(term, globalScope, nameOfFunction)
{
    // Here 'globalScope' contains a list of name-value pair where every value is of class 'ClosedType', 
    // meaning their type will be cloned before unified in the unification algorithm so that they can be used polymorphically 
    tempType = new TypeVariable() // Assign a dummy type to `tempType`, say, type 'x'.
    // The next line creates an scope with everything in 'globalScope' plus the 'nameOfFunction = tempType' name-value pair
    tempScope = new Scope(globalScope, nameOfFunction, tempType) 
    type = TypeOfTerm(term, tempScope) // Calculate the type of the term 
    Unify(tempType, type)
    return type
    // After returning, the code outside will create a 'ClosedType' using the returned type and add it to the global scope.
}

코드는 기본적으로 평소와 같이 용어의 유형을 얻지 만 통합하기 전에 더미 유형으로 정의되는 함수의 이름을 유형 범위에 추가하여 자체적으로 재귀 적으로 사용할 수 있습니다.

편집 2 : 방금 필요없는 목록을 정의하기 위해 재귀 유형이 필요하다는 것을 깨달았습니다.


정확히 구현 한 것에 대해 좀 더 구체적으로 설명 할 수 있습니까? 간단하게 입력 된 람다 미적분학 (재귀 적 정의 포함)을 구현하고 Hindley-Milner 스타일의 파라 메트릭 다형성을 제공 했습니까? 아니면 2 차 다형성 람다 미적분학을 구현 했습니까?
Andrej Bauer

어쩌면 더 쉬운 방법으로 요청할 수 있습니다 .OCaml 또는 SML을 가져 와서 순수한 람다 용어와 재귀 정의로 제한하면 그게 당신이 말하는 것입니까?
Andrej Bauer

@AndrejBauer : 질문을 편집했습니다. OCaml과 SML에 대해 잘 모르겠지만 Haskell을 가져 와서 람다 용어와 최상위 재귀 렛 (예 :)으로 제한하면 확실합니다 let func = \x -> (func x).
Juan

1
궁금한 점이 있으시면 이 메타 포스트를 확인하십시오 .
Juho

답변:


13

한 쌍

이 인코딩은 교회 인코딩 쌍입니다. 비슷한 기술로 부울, 정수, 목록 및 기타 데이터 구조를 인코딩 할 수 있습니다.

x:a; y:bpair x y(a -> b -> t) -> t¬

()¬(¬¬)(¬)()
ab tpairt

pair한 쌍의 유형에 대한 생성자이며 first하고 second소멸자이다. (이것들은 객체 지향 프로그래밍에서 사용되는 것과 같은 단어입니다. 여기서 단어는 여기 에 들어 가지 않을 유형과 용어 의 논리적 해석 과 관련된 의미를 갖 습니다.) 직관적으로 소멸자는 객체와 생성자에서 객체의 일부에 적용되는 함수를 인수로 사용하여 소멸자를위한 길을 닦습니다. 이 원칙은 다른 유형에도 적용될 수 있습니다.

합계

차별적 노동 조합의 교회 인코딩은 본질적으로 한 쌍의 교회 인코딩과 이중이다. 한 쌍으로 구성해야하는 두 부분이 있고 둘 중 하나를 추출하도록 선택할 수있는 경우 두 가지 방법 중 하나로 조합을 구성 할 수 있으며 사용할 때 두 가지 방법을 모두 사용해야합니다. 따라서 두 개의 생성자가 있으며 두 개의 인수를 취하는 단일 소멸자가 있습니다.

let case w = λf. λg. w f g           case : ((a->t) -> (b->t) -> t) -> (a->t) -> (b->t) -> t
  (* or simply let case w = w *)
let left x = λf. λg. f x             left : a -> ((a->t) -> (b->t) -> t)
let right y = λf. λg. g x            right : b -> ((a->t) -> (b->t) -> t)

유형을 (a->t) -> (b->t) -> t로 축약하겠습니다 SUM(a,b)(t). 그런 다음 소멸자와 생성자의 유형은 다음과 같습니다.

case : SUM(a,b)(t) -> (a->t) -> (b->t) -> t
left : a -> SUM(a,b)(t)
right : b -> SUM(a,b)(t)

그러므로

case (left x) f g → f x
case (rightt y) f g → g y

기울기

목록의 경우 동일한 원칙을 다시 적용하십시오. 요소 유형이있는 목록 a은 두 가지 방법으로 만들 수 있습니다. 빈 목록이거나 요소 (머리)와 목록 (꼬리)이 될 수 있습니다. 이 소멸자에 관해서 쌍에 비해 약간의 트위스트있다 : 당신은 두 개의 별도의 소멸자를 가질 수 없습니다 headtail때문에 비어 있지 않은 목록에 그들이하고자에서만 작동합니다. 두 개의 인수를 가진 단일 소멸자가 필요합니다. 그 중 하나는 nil 경우에 0 인수 함수 (즉, 값)이고 다른 하나는 cons 경우에 2 인수 함수입니다. 기능은 좋아하는 is_empty, head그리고 tail그에서 파생 될 수있다. 합계의 경우와 마찬가지로 목록은 직접 자체 소멸자 함수입니다.

let nil = λn. λc. n
let cons h t = λn. λc. c h t
let is_empty l = l true (λh. λt. false) 
let head l default = l default (λh. λt. h)
let tail l default = l default (λh. λt. t)

consconscons1,,

추측 할 때 동종 목록 만 포함하는 형식을 정의하려면 재귀 형식이 필요합니다. 왜? 리스트의 타입을 보자. 리스트는 두 개의 인수, 즉 비어있는리스트에서 리턴 할 값과 cons 셀에서 리턴 할 값을 계산하는 함수를 사용하는 함수로 인코딩됩니다. 하자 a요소의 형태가 될, b리스트의 종류, 그리고 c소멸자에 의해 반환되는 유형합니다. 목록의 유형은

a -> (a -> b -> c) -> c

목록을 균질하게 만드는 것은 단점 셀이라면 꼬리는 전체와 동일한 유형을 가져야한다는 것입니다.

a -> (a -> b -> c) -> c = b

Hindley-Milner 유형 시스템은 이러한 재귀 유형으로 확장 될 수 있으며 실제로는 실제 프로그래밍 언어가이를 수행합니다. 실제 프로그래밍 언어는 이러한 "네이 키드 (naked)"방정식을 허용하지 않으며 데이터 생성자를 필요로하지만 기본 이론의 본질적인 요구 사항은 아닙니다. 데이터 생성자를 요구하면 형식 유추가 간단 해지지 만 실제로는 실제로 버그가 있지만 의도하지 않은 제약 조건으로 인해 함수가 사용되는 경우 이해하기 어려운 형식 오류가 발생하는 함수를 받아들이지 않는 경향이 있습니다. 예를 들어 OCaml은 기본이 아닌 -rectypes컴파일러 옵션 으로 만 보호되지 않은 재귀 유형을 허용 합니다. OCaml 구문에서 위의 정의와 함께 표기법을 사용하는 동종 목록의 유형 정의와 함께 있습니다.앨리어싱 된 재귀 유형 : type_expression as 'a유형 type_expression이 변수와 통합 됨을 의미 합니다 'a.

# let nil = fun n c -> n;;
val nil : 'a -> 'b -> 'a = <fun>
# let cons h t = fun n c -> c h t;;
val cons : 'a -> 'b -> 'c -> ('a -> 'b -> 'd) -> 'd = <fun>
# let is_empty l = l true (fun h t -> false);;
val is_empty : (bool -> ('a -> 'b -> bool) -> 'c) -> 'c = <fun>
# let head l default = l default (fun h t -> h);;
val head : ('a -> ('b -> 'c -> 'b) -> 'd) -> 'a -> 'd = <fun>
# let tail l default = l default (fun h t -> t);;
val tail : ('a -> ('b -> 'c -> 'c) -> 'd) -> 'a -> 'd = <fun>
# type ('a, 'b, 'c) ulist = 'c -> ('a -> 'b -> 'c) -> 'c;;
type ('a, 'b, 'c) ulist = 'c -> ('a -> 'b -> 'c) -> 'c
# is_empty (cons 1 nil);;
- : bool = false
# head (cons 1 nil) 0;;
- : int = 1
# head (tail (cons 1 (cons 2.0 nil)) nil) 0.;;
- : float = 2.

(* -rectypes is required for what follows *)
# type ('a, 'b, 'c) rlist = 'c -> ('a -> 'b -> 'c) -> 'c as 'b;;
type ('a, 'b, 'c) rlist = 'b constraint 'b = 'c -> ('a -> 'b -> 'c) -> 'c
# let rcons = (cons : 'a -> ('a, 'b, 'c) rlist -> ('a, 'b, 'c) rlist);;
val rcons :
  'a ->
  ('a, 'c -> ('a -> 'b -> 'c) -> 'c as 'b, 'c) rlist -> ('a, 'b, 'c) rlist =
  <fun>
# head (rcons 1 (rcons 2 nil)) 0;;
- : int = 1
# tail (rcons 1 (rcons 2 nil)) nil;;
- : 'a -> (int -> 'a -> 'a) -> 'a as 'a = <fun>
# rcons 1 (rcons 2.0 nil);;
Error: This expression has type
         (float, 'b -> (float -> 'a -> 'b) -> 'b as 'a, 'b) rlist = 'a
       but an expression was expected of type
         (int, 'b -> (int -> 'c -> 'b) -> 'b as 'c, 'b) rlist = 'c

주름

이것을 좀 더 일반적으로 살펴보면 데이터 구조를 나타내는 함수는 무엇입니까?

  • (엑스,와이)엑스와이
  • 나는나는(엑스)나는엑스
  • [엑스1,,엑스]

일반적으로 데이터 구조는 접기 기능으로 표시됩니다 . 이것은 데이터 구조에 대한 일반적인 개념입니다. 접기 기능은 데이터 구조를 순회하는 고차 함수입니다. 접기가 보편적 이라는 기술적 의미가 있습니다 . 모든 "일반적인"데이터 구조 탐색은 접기로 표현할 수 있습니다. 데이터 구조는 폴드 함수가 보여 주듯이 표현 될 수 있습니다. 데이터 구조에 대해 알아야 할 것은 데이터를 순회하는 방법 뿐이고 나머지는 구현 세부 사항입니다.


정수, 쌍, 합계의 " 교회 인코딩"을 언급 하지만 목록에는 Scott 인코딩 을 제공합니다 . 유도 형의 인코딩에 익숙하지 않은 사람들에게는 다소 혼란 스러울 수 있다고 생각합니다.
Stéphane Gimenez

그러니까 기본적으로, 내 쌍의 유형은 정말 그냥 반환 할 수 이러한 유형의 함수로 제품의 유형이 아닌 t걸릴 것으로 예상되는 인수를 무시 a하고 b(이는 정확히 (a and b) or t말하고있다). 그리고 합계와 같은 종류의 문제가있는 것 같습니다. 또한 재귀 유형이 없으면 동종 목록이 없습니다. 몇 마디로, 균질 목록을 얻으려면 합계, 곱 및 재귀 유형 규칙을 추가해야한다고 말하고 있습니까?
Juan

합계 섹션 case (right y) f g → g y이 끝났 습니까 ?
Juan

@ StéphaneGimenez 나는 몰랐다. 나는 타이핑 된 세계에서 이러한 인코딩 작업에 익숙하지 않습니다. Church 인코딩과 Scott 인코딩에 대한 참조를 제공 할 수 있습니까?
Gilles 'SO- 악마 그만'

@JuanLuisSoldi 아마도 "추가적인 간접적 인 수준으로 해결할 수없는 문제는 없다"고 들었을 것입니다. 교회 인코딩은 함수 호출 레벨을 추가하여 데이터 구조를 함수로 인코딩합니다. 데이터 구조는 파트에 작용하기 위해 함수에 적용하는 2 차 함수가됩니다. 동종 목록 유형을 원한다면 테일 유형이 전체 목록 유형과 동일하다는 사실을 처리해야합니다. 나는 이것이 일종의 재귀 형식을 포함해야한다고 생각합니다.
Gilles 'SO- 악마 그만해'

2

합계 유형을 태그 및 값이있는 제품 유형으로 나타낼 수 있습니다. 이 경우 비트를 속여서 하나의 태그를 사용하여 null을 나타내거나 두 번째 태그가 머리 / 꼬리 쌍을 나타냅니다.

일반적인 방법으로 부울을 정의합니다.

true = λi.λe.i
false = λi.λe.e
if = λcond.λthen.λelse.(cond then else)

그런 다음 목록은 첫 번째 요소가 부울이고 두 번째 요소가 머리 / 꼬리 쌍인 쌍입니다. 몇 가지 기본 목록 기능 :

isNull = λl.(first l)
null = pair false false     --The second element doesn't matter in this case
cons = λh.λt.(pair true (pair h t ))
head = λl.(fst (snd l))   --This is a partial function
tail = λl.(snd (snd l))   --This is a partial function  

map = λf.λl.(if (isNull l)
                 null 
                 (cons (f (head l)) (map f (tail l) ) ) 

그러나 이것은 나에게 균질 한 목록을주지 않을 것입니다, 이것이 맞습니까?
Juan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.