A History Of Haskell

이 기사는 Hudak, Paul, et al. "A history of Haskell: being lazy with class." Proceedings of the third ACM SIGPLAN conference on History of programming languages. ACM, 2007.라는 논문을 읽으면서 개인적으로 필요한 부분을 정리하였습니다. 관심있거나 개인적으로 의미가 있다고 생각하는 부분을 중심으로 번역 및 의역하였습니다. 많은 부분이 생략되어 있기 때문에 꼭 해당 논문을 읽으시고 레퍼런스로 사용하길 권장합니다.

  • UPDATED
    • 2018.05.01, 해당 항목에 링크를 추가하였음
    • 2018.05.15, 04~08장을 요약해서 추가하였음

논문의 저자

1. Introduction

1990년 4월 1일에 발간된 Haskell 보고서의 첫버전(Haskell Report, Version 1.0)의 머릿말엔 Haskell의 태동을 알리는 일화를 짧게 소개하고 있습니다.

1987년 오레곤 주의 포틀랜드에서 열린 Functional Programming Languages and Computer Architecture의 회의 내용은 1) 공통된 형태의 functional programming languages를 설계, 2) 언어에 관한 아이디어를 손쉽게 전달, 3) 응용 프로그램 개발을 위한 안정적인 기반을 제공하고 4) 다른 사람들이 functional programming languages를 사용하도록 장려하는 방법을 논의하는 위원회가 구성되어야 한다고 결정하였습니다.

표현력과 의미론이 비슷하고 non-strict, purely functional을 기반으로 한 프로그래밍 언어가 과도하게 만들어지고 있기 때문에 언어의 일관적인 형태가 없어서 널리 사용되지 못하고 있었습니다.

위의 인용구에서 거론한 문제를 해결하고, 엄격하고 순수한 함수형 형태의 공통 언어를 충족시키기 위한 대안으로 Haskell이 제안되었고, 이 모든 설계 과정을 주도할 위원회를 구성하게 되었습니다.

논문의 구성

본 논문의 목표는 누가 참여했는지, 그리고 어떤 것이 구성원들에게 영감을 주었는지를 포함한 역사를 전달하는 것으로 이 논문은 기술적인 설명이나 튜토리얼이 아닌 발전 과정에 집중합니다. 저자들은 Haskell의 발전 과정을 설명하려고 노력했고 구성원들의 일화와 개인적 반성을 포함시킴으로써 열정적이고 생상한 모습을 담고자 하였으나 설명이 왜곡될 수 있고, 모든 것을 담을 수 없다는 단점을 가집니다.

2. The genesis of Haskell

  • 1978년 John Backus(Fortran, BNF)는 <<Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs>>를 통해 수학적인 논의가 아닌 실제로 사용할 수 있는 새로운 형태의 함수형 프로그래밍 방법을 제안하였습니다.

  • 1950년대 후반 John McCarthyLisp를 만들었고, 1960년대 Peter LandinChristopher Strachey는 프로그래밍 언어를 모델링하는데 람다 미적분학(the lambda calculus)이 중요하다는 사실을 확인하고 추상 기계(abstract machines)와 denotational semantics을 통해 operational semantics의 기초를 만들었습니다.

  • 몇 년 후 StracheyDana Scott은 도메인 이론은 denotational semantics에 수학적인 의미를 부여했습니다. 70년대 초, Rod BurstallJohn Darlington패턴 매칭에 의한 함수 정의를 사용하여 first-order functional language로 변환할 수 있는 방법을 만들었습니다. 같은 시기에 Strachey의 제자인 David Turner는 Landin의 ISWIM a.k.a ISWYM의 하위 집합에서 유래 된 a sugared lambda calculus를 사용해서 어휘적으로 범위가 정해져(lexically scoped variables) 있고, 패턴 매칭에 관한 BurstallDarlington의 아이디어가 포함된 실행 가능한 a pure higher-order functional languageSASL을 개발했습니다.

  • 70 년대 후반, Gerry SussmanGuy SteeleLisp의 파생언어 중 하나인 Scheme을 개발했습니다. Scheme은 lexical scoping를 구현했고, lambda calculus와 밀접하게 관련되어 있습니다. Robin Milner는 거의 같은시기에 MLEdinburgh의 LCF 정리를 증명하기 위한 메타언어로 개발하였습니다. ML에 대한 Milner의 다형성(polymorphic type system)은 많은 영향력을 끼쳤습니다. SchemeML은 모두 엄격한(call-by-value) 언어였고 명령형 기능(features)을 포함하고 있었지만 함수형 프로그래밍 스타일을 촉진하고 특히 고차 함수(higher-order function)을 많이 사용했습니다.

2.1 The call of laziness

그런데 70년대 후반과 80년대 초반에 뭔가 새로운 일이 일어났습니다. 일련의 출판물(series of seminal publications)에서 중요한 프로그램을 작성하기 위한 수단으로서 lazy(non-strict, call-by-need) functional languages에 대한 관심의 촉발 시켰습니다. Lazy evaluation은 독립적으로 세 번의 제안이 있었던 것으로 보입니다.

  • Dan FriedmanDavid WiseLisp의 관점에서 "Cons should not evaluate its arguments(단점은 그 주장을 평가해서는 안됩니다.)"(Friedman and Wise, 1976)라고 평가하였습니다.

  • Peter Henderson(Newcastle)과 James H. Morris Jr.(Xerox PARC)는 "A lazy evaluator"(Henderson and Morris, 1976)를 발표했습니다. 그들의 아이디어의 기반을 제공했던 Vuillemin(Vuillemin, 1974)과 Wadsworth(Wadsworth, 1971)를 인용하지만 POPL에서 그 아이디어를 대중화하였다. Lisp의 변종을 사용하여 denotational semantics과 evaluator의 관계를 보여주었다.

  • David Turner(St. Andrews와 Kent)는 영향력 있는 일련의 언어를 소개했습니다. 1972년에 stric한 언어로 개발되었지만, 1976년에 lazy한 SASL(St Andrews Static Language)(Turner, 1976)를 1982년에 KRC(Kent Re-cursive Calculator)(Turner, 1982)를 소개하였습니다. Turner는 lazy evaluation을 사용한 프로그래밍 방법과 다양한 종류의 행동양식을 사용하기 위한 lazy lists를 소개하였습니다. SASLBurroughs에서 운영체제를 개발하는데 사용되었습니다. 순수하고(pure), 게으른(lazy), 함수형 프로그래밍(functional programming)의 대대적인(in the large) 첫 번째 사례가 거의(almost) 확실합니다.

동시에 lazy languages를 구현하는 새로운 방법에 대한 노력이 있었습니다.

  • 소프트웨어에서 그래프 축소(graph reduction)를 기반으로 한 다양한 기법이 탐구되었으며 특히 TurnerSK combinators를 아주 우아하게(inspirationally elegant) 사용했습니다.(Turner의 작업은 Alonzo Church의 lambda calculus(Church, 1941)의 변형방법인 Haskell 커리인 combinatory calculus(Curry and Feys, 1958)의 기초가 되었습니다.)
  • 이 모든 것이 근본적으로 다른 'non-von Neumann' 하드웨어 아키텍처로 이어질 수 있다는 점입니다. 다양한 종류의 데이터 흐름 및 그래프 축소 기계를 구축하기위한 몇 가지 중요한 프로젝트가 진행 중 (또는 진행 되고) 이었습니다(MITId 프로젝트(Arvind and Nikhil, 1987), UtahRediflow 프로젝트(Keller et al., 1979), Cambridgethe SK combinator 기계인 SKIM(Stoye 외, 1984), the Manchester dataflow 장치(Watson and Gurd, 1982), ImperialALICE paraller reduction 장치(Darlington and Reeve, 1981), Burroughs NORMA combinator 장치(Scheevel, 1986) 및 UtahDDM dataflow 시스템(Davis, 1977)). 이 아키텍처 지향적인 작업의 상당 부분은(전부는 아니지만)은 별다른 소득이 없었다고 판명되었는데, 나중에 상투적인(stock) 아키텍처 컴파일러가 특수용도(specialised) 아키텍처를 능가 할 수 있다는 것을 알게되었습니다. 그러나 그 당시에는 모두 급진적이고 흥미로웠습니다.

80년대 초반 몇몇 주요 회의가 해당 분야에 추가적인 자극을 주었습니다.

  • 1980년 8월, 캘리포니아 주 스탠포드에서 열린 첫 번째 Lisp 회의에서 Burstall, Dave MacQueenDon Sannella on Hope가 대수 데이터 유형(algebraic data types)을 도입한 언어를 소개하였습니다(Burstall 등, 1980).

  • 1981년 7월 Peter Henderson, John Darlington, David TurnerFunctional Programming and its Application 관한 고급 과정을 Newcastle에서 이수했습니다(Darlington et al., 1982). 참석자들 중에는 Gerry Sussman, Gary Lindstrom, David Park, Manfred Broy, Joe StoyEdsger Dijkstra가 참석했습니다(Hughes and Peyton Jones는 학생으로 참석했습니다). Dijkstra는 해당 주제에 대한 논의에 별다른 인상을 받지 못했지만 다른 많은 참석자들에게는 하나의 분수령이 되었습니다.

  • 1981년 9월, Functional Programming Languages and Computer Architecture(FPCA)에 관한 첫번째 회의가 뉴 햄프셔 포츠머스에서 개최되었습니. 여기 Turner는 <>를 발표하였습니다(Wadler는 그의 첫번째 컨퍼런스 발표도 하였습니다). FPCA는 2년에 한번씩 개최되는 주요 컨퍼런스로 자리 잡았습니다.

  • 1982년 9월, 현재 LFP라고 이름 붙여진 두번째 Lisp 컨퍼런스가 펜실베이니아 주 피츠버그에서 열렸습니다. Peter Henderson(헨더슨, 1982)이 functional geometry(Henderson, 1982)에 대해 발표했고 Turner는 infinite data structures를 가진 프로그래밍에 관한 초청 강연을 했습니다(Hudak, Hughes,와 Peyton Jones의 첫번째 논문도 공개되었습니다). 이 회의에 특별 게스트로 ChurchCurry(아마도 뉴욕의 마술사인 Paul Curry일 것으로 추정)가 포함되었습니다. 저녁 식사 후 Barkley Rosser에 의해 간단한 대화가 이어졌고, Curry's paradox의 증명을 발표했을 때 그것을 Y combinator와 관련짓고 Church-Rosser 정리의 새로운 증명을 선보였습니다. LFP는 2년에 한번씩 열리는 중요한 컨퍼런스가 되었습니다. (1996년에 FPCA는 LFP와 결합하여 ICFP(Functional Programming on International Functional Programming)라는 학회가 되었으며 이 분야의 주요 컨퍼런스로 현재까지 남아있습니다)

  • 1987년 8월, 텍사스 대학의 Ham RichardsDavid Turner는 UT의 "Year of Programming"의 일환으로 텍사스 오스틴에서 선언적 프로그래밍(Declarative Programming)에 관한 international school을 조직했습니다. 연사로는 Samson Abramsky, John Backus, Richard Bird, Peter Buneman, Robert Cartwright, Simon Thompson, David TurnerHughes가 있었습니다. 학교의 주요 부분은 Miranda를 사용하는 실용적인 수업과 함께 lazy functional programming 과정이었습니다.

이 모든 것에 엄청난 관심을 보였습니다. functional programming의 단순성과 우아함은 당시의 저자와 다른 연구자들을 사로 잡았습니다. Lazy evaluation(순수한 call-by-name lambda calculus, 무한 데이터 구조를 표현하고 조작 할 수있는 놀라운 가능성, 그리고 간단하고 아름다운 구현 기술)은 마약같은 매력이 있었다.

2.2 A tower of Babel

이 모든 활동의 결과로 1980년대 중반까지 순수한 lazy languages에 대한 설계 및 구현 기술에 관심이 있는 수 많은 연구자가 있었습니다. 사실, 우리 중 많은 사람들이 lazy languages를 독자적으로 설계했고 바쁘게 만들었습니다. 우리의 구현 기술을 설명하기 전에 먼저 언어를 설명하는 논문을 쓰고 있었습니다. 이 lazy Tower of Babel에는 다음과 같은 언어가 포함됩니다.

  • Miranda, SASLKRC 후계자인 MirandaDavid Turner가 SK combinator reduction를 사용하여 설계하고 구현했습니다. SASLKRC는 타입이 지정되지 않았지만 Miranda는 다형성 및 타입 추론을 추가했습니다. 이 아이디어는 ML에서 매우 성공적으로 입증되었습니다.

  • Lazy ML(LML)은 AugustssonJohnsson에 의해 Chalmers에서 개척되었으며 Peyton Jones가 University College London에서 채택했습니다. 이 노력으로 G-machine라는 영향력을 가진 것을 개발하게 되었고, lazy functional programs을 다소 효율적인 코드로 컴파일 할 수 있다는 것을 보여주었습니다(Johnsson, 1984; Augustsson, 1984)

  • OwwellWadler가 개발 한 lazy language로, KRCMiranda의 영향을 받았고 Owwell의 차기 변형 버전 인 OL도 영향을 받았다. BirdWadlerMirandaOrwell에 수학 표기법에 가까운 문법을 사용하여 "바벨탑"을 피한 기능적 프로그래밍에 대한 영향력있는 책을 공동 저술했다(Bird and Wadler, 1988).

  • Alfl, HudakSchem과와 T(Scheme의 변형)를 위해 개발된 기술을 기반으로 컴파일러뿐만 아니라 Alfl을위한 a combinator-based interpreter를 개발했습니다(Hudak, 1984b; Hudak, 1984a).

  • Id, ArvindNikhilMIT에서 개발 한 엄격하지 않은 데이터 흐름 언어로, 목표는 dataflow machine 이었습니다.

  • Clean, Rinus Plasmeijer와 그의 동료(Brus 등, 1987)가 Nijmegen에서 개발한 graph reduction에 기반한 lazy language based explicitly 언어입니다.

  • Ponder, Jon Fairbairn이 고안한 언어인 Impromative higher-rank type systemlexically scoped type 변수가 SKIM의 운영 체제를 작성하는데 사용되었습니다(Fairbairn, 1985; Fairbairn, 1982).

  • Daisy, Cordelia Hall, John O'Donnell 및 그들의 동료(Hall and O'Donnell, 1985)가 인디애나에서 개발 한 변형된 lazy Lisp를 개발하였습니다.

Miranda를 제외하고는 모두 기본적으로 하나의 목표를 위해서 개발된 언어였으며 디자인, 구현 및 사용자 측면에서 부족함을 보였습니다. 각각 흥미로운 아이디어가 많았지만 한 언어가 다른 언어보다 월등히 뛰어나다고 주장할 수 있는 근거는 거의 없습니다. 그와 반대로, 우리는 그것들이 대략적으로 비슷한 구문을 사용하지 못한다고 느꼈습니다. 왜 우리는 우리 모두가 혜택을 누릴 수있는 공통의 공통 언어가 하나도 없었는지 궁금해하기 시작했습니다.

2.3 The birth of Haskell

1987년까지는 필요한 모든 것을 하나의 것으로 뭉칠 수 있도록 촉진시키는 다양한 이벤트들이라 할 수 있습니다. 이 사건은 Peyton Jones가 1987년 Oregon 포틀랜드의 Functional Programming and Computer Architecture Conference (FPCA)에서 Hudak을 만나기 위해 Yale에 머물렀던 87년 가을에 일어났습니다. 상황을 논의한 후 Peyton JonesHudak은 FPCA에서 회의를 시작하여 새롭고 공통된 기능 언어를 디자인하는데 관심을 가지기로 결정했습니다. Wadler는 또한 FPCA로가는 도중에 Yale에서 멈추었으며 회의에 대한 아이디어를 지지했습니다.

FPCA 회의는 Haskell 설계의 시작을 알렸지만 우리는 언어에 대한 이름이 없었으며 기술 토론이나 설계에 관한 결정을 하지 않았습니다. 회의의 핵심 포인트는 앞으로 나아갈 수있는 가장 쉬운 방법중 하나로 기존 언어를 사용하여 발전시키는 것 이었습니다. 개발중인 모든 lazy language 중 David TurnerMiranda가 가장 좋은 후보였습니다. pure하고 잘 설계되어 있으며 Turner의 회사 인 Research Software Ltd의 제품으로 관리되었으며 120개 사이트에서 운영되었습니다. Turner는 회의에 참석하지 않았으므로 위원회의 첫 번째 조치 항목은 Miranda를 새로운 언어의 출발점으로 채택 할 수 있는지 Turner에게 요청하는 것이라고 결론을 냈습니다.

짧고 친절했던 교류끝에 Turner는 제안을 거절했습니다. 그의 목표는 우리와 달랐습니다. 우리는 언어의 기능에 대한 연구를 위해 기존의 목적과는 다른 혀태로 사용될 수 있는 언어를 원했습니다. 특히 우리는 누구나 언어를 확장, 수정, 구현, 구축 그리고 배포 할 수 있는 자유를 추구했습니다. 대조적으로 TurnerMiranda를 완벽한 이식성을 갖춘 단일 언어 표준을 유지하길 원했습니다. 또한 그는 Miranda의 여러 변형 언어가 존재하기를 원하지 않았고 우리가 새로운 언어를 Miranda와 구분하여 두 가지 언어가 혼동되지 않을 것을 요구했습니다. Turner는 위원회에 참석하는 것을 거절하였습니다.

좋든 나쁘 든간에 이것은 중요한 갈림길 이었습니다. 새로운 언어 디자인의 모든 세부 사항을 작업해야 한다는 의미였지만, 언어 설계의 여러 측면에 대한 근본적인 접근 방식을 자유롭게 고려할 수 있었습니다. 예를 들어 Miranda에서 시작했다면 우리는 Typeclasses를 개발하지 않았을 것 같았습니다. 그럼에도 불구하고, HaskellMiranda에게 상당한 빚을 지고 있습니다.

Turner가 우리에게 미란다 사용을 허용하지 않는다는 것을 알게되자 런던의 유니버시티 칼리지에서 호스팅 된 메일링 리스트 fplangc@cs.ucl.ac.uk를 사용하여 활발한 이메일 토론이 빠르게 진행되었습니다. Jones는 교수진이었습니다. 전자 메일 목록 이름은 원래 언어에 대한 이름이 없기 때문에 원래 FPLang 위원회라고 불렀습니다. 우리가 언어를 명명한 후에 "Haskell 위원회"(Haskell Committee)라고 부르기 시작했습니다.

2.4 The first meetings

Yale에서 1988년 1월 9일에서 12일까지 개최 된 첫 번째 모임에서 Hudak은 교수였고, 모임의 첫 번째 순서는 언어에 대한 다음 목표를 수립하는 것이 었습니다.

  1. 대규모 시스템 구축을 비롯하여 교육, 연구 및 응용 프로그램에 적합해야합니다.
  2. 그것은 formal syntaxsemantics를 온전하게 기술 및 출판되어야 한다.
  3. 자유롭게 이용할 수 있어야 합니다. 누구든지 해당 언어를 구현하고 원하는 사람에게 배포 할 수 있어야합니다.
  4. 더 많은 언어 연구를 위한 기초 자료로 활용 되어야 합니다.
  5. 새로운 제안은 폭 넓은 합의를 통해 이뤄저야 합니다.
  6. functional programming languages의 불필요한 다양성을 줄여야 합니다. 보다 구체적으로 말해서 OL에 기초를 두기로 합의했습니다.

마지막 두 가지 목표는 우리가 새로운 영역을 개척하기 보다는 언어를 보수적인 형태로 유지하기로 했다는 사실을 반영합니다. 우리는 아이디어에 관한 합의를 구체화하고 단일한 형태로 통합하는 것 이상을 진행하기로 하였습니다.

이 모든 목표들이 실현 된 것은 아닙니다. 우리는 OLHaskell에 명시적으로 적용한다는 생각을 버렸습니다. 특히 교육, 연구 수업의 적합성, 형식적인 의미를 개발하지 못했습니다.

위원회는 다음과 같은 합의 프로세스를 만들었습니다.

  1. 토론 할 주제를 결정하고 각 주제에 "lead person"을 지정하십시오.
  2. "lead person"은 자신의 주제를 요약하여 토론을 시작합니다. 특히 OL이 어떻게 하는지에 대한 설명하고, 더 나은 대안이 없는 경우 OL을 기본으로 합니다.
  3. 필요한 경우 연구. 휴식, 개별 토론 및 글쓰기를 장려합니다.
  4. 일부 문제는 해결되지 않을 것입니다! 그러한 경우 최종 결의안을 위한 조치 항목을 마련해야 합니다.
  5. 어리석은 것처럼 보일 수도 있지만 적어도 한 가지가 해결 될 때까지 모임을 연기해서는 안됩니다.
  6. 협조와 타협하는 자세가 중요합니다.

위의 다섯 번째 항목은 중요했습니다. 왜냐하면 어떤 언어의 진화에서 작지만 중요한 이름이 부여되는 순간이기 때문입니다. Yale 회의에서 우리는 이름을 선택하기 위해 다음과 같은 과정 (Wadler가 제안한)을 사용했습니다.

누구나 칠판에 쓰여진 하나 이상의 언어 이름을 제안 할 수 있습니다. 이 과정이 끝날 때, Semla, Haskell, Vivaldi, Mozart, CFL (Common Functional Language), Funl 88, Semlor, Candle, Fun, David, Nice ML Nouveau (또는 Miranda Nouveau 또는 LML Nouveau, 또는 ...), Mirabelle, Concord, LL, Slim, Leval, Curry, Frege, Peano, Ease, PortlandHaskell B Curry 등의 다양한 이름에 관한 토론이 끝난 후 각 사람은 그가 싫어하는 이름을 자유롭게 쓸 수 있었습니다. 우리가 끝났을 때, 한가지 이름이 남았습니다.

그 이름은 수학자이자 논리학자인 ``HaskellB. 커리(Haskell B. Curry)에게 경의를 표하여 "카레 (Curry)"를 생각하였으나 더 깊은 토론을 거쳐 새로운 언어의 이름 인 Haskell에 정착했습니다. 나중에 이것이 Pascal 또는 Hassle과 너무 쉽게 혼동되었다는 것을 깨달았습니다!

HudakWise는 커리의 미망인 인 Virginia Curry에게 남편의 이름을 사용할 수 있는지 물어보았습니다. Hudak는 나중에 그녀의 집을 방문하여 거기에 머물렀던 사람들에 관한 이야기를 들었습니다 (예 : ChurchKleene). 그녀의 논평은 "당신도 알다시피, Haskell은 실제로 Haskell이라는 이름을 결코 좋아하지 않았다."였습니다.

글래스고 대학에서 1988년 4월 6일에서 9일까지 개최된 회의를 통해서 functional programming languages 그룹이 급성장하였습니다. 이 회의에서 주요한 많은 결정이 내려졌습니다. HudakWadler가 첫 번째 Haskell 보고서의 편집자가 되었고, 보고서 이름은 "Report on the Programming Language Haskell, A Non-strict, Purely Functional Language"로 정해졌고, 이 보고서는 "Report on the Algorithmic Language Scheme"에 영향을 받았습니다.

'80년대는 functional programming 연구를 수행하는 흥미 진진한 시기였습니다. 이러한 사례를 보여주는 한 가지 사실은 기능 개발에 관한 IFIP Working Group 2.8John Williams(IBM Almaden의 John Backus와의 오랜 협력자)의 노력 덕분입니다.

2.5 Refining the design

얼굴을 맞대고 처음 만난 후, email을 사용해서 상세한 언어 설계 및 개발이 이루어졌습니다. Haskell 개발에 대한 간단한 타임라인은 아래와 같습니다.

  • 1987년 9월. 오리건 주 포틀랜드 FPCA에서의 최초 회의를 진행했습니다.

  • 1990년 4월 1일. Haskell 버전 1.0 보고서는 HudakWadler에 의해 편집되고 125 페이지로 출판되었다. 동시에, Haskell 메일 링리스트가 시작되었고, 모두에게 공개되었다. Haskell에 관한 모든 논의는 공개적으로 진행되었지만 여전히 위원회에서 결정을 내렸습니다.

  • 1991년 8월 Haskell 버전 1.1 보고서는 Hudak, Peyton JonesWadler가 편집하여 출판(153 페이지) 되었습니다. John Petersonhaskell.org 도메인을 등록하고, Yale에서 서버와 웹 사이트를 개설했습니다.

  • 1996년 5월. Haskell 버전 1.3 보고서가 출판되었으며 HammondPeterson이 편집했습니다. 기술적인 변화의 관점에서, Haskell 1.3은 Haskell의 1.0 이후 가장 중요한 릴리스였습니다.

  • 1997년 4월. PetersonHammond에 의해 편집 된 Haskell 버전 1.4 보고서(139 + 73 페이지)가 출판되었습니다.

  • 1999년 2월 Haskell 98 보고서가 출판되었습니다.

  • 1999-2002 1999년 Haskell 메일 링리스트를 읽는 모든 사람이 참여할 수 있게되었습니다. 그러나 Haskell이 널리 사용됨에 따라 작은 결함이 발견되었으며 보고서의 모호함도 보고되었습니다. Peyton Jones의 역할은 BDLM(온화한 언어 독재자)가 되었습니다.

  • 2002년 12월 수정된 Haskell 98 보고서가 발간됩니다. 케임브리지 대학 출판부는이 보고서를 책으로 출판하였지만, 전체 텍스트가 온라인상에서 이용 가능하고 누구든지 자유롭게 사용할 수 있다는 것에 동의했습니다.

  • Haskell 98이 나왔을 때 Haskell은 이미 세상에 나온지 8년이 지났음에도 불구하고, Haskell 98이 처음 발표 된 후 4년이 지난 후에 언어 명세가 변경(shake down) 되었습니다. 언어 디자인은 느린 과정입니다!

2.6 Was Haskell a joke?

Haskell 보고서의 초판은 1990년 4월 1일 만우절에 출판되었습니다. 그 이후에도 만우절은 계속해서 이어졌습니다. 이 모든 것이 시작된 것은 Haskell 보고서의 편집자의 역할이 스트레스를 주는 다소 어려운 시기였습니다. Hudak은 위원회에 메세지를 보냈고, 그는 또한 음악 분야에서 경력을 쌓기 위해 Yale을 그만두기도 했습니다. David Wise는 즉각 Hudak에게 전화를 걸어 결정을 재고 해달라고 호소했습니다.

그 이후 만우절 농담이 이어졌습니다. 대부분은 Haskell 웹 사이트에 있는 haskell.org/humor에 설명되어 있으며, 여기서는 흥미로운 것들을 요약해 놓았습니다.

  1. 1993년 4월 1일, Will PartainHaskell의 좋은 아이디어와 Perl의 훌륭한 아이디어를 결합한 Haskerl라는 Haskell 확장에 대한 훌륭한 발표문을 썼습니다. 기술적으로 상세하고 진지한 문체는 그것을 믿을만한 것으로 만들었습니다.

  2. Partain의 잘 쓰여진 장난에 대한 여러 답변은 똑같이 우스꽝스러운 것으로 Hudak이 작성한 것도 있습니다. "최근 HaskellYale 의대의 실험에 사용되었습니다. 그것은 heart-lung 기계를 제어하는 C 프로그램을 대체하기 위해 사용되었습니다. C 프로그램보다 Haskell 프로그램이 훨씬 강력했기 때문에 아마도 십여명의 생명이 구했다고 병원은 추정했습니다. 이에 대해 Nikhil은 다음과 같이 썼습니다. "X-Ray 장비의 소프트웨어 결함 때문에 많은 의료 사고를 겪었습니다. 그래서, 그들은 신뢰성을 위해 Haskell에서 코드를 재작성하기로 결정했습니다. 과실은 이제 0으로 떨어졌고, 그 이유는 새로운 X-Ray 촬영을 하지 않았기 때문입니다."

  3. 1998년 4월 1일 John PetersonSun MicrosystemsJava 사용에 대해 Microsoft를 고소했기 때문에 MicrosoftHaskell을 주 소프트웨어 개발 언어로 채택하기로 결정했다는 가짜 보도 자료를 썼습니다. 역설적이게도, 이 보도 자료 이후 얼마 지나지 않아 Peyton JonesGlasgow에서 CambridgeMicrosoft Research로 옮겼습니다. 그 당시 Peterson은 당시에는 알지 못했던 사건이었습니다. Microsoft는 실제로 다른 언어를 지원함으로써 Java에 응답했지만 Haskell이 아닌 C#입니다. 그러나 C#polymorphic typesLINQ(Language Ingrated Query)는 Haskell 및 기타 함수형 언어에 많은 영향을 받았습니다. LINQ의 수석 디자이너 인 Erik MeijerLINQHaskellmonad comprehensions에 직접적으로 영향을 받았다고 말합니다.

  4. 2002년 4월 1일, Peterson은 "개발자가 재무 스캔들의 하단에 위치하다!"라는 또 다른 가짜지만 즐겁고 타당한 기사를 썼습니다. 이 기사는 Peyton JonesHaskell을 사용하여 금융 계약을 평가하는 연구(Peyton Jones et al., 2000)에서 Enron의 초라한 금융 네트워크를 해결 할 수 있었다는 내용입니다. Peyton Jones는 다음과 같이 인용합니다. "정말 간단합니다. 가치가 주가에서 비롯되고 주식 가치가 계약에 달려 있다는 계약을 쓴다면 우리는 가장 최하위가 됩니다. 그래서 결국 엔론은 궁극적으로 전혀 가치가 없는 일련의 복잡한 계약을 만들었습니다."

3. Goals, principles, and processes

이번 절에선 우리의 생각, 선택, 그리고 그것들을 이끌어 낸 과정의 중요한 기반이 되었던 원칙에 대해서 설명합니다.

3.1 Haskell is lazy

LazinessHaskell의 설계에 기여한 다양한 모임을 하나로 묶은 중요한 주제였습니다. 기술적으로 Haskellnon-strict semantics을 가진 언어입니다. lazy evaluationnon-strict한 언어를 구현하는 기법 중 하나입니다. 그럼에도 불구하고 laziness라는 용어는 non-strict 보다 명료하고, 뚜렷하기 떄문에 우리는 Haskelllaziness라 설명하였습니다. 구현 기술을 구체적으로 언급 하는 부분에선 LispML과 같은 언어의 call-by-value와 구별해서 call-by-need라는 용어를 사용할 것입니다.

80년대 중반까지 lazy functional programming에 대한 매력을 잘 이해하고 있었습니다. Hughes의 논문 <>는 lazy programming에 대한 영향력 있는 선언문에 영향을 받은 것이고, Haskell의 초기 설계와 일치했습니다(Hughes는 1984년 옥스포드 대학에서 취직했을 때 인터뷰에서 처음 발표했으며, 1989년에 출판되기 전에 비공식적으로 배포되었다(Hughes, 1989)).

Laziness는 비용(cost)이 듭니다. Call-by-need는 일반적으로 개별적으로 평가가 진행되지 않도록 지연을 요구하고 값을 겹쳐 사용해야 하는 추가 부담 때문에 call-by-value보다 효율적이진 않습니다. 해당 비용에 대한 평가는 Haskell이 설계되었을 당시에 충분히 받아들여졌습니다.

훨씬 더 중요한 문제는 숙련된 프로그래머조차도 lazy programs의 동작을 예측하는 것이 매우 어렵습니다. 이러한 이유로 Haskellseqstrict data types(SASLMiranda에서 수행 된 것처럼)과 같은 몇 가지 기능을 추가하였습니다. strict한 언어에 lazy가 적용되었습니다(Wadler et al., 1988). 결과적으로 strict/lazy에 대한 분열은 결정되어졌고 각자 다른 쪽의 가치를 인식하였습니다.

3.2 Haskell is pure

laziness의 직접적으로 말하자면 수요 중심(demand-driven) 이라 할 수 있습니다. 결과적으로 I/O에 영향을 받거나 함수 호출로 인한 side effects가 거의 불가능해졌다. 따라서 Haskell은 순수한(pure) 언어입니다. 예를 들어, 함수 fInt -> Int 유형을 가지고 있다면 f가 변수를 사용하거나 I/O를 수행하지 않는다는 것을 확신 할 수 있습니다. 간단히 말해서, f는 실제로 수학적 의미에서의 함수입니다. 몇번을 호출해도 (f 3)은 같은 값을 반환합니다.

laziness를 선택하고 나면 순수한 언어는 피할 수 없습니다. 역은 성립하지 않지만 대부분의 순수한 프로그래밍 언어는 laziness 합니다. 왜냐하면 call-by-value를 선택한 언어의 경우 side effects를 거의 허용하고 있기 때문입니다.

Purity는 큰 결정이며 거의 모든 곳에 영향을 미칩니다. side effects를 허용하는 것은 의심할 여지없이 편리함을 제공합니다. side effects가 없기 때문에, Haskell의 입출력은 처음에는 힘들고, 서툴고 당혹스럽습니다. 이러한 불편함은 궁극적으로 monadic I/O로 이어졌고 아주 중요한 기여로 간주됩니다.

궁극적으로 pure language (with monadic effects)가 프로그램을 작성하는 가장 좋은 방법인지 여부는 여전히 열려있는 근본적이고 우아한 질문이지만 강력하고 우아한 설계의 동기(motivate)로 작용합니다. 돌이켜 보면 laziness의 가장 큰 단일 장점은 laziness 그 자체가 아니라 우리를 순수하게 유지시켜서 Monadencapsulated state를 제공해서 더 많은 생산적인 작업을 하도록 동기를 부여했다는 점이라 할 수 있습니다.

3.3 Haskell has type classes

lazinessHaskell의 디자이너를 하나로 모으는 것이었지만 Haskell의 가장 분명한 특성으로 간주되는 type classes 일 것입니다. Type classes는 1988년 2월 24일 fplangc 메일 링리스트로 보내진 메시지에서 Wadler에 의해 Haskell위원회에 소개되었습니다.

초기에 타입 클래스는 수치 연산자의 오버로딩과 평등이라는 좁은 문제에 의해 동기 부여되었습니다. 이러한 문제는 MirandaSML에서 완전히 다른 방식으로 해결되었습니다.

SML은 내장 숫자 연산자에 대한 오버로드를 사용하여 호출 시점에서 해석됩니다. 이로 인해 오래된 숫자로 새로운 숫자 연산을 정의하는 것이 어려워졌습니다. 예를 들어, 곱셈의 측면에서 정사각형을 정의하고 싶다면, 정수 및 부동 소수점과 같이 각 숫자 유형마다 다른 버전을 정의해야합니다. Miranda는 정수라고 불리는 단일 숫자 유형 (num이라고 불림)을 피함으로써이 문제를 피했습니다. 정수는 무한 크기의 정수와 배정 밀도 수를 결합한 것으로, 필요할 때 int를 자동으로 float로 변환합니다. 이것은 편리하고 유연하지만 정적 타이핑의 몇 가지 장점을 희생합니다. 예를 들어 Miranda에서는 대부분의 언어에서 모듈러스 연산자 mod가 정수 모듈에 대해서만 의미가 있지만 표현식 (mod 8 3.4)은 유형에 맞습니다.

또한 SML은 원래 오버로딩을 사용하여 동등 함을 나타 내기 때문에 목록 및 값을 취한 다형 함수를 정의 할 수 없으며 값이 목록의 일부 요소와 동일하면 true로 되돌립니다. (이 함수를 정의하기 위해, 추가 인자로서 equality-testing 함수를 사용해야 할 것입니다.) Miranda는 단순히 다형성 타입을 평등하게했지만 함수 타입에 대해 평등성을 정의했습니다 (런타임에 오류가 발생했습니다). (추상화 장벽을 침해하는 등 평등성에 대한 기본 표현을 비교). 최신 버전의 SML에는 다형성 평등이 포함되었지만 동등성이 정의 된 유형 (즉, 함수 유형 또는 추상 유형이 아닌)에 대해서만 범위가 지정된 특수 "동등 유형 변수"(a 대신 a로 작성)가 도입되었습니다.

유형 클래스는 이러한 두 가지 문제에 대해 통일 된 해결책을 제공했습니다. 그들은 SML에서 평등 유형 변수의 개념을 일반화하여 주어진 연산 집합 (예 : 수치 연산 또는 평등)을 가진 유형의 "클래스"개념을 도입했습니다.

타입 클래스 솔루션은 우리에게 매력적이었습니다. 왜냐하면 어떤 대안보다도 더 원칙적이고 체계적이며 모듈화 된 것처럼 보였기 때문입니다. 그것의 다소 과격하지만 검증되지 않은 성격에도 불구하고, 그것은 찬사에 의해 채택되었습니다. 우리는 우리가 무엇을 위해 스스로를 기다리고 있는지 알지 못했습니다!

Wadler는 Haskell 회의 중 한 후에 Joe Fasel과 대화하면서 형식 수업을 생각했습니다. Fasel은 다른 생각을 염두에 뒀지 만 과부하가 기능의 유형에 반영되어야한다는 핵심 통찰력을 가진 사람이었습니다. WadlerFasel이 염두에두고있는 것을 오해했고, 타입 클래스가 탄생했습니다! 와들러의 학생 Steven Blott은 형식 규칙을 공식화하는 데 도움을 주었으며, 박사 학위 논문에 대해 시스템이 완전하고 완전하며 일관성이 있음을 입증했습니다(Wadler and Blott, 1989; Blott, 1991). 비슷한 생각이 Stefan Kaes(Kaes, 1988)에 의해 독립적으로 형성되었다.

우리는 6 절에서 유형 - 클래스 접근법의 세부 사항과 결과 중 일부에 대해 자세히 설명한다. 한편, Haskell 언어의 근본적이고 광범위한 측면의 다소 우발적 인 성격을 반영하는 것은 유익하다. WadlerBlott가 언어 디자인이 여전히 유동적 인 순간에이 핵심 아이디어를 산출 한 것은 타이밍의 행복한 우연이었습니다. 우리는 시도되고 검증 된 합의를 구현하는 암묵적인 목표에 직접적으로 모순되는 가운데, 거의 논의하지 않고 채택되었습니다. 그것은 처음부터 그것을 채택하는 우리의 초기 이유를 극적으로 초과하는 광범위한 결과를 낳았습니다.

3.4 Haskell has no formal semantics

우리의 명확한 목표 중 하나는 정식으로 정의된(formally defined) type system과 의미론(semantics)을 가진 언어를 만드는 것이 었습니다. 우리는 프로그래밍 언어 설계에서 수학적인 방법을 도입하는 것에 많은 관심이 있었습니다. 우리는 ML로부터 많은 영감을 받았습니다. ML은 언어에 대한 완전한 formal definition을 할 수 있음을 보여 주었고, Standard ML(Milner and Tofte, 1990; Milner et al., 1997)이 그 영광의 자리를 차지했습니다.

그럼에도 불구하고 우리는 이 목표를 달성하지 못했습니다. Haskell Report는 일반적인 언어 정의의 전통을 따르고 있습니다. 언어의 일부(예를 들자면 패턴 매칭)는 작은 "핵심 언어"로 번역이 되었지만, 다른 것은 정식으로 번역되지 않았습니다. 후속 논문은 Haskell의 좋은 부분, 특히 Type System(Faxen, 2002)을 기술하고 있지만, 모든 것을 설명하는 문서는 없습니다. Haskell위원회의 의식적인 선택 때문이 아닙니다. 그것이 가장 절박한 일이 아닌 것 같습니다. 아무도 그 일을 맡지 않았고, 실제로 언어 사용자와 구현자는 그것 없이도 완벽하게 언어를 관리하는 것처럼 보였습니다.

실제로 Haskell의 정적 의미론(즉, 유형 시스템의 의미론)에 복잡성의 대부분이 몰려 있습니다. 형식적인 정적 의미를 가지지 않는 결과는 컴파일러 작성자에게 매우 난감한 일이며 때로는 컴파일러간의 차이도 발생합니다. 그러나 사용자가 프로그램 유형을 검사하면 정적 의미론에 대한 염려가 거의 없으며 정적 의미론에 대해 공식적인 문서가 거의 필요하지 않습니다.

다행히 Haskell의 동적 의미는 비교적 간단합니다. Haskell의 설계 과정에서 우리는 Haskell의 의미가 정식으로 쓰이지 않아도, 우리 모두가 Haskell의 의미가 무엇인지 알았듯이, 설계와 관련된 다양한 논의를 하기 위해 의미론적 의미에 의지했습니다. 이러한 추론은 "Bottom"(오류 또는 비-종결을 나타내며 패턴 매칭, 함수 호출, 재귀적으로 정의 된 값 등이 Lazy 언어에서 빈번하게 발생)에 대한 추론에 특히 유용합니다.

더 중요한 것은 Haskellthe dynamic semanticsHaskell의 Pure한 특징 덕분에 formal denotational 또는 operational semantics보다 적용하기 훨씬 쉬운 equational reasoning을 제공합니다. equational reasoning에 대한 이론적 근거는 lambda calculus (β-and η-reduction)primitive operations (so-called δ-rules)에서 파생(derives)됩니다. 유도(및 공동 유도(co-induction)) 원리와 결합된 강력한 추론 방법입니다. Haskell에서의 equational reasoning은 문화의 일부이며 모든 Haskell 프로그래머가 받는 교육의 일부입니다. 결과적으로 formally specified semantics가 부족함에도 불구하고 Haskell에서 다른 언어보다 correctness propertiesprogram transformations에 대한 더 많은 증거가 있습니다. 그러한 증명은 대개 Haskellη-reduction과 같은 사용 된 기본 단계 중 일부가 실제로 공식적인 의미를 보존하지 않는다는 사실을 무시하지만 (적절한 상황에서) 올바른 조건에선 유효합니다(Danielsson et al., 2006)!

3.5 Haskell is a committee language

Haskell은 위원회에서 설계한 한 언어이며, 일반적인 통념상 위원회에서 설계한 언어는 어색한 타협으로 가득 차 있다고 말합니다. Tony Hoare가 Haskell 위원회에 보낸 기억에 남는 편지에서는 Haskell이 "성공할 운명"이라고 단호하게 말했습니다.

그러나 앞선 결점 때문에 Haskell은 종종 "아름다운" 또는 "우아한" 심지어는 "쿨"한 것으로 묘사됩니다. 이것은 보통 위원회 설계와 거의 관련 없는 말입니다. 어떻게 이런 일이 생겼을까요? 이 질문에 대해 우리는 다음과 같은 몇 가지 요인을 확인했습니다.

  • 초기 상황이 매우 호의적이였습니다. 우리 모두 Haskell이 필요했습니다.

  • 수학적 우아하함을 추구했고 이러한 결정으로 언어 기능이 복잡해지는 것을 확실하게 막았습니다.

  • 이메일을 사용한 회의뿐만 아니라 대면회의를 사용해서 의사소통을 진행했습니다.

  • 설계 과정에서 매 순간마다 한 두명 위원이 The Editor의 역할을 수행고, 책임과 권한을 올바르게 수행했습니다. 그는 보고서의 관리인이었으며 결론을 구체화 할 책임이 있었습니다.

  • 설계 과정에서 매 순간에 위원회의 한 구성원(반드시 편집자일 필요는 없음)이 Syntax Czar 역할을 했습니다. Czar는 syntactic 문제들에 대해서만 단호한 결정을 할 수 있는 권한을 부여 받았습니다. 모든 사람들은 구문을 논의하는 데 너무 많은 시간을 할애한다고 말지만 많은 사람들이 람다에 대한 선호하는 기호로 끝나지 않는 싸움이 될 것입니다. Syntax Czar는 그러한 논쟁을 끝내기 위한 우리만의 방식이었습니다.

3.6 Haskell is a big language

위원회의 중요한 논쟁 주제는 효율성과 구조적 합리성이었습니다. 우리는 간단하고 우아한 언어를 설계하고 싶었습니다.

"소프트웨어 설계를 구성하는 데에는 두 가지 방법이 있다. 한가지 방법은 아주 단순하게 만들어서 명백히 결함이 없게 된다. 그리고 다른 방법은 너무 복잡하게 만들어서 명백한 결함이 없게 된다"(C.A.R. Hoare)

한편, 우리는 Haskell이 교육 및 실제 응용 프로그램 모두에 유용한 언어가 되기를 원했습니다.

Richard Bird는 1988년에 위원회를 사임하면서 그는 "fplang에 제출 된 많은 자료와 의견에 대한 기록에서 단순성, 증명의 용이성, 우아함의 원칙이 무너질 것 같은 심각한 위험이 있다. 제안 된 것 중 많은 부분이 부족하고, 역행적이고, 괴상하기 때문에 결과가 엉망이 될 가능성이 큽니다. 우리는 Lisp(10년 넘게 pursuit of functional programming의 발전을 저해한 언어)와 관련없는 곳으로 되돌아 가야한다. 우리는 규모가 커질 때 작은 프로그램을 위한 '미학적' 구조가 매력을 잃어 버리기 때문에 '큰' 프로그램을 설계하도록 촉구한다. 깊게 중첩 된 구조를 가진 큰 where 절을 허용 할 것을 촉구한다. 간단히 말해서, 기존 프로그래밍과 구별되는 functional programming의 한 가지 특징을 버리면 21세기의 생존을 보장 할 수 있습니다"

결국,위원회는 복잡성과 관련된 문제를 진지하게 받아들였습니다. 예를 들어, 모순된 표현방법, 복잡한 클래스 선언 등을 수정하였습니다. 우리는 purely functional programming의 우아한 핵심을 손상하지 않고 살아남았다고 생각합니다. 복잡성과 관련된 타협이 이루어진 분야는 monomorphism 제한과 seq로 인한 parametricity, curryingsurprive pairing의 손실이라 할 수 있습니다.

3.7 Haskell and Haskell 98

연구를 위해 Haskell을 사용할 땐 안정적으로 동작하고 몇가지 부분은 개선을 필요로 합니다. Haskell 보고서의 각 버전의 취지는 다음과 같이 말합니다.

"위원회는 Haskell이 언어 설계의 미래 연구의 기초가 될 것으로 기대하고 있습니다. 우리는 언어의 확장과 변형이 실험적인 특징을 반영할 수 있기를 바라고 있습니다."

그러나 Haskell의 인기가 높아짐에 따라 언어의 설계 변경이나, 우리의 계획이 무엇인지에 관한 질문이 나오기 시작했습니다. 아래가 대표적인 예라고 할 수 있습니다.

"나는 Haskell에 대한 책을 쓰려고 생각하고 있습니다. 그런데, 언어가 변화하는 경우 책을 쓸 수 없습니다."

위원회는 이러한 요청에 대응하여 간단하고 이해하기 쉬운 해결책을 제시했습니다. Haskell 98이라는 특정한 버전을 지정하고 Haskell 98을 무기한 지원할 것을 약속했습니다. 우리는 Haskell 98을 합리적이며 보수적인 설계를 유지하기로 하였습니다. 예를 들면, 당시에 multi-parameter type classes가 널리 사용되고 있었지만, Haskell 98single-parameter type classes만 지원했습니다(Peyton Jones et al., 1997).

Haskell 98(비공식) 표준화는 또 다른 이유를 위해 중요한 전환점이었습니다. Haskell 위원회가 해산 한 순간이었습니다. Haskell 커뮤니티에는 언어 기능을 위한 수많은 제안을 포함한 방대한 양의 혁신 활동이 있었습니다(그리고 앞으로도 계속 될 예정입니다). 그러나 위원회는 특별한 선택을 하는 것이 아니라 천개의 꽃이 피고, 어떤 생물이 살아남는지 지켜봐야 한다고 결정했습니다. 위원회의 작업을 종료하고 거대한 메일 저장소를 안전하게 저장하였습니다.

우리는 Haskell 98 이외의 Haskell의 변형을 저지하려는 시도를 하지 않았습니다. 오히려 우리는 명시적으로 언어의 발전을 장려했습니다. 이 명명법은 "Haskell 98"은 안정된 언어의 변형이며 자유 분방한 종류의 모든 구현체를 "Haskell"이라고 말할 수 있도록 장려하고 있습니다.
위원회가 존재하지 않기 때문에 Haskell은 서로 다른 방식으로 발전하고 있습니다.

  • Haskell은 수천 명의 사용자와 성숙한 언어로되어 있기 때문에, 현실의 언어가 직면하는 규모와 복잡성 문제를 해결해야 합니다. 따라서 외부 기능을 도입하기 위한 라이브러리 인터페이스, 풍부한 컬렉션, 동시성 예외 등 실질적으로 지향 된 기능이나 자원의 범위가 확대되고 있습니다.
  • 동시에 언어는 특히 타입 시스템과 메타 프로그래밍 분야에서 고급 언어 디자인 아이디어를 탐구하기 위한 매우 효과적인 실험실 역할을 하고 있습니다. 이러한 아이디어는 Haskell을 기본 언어로 한 연구 논문의 수와 Haskell 구현 논문 모두에 나타나고 있습니다.

Haskell이 지금까지 개발의 두 가닥 사이의 긴장을 잘 관리해 온 것은 우연의 미덕 때문입니다. Haskell은 그다지 성공하지 않습니다. Java와 같은 급속한 성공은 많은 사용자를 획득할 수 있으며, 동시에 표준 사용자 그룹 및 언어의 명세가 잘못 되 ㄹ수 있다는 점이고, 대조적으로 Haskell 커뮤니티는 민첩하게 변화를 흡수하고 있습니다.

3.8 Haskell and Miranda

Haskell이 태어난 시점에서 가장 성숙하고 널리 사용되고있는 non-strict functional language는 Miranda였습니다. Miranda는 1983년에 설립 된 David TurnerResearch Software Limited에서 개발 되었고, Hindley-Milner(Milner, 1978)에 의한 lazy functional programming을 상용 도메인에 도입하는 것을 목표로 하였습니다. 1985년에 처음 출시 되고 1987년과 1989년에 개선된 Miranda는 뛰어난 구현, REPL, 다양한 textbook(대표적인 것은 Bird and Wadler 1988)을 가지고 있었습니다. 학술 라이센스 및 상용 라이센스가 거론되었습니다. 1990년대 초 250개 대학과 20개국의 50개 회사에 Miranda가 도입되었습니다.

따라서 Haskell의 디자인은 Miranda의 영향을 강하게 받았습니다. 당시 MirandaHindley-Milner형 시스템과 algebraic data types을 가진 non-strict, purely functional language를 표방했습니다. 그것은 바로 Haskell이 기대했던 언어의 종류였습니다. 그 결과, 기본적인 접근(purity, higher order, laziness, static typing)과 구문적인 모양과 관련되어 두 언어 사이에 많은 유사점이 있습니다. the equational style of function definitions, pattern matching, guards, where clauses, algebraic types, the notation for lists and list comprehensions, ML의 int*bool형태가 아닌 (num,bool) 형식, capitalisation of data constructors, lexically distinguished user-defined infix operators, the use of a layout rule, 그리고 the naming of many standard functions등이 대표적인 예라 할 수 있습니다.

Miranda와 현저한 차이가 있습니다. 예를 들어, garud 위치, 표현식, 데이터 타입 선언 구문이 다릅니다. 타입 생성자와 데이터 생성자, 변수에 숫자 식별자를 사용 가능하다. 사용자 정의 연산자의 구별(x $op yx 'op' y), 레이아웃 규칙도 차이를 보입니다. 근본적으론 Haskell은 모듈 시스템을 대신 사용하여 추상 데이터 타입을 채용하지 않았습니다. 모나드 I/O가 추가되었습니다. Hindley-Milner type system, 특히 type classes에서 차이를 보입니다.

오늘 Miranda는 주로 Haskell로 대체했습니다. Haskell 책은 정기적으로 등장하고 있습니다만, Miranda의 교과서는 1995년에 출판되었습니다. Research Software Limited는 학술 라이센스를 상용 라이센스보다 저렴하게 제공했다. 모두 무료는 아니었지만, Haskell은 대학 그룹에 의해 제작되고 있으며, 학술 및 상업 이용자도 자유롭게 이용할 수 있다. 또한 MirandaUnix에서만 작동하고 Windows 버전이 존재하지 않았다.

Miranda는 뛰어난 구현을 제공했지만, Haskell의 구현은 보다 신속하게 개선되었습니다. 작은 회사가 따라 잡기는 어려웠습니다. HugsMiranda에 제공된 REPL을 Haskell에게 주었습니다. 무어의 법칙은 Haskell의 느린 컴파일러를 개선했습니다. 이 논문에서는 Haskell이 중요한 새로운 아이디어를 가지고 있었다고 말합니다. 1990년대 중반까지 HaskellMiranda 실제 프로그래밍을 위한 훨씬 더 현실적인 선택이었다.

Miranda의 독점적 지위는 학계에서 보편적 인지도를 얻고 있지 못했습니다. Turner는 상표를 보호하기 Research Software Limited를 항상 서류에 기록했습니다. 이에 대응하여 몇 가지 초기 Haskell의 출판물에는 "Haskell은 상표가 없습니다"라는 각주가 포함되어 있었습니다. 당시 미란다의 라이센스 조건은 미란다의 구현을 배포하기 전에 라이센스 소유자에게 허락을 받아야했습니다. 하지만 Turner는 이러한 부채에 대해서 항의를 하지 않았습니다.

"혹시"라는 가정을 해보게 됩니다. TurnerMiranda를 공개했다면 어떻게 되었을까요? 80년대 중반에는 연구 커뮤니티와 그 지원을 받고있는 기업이 지원하는 lazy functional language를 기대해 볼 수 있었을까요? 비즈니스 모델을 발견 할 수 있었습니까? 역사는 어떻게 달라졌을까요?

Miranda는 물론 상업적, 연구용으로 실패하지 않았습니다. 많은 대학에서 채택된 잘 구현된 작고 우아한 언어로 functional language 보급을 촉직하는 것을 도왔습니다. 학문 분야를 넘어 여러 대규모 프로젝트(Major and Turcotte 1991; Page and Moe, 1993)에서 Miranda의 사용은 lazy functional language의 가능성을 보여주었습니다. Miranda는 오늘날에도 여전히 사용되고 있습니다. 아직도 일부 기관에서 진행하고 있으며, LinuxSolaris 구현체는 계속 제공되고 있습니다. Turner의 노력은 몇 년 후 Haskell의 일반적인 주제에 대한 관심과 발전에 지속적이고 가치있는 기여를 했습니다.

4. Syntax

프로그래밍 언어와 관련된 토론에서 "syntax는 중요하지 않습니다"라는 말을 종종 들을 수 있습니다. 1980년대 저 문구는 오늘날보다 자주 거론되었는데 프로그래밍 언어의 semantics를 강조했기 때문입니다. 많은 프로그래밍 언어 연구자들은 syntax를 설계의 사소한 부분으로 간주하고 semantics에 중점을 두었습니다. 그럼에도 불구하고 위원회는 Haskell의 syntax를 고민하고 토론하였습니다. 더 좋든 더 나쁘든간에 구문 설계는 재미뿐만 아니라 하나의 집착이었습니다. 언어의 사용자 인터페이스인 syntax는 개인적인 형태로 구성 될 수 있음을 발견했습니다. 우리가 가장 열렬히 논한 논쟁 중 일부는 semantics가 아니라 syntax를 발전시키는 것이었습니다. 결국에는, Haskell의 syntax는 우아하다는 평가를 몇번이나 들었습니다. 어떤 부분, 어떤 점에서 그러한 평가를 듣게 되는지 syntax 언어 기능에 대한 역사적 관점을 소개하고자 합니다. 세부적인 사항은 Hudak의 기사를 참고하세요(Hudak, 1989).

4.1 Layout

대부분의 imperative(명령형) 언어는 세미콜론을 사용하여 명령을 구분합니다. 그러나 부작용이 없는 언어에서는 순서 개념이 존재하지 않습니다. 여러 종류의 선언을 분리할 필요는 있지만, 세미콜론과 순서는 피해야 했습니다. 프로그램 코드를 물리적으로 제한하는 것은 구문의 혼란 및 악용을 방지하기 위한 간단하고 우아한 방법입니다. "off-side rule"은 TurnerSASL(Turner, 1976)과 Miranda(Turner, 1986)에서 소개되었고, 이 아이디어는 Christopher StracheyCPL(Barron et. al., 1963), ISWIM(Landin, 1966)에서도 소개되었습니다.

sd: "off-side rule"과 관련된 가장 명쾌한 예제를 떠올려야 한다면 Python을 생각하시면 됩니다.

def is_even(a):
    if a % 2 == 0:
        print('Even!')
        return True
    print('Odd!')
    return False

레이아웃 규칙은 매우 단순할 필요가 있었지만, 그렇지 않은 경우 사용자는 반대했고 많은 변형을 탐구했습니다. 작고 짧은 함수를 작성하는데 좋은 프로그래밍 스타일이 필요하다고 생각했지만 실제로 프로그래머는 상당히 큰 함수 정의를 작성하기를 원했습니다. 레이아웃이 길어지면 가독성에 좋지 않습니다. 따라서 Haskell의 레이아웃 규칙은 이 점에서 Miranda의 레이아웃 규칙보다 관대합니다. Miranda와 마찬가지로 우리는 사용자가 명시적 중괄호와 세미콜론을 사용하여 암시적 레이아웃을 선택적으로 무시할 수 있는 방법을 제공했습니다. 우리가 이것을 중요하다고 생각한 한 가지 이유는 사람들이 Haskell 프로그램을 작성해야 한다고 생각했기 때문에 레이아웃을 보다 명확한 구분 기호로 표현하는 것이 더 쉽다고 생각했습니다. 이러한 제한과 "프로그래머가 기대하는 바를 수행(do what the programmer expects)"하고자 하는 욕구의 영향을 받아 Haskell은 레이아웃 규칙을 발전시켰습니다. Haskell 98 보고서에서 공식적으로 지정되었습니다. 그러나 대부분의 사용자는 레이아웃 규칙에 속하는 프로그래밍 스타일을 사용하거나 개인적인 형태의 스타일을 사용할 수 있습니다.

4.2 Functions and function application

Haskell은 functional language 이기 때문에 함수를 정의하는 방법은 다양하며 간단하고 합리적인 방법으로 가능합니다.

  • Currying, 두 개의 인수를 전달받는 함수는 하나의 인수를 전달받는 함수로 표현 될 수 있습니다. 이 전통은 Moses SchonfinkelHaskell Curry에 의해 커링(currying)이라고 불리게 되었습니다. Function application은 여러개를 표현하며 왼쪽으로 연결됩니다. 따라서 f x y(f x) y로 분석됩니다. 이것은 간결하고 강력한 코드로 이어집니다. 예를 들어 목록의 각 숫자를 제곱하려면 square square [1,2,3]를 쓰고 목록의 각 숫자를 제곱하려면 map (map square) [[1,2], [3]]을 사용하면 됩니다. Haskell은 람다 계산(lambda calculus)에 기초한 다른 많은 언어와 마찬가지로 currieduncurried 모두 지원합니다.
hyp :: Float -> Float -> Float
hyp x y = sqrt (x*x + y*y)

hyp :: (Float, Float) -> Float
hyp (x,y) = sqrt (x*x + y*y)
  • Anonymous functions, 백 슬래시가 그리스 문자 λ에 가장 가까운 단일 ASCII 문자 였기 때문에 익명 함수인 \x -> exp에 대한 구문이 람다식과 비슷하게 선택되었습니다. 그러나 최종적으로 ->가 사용되었습니다.

  • Prefix operators, Haskell은 단 하나의 접두 연산자 즉 산술 부정을 가지고 있습니다. 사실 위원회는 접두어 연산자를 원하지 않았지만 사용자들이 더 일반적인 -42에 대해 minus 42 또는 ~42와 같은 것을 쓰도록 강요 할 수는 없었습니다. 그럼에도 불구하고 접두어 연산자의 갯수가 작기 때문에 독자가 표현식을 쉽게 파싱 할 수 있습니다.

  • Infix operators, Haskell은 가능한 수학식과 비슷하게 보이기를 원했기 때문에 Haskell이 중위 연산자를 도입해야 한다는 아이디어를 얻었습니다. 또한 중위 연산자가 우선 순위 및 연관성 선언을 포함하여 사용자가 정의 할 수 있는 것이 중요했습니다. 이 모든 것은 보편적인 요구사항이었지만, 우리는 또한 중위 어플리케이션과 기존 함수 어플리케이션 사이에 다음의 간단한 관계를 정의했다.

전자는 후자보다 항상 엄격하게 결합한다(the former always binds less tightly than the latter).

따라서 f x + g y는 중위 연산자가 사용되는 것과 상관없이 괄호가 필요하지 않습니다. 이 디자인 결정은 프로그램의 가독성을 높이기 때문에 좋은 결정이었습니다.

  • Sections, 중위 연산자에 대한 논의가 시작되었지만 Haskell의 모든 값은 first class여야 한다는 논의가 있었습니다. 그래서 중위 연산자가 그 자체로 first class라는 사실에 대한 상당한 우려가 있었습니다. 이 문제는 f + x라는 표현을 고려함으로써 명백해지는 문제입니다. 이것은 함수 f가 두 개의 인수에 적용되거나 + 함수가 두 개의 인수에 적용될 수 있습니다.
(+) =\x y-> x+y
(x+) = \y -> x+y
(+y) = \x -> x+y

우리가 섹션을 가지고 있고 특히 중위 연산자를 일반 함수 값으로 변환하는 방법을 찾았다면 우리는 왜 우리가 다른 방향으로 갈 수 없었는지 스스로에게 물었습니다. 일반 함수를 중위 연산자로 변환하는 간단한 해결책은 기능 식별자를 역인용 부호로 묶는 것이 었습니다. 예를 들어 x 'f' yf x y와 같습니다. 우리는 중위 연산자로 "단어"를 사용할 수있는 능력뿐만 아니라 이것이 제공하는 일반성을 좋아했습니다. 예를 들어, 우리는 elem x xs 대신 x 'elem' xs로 쓰여졌을 때 리스트 멤버쉽이 더 읽기 쉽다고 느꼈습니다.

sd: 이렇게 연산자를 앞으로 옮길 수 있게 되면 피 연산자 하나와 괄호를 엮을 수 있습니다. (x+) 또는 (+x) 처럼요. 이렇게 피연산와 연산자가 괄호로 묶인 것을 section이라 부릅니다. x + y 를 이용하면 두 개의 섹션을 만들 수 있습니다. (x+)(+y) 입니다. section은 언제 유용할까요? 우리는 section을 이용하면 람다를 만들어 주는 대신에 좀 더 의미있는 부분 함수(partiall applied function)를 만들 수 있습니다. 이외에도 섹션은 연산자의 타입을 기술하거나 연산자가 다른 함수의 인자로 들어갈때 필요합니다.

4.3 Namespaces and keywords

네임스페이스는 위원회에서 상당한 토론의 포인트였습니다. 우리는 사용자가 모호한 형태를 피하면서 최대한 많은 자유 누릴 수 있기를 원했기 때문입니다. 그래서 우리는 각각의 네임스페이스에 대한 어휘 집합을 주의 깊게 정의했습니다. 직교성의 예로 normal variables, infix operators, normal data constructors 및 infix data constructors를 상호 배타적인 것으로 정의했습니다. overlap(중첩)의 예로서 대문자로 된 이름은 동일한 어휘 범위에서 형식 생성자, 데이터 생성자 및 모듈을 참조 할 수 있습니다. 예를 들어 다음과 같이 하나의 생성자 데이터 유형을 선언하는 것이 일반적입니다.

-- Vector는 데이터 유형의 이름이고 그 타입의 단일 데이터 생성자
data Vector = Vector Float Float 

마지막 코멘트로서, 위원회의 변수의 섀도잉이 허용되어서는 안된다고 주장했습니다. 왜냐하면 섀도잉을 사용하면 실수로 외부 범위에 바인딩 된 변수를 캡처 할 수 있기 때문이다. 그러나 결국 Haskell은 섀도잉을 허용했습니다. Haskell은 값이나 타입의 이름으로 사용할 수 없는 21개의 예약어를 가지고 있습니다. 이것은 상대적으로 적은 숫자입니다 (Erlang은 28 개, OCaml은 48 개, Java는 50 개, C ++은 63 개, Miranda는 10 개뿐입니다). 유용한 변수 이름이 될 수 있는 키워드 (예 : "as")는 피하기 위해 노력했습니다.

sd: 내부에 있는 x는 바깥쪽 x를 숨겨지거나 새도우(shadowing)한다고 합니다. 같은 이름을 갖고 있지만 다른 타입과 다른 값이다.

4.4 Declaration style vs. expression style

토론이 진행됨에 따라 두가지 functional 프로그램을 작성할 수 있는 스타일은 "선언 스타일(declaration style)"과 "표현 스타일(expression style)"이 있습니다.

filter :: (a -> Bool) -> [a] -> [a]

-- Declaration style
filter p [] = []
filter p (x:xs) | p x = x : rest
                | otherwise = rest
                where
                  rest = filter p xs

-- Expression style
filter = \p -> \xs ->
          case xs of
          [] -> []
          (x:xs) -> let rest = filter p xs
          in if (p x) then x : rest
            else rest

선언 스타일은 가능하면 여러 방정식에 의해 함수를 정의하려고 시도합니다. 각 방정식은 패턴 매칭 및 가드를 사용합니다. 대조적으로, 표현형에서는 더 큰 표현식을 만들기 위해 표현식을 구성함으로써 함수가 구축됩니다. 각 스타일은 일련의 구문 구조로 특징 지어집니다.

Declaration style Expression-style
where clause let expression
Function arguments on left hand side Lambda abstraction
Pattern matching in function definitions case expression
Guards on function definitions if expression

선언 스타일은 Turner의 언어인 KRCMiranda에서 크게 강조되었습니다. 표현식 스타일은 Lisp, MLScheme과 같은 다른 기능 언어에서 많이 사용됩니다.

우리가 여기서했던 것처럼 문체 선택을 확인하는데 어느 정도 시간이 걸렸지만 그 후엔 "더 나은"것에 대해 격렬한 논쟁을 벌였습니다. 결국 우리는 기본 가정을 포기하고 두 스타일 모두에 대한 완전한 구문 지원을 제공했습니다.

이것은 당연한 결정처럼 보일지 모르지만, 현재 저자들이 믿는 것은 훌륭한 선택이며, 이제 우리는 언어의 힘이라고 생각합니다. 다른 구조는 서로 다른 뉘앙스를 가지고 있습니다. 실제 프로그래머는 실제로 동일한 프로그램 에서뿐만 아니라 동일한 함수 정의에서 let과 where, guard와 conditional, 패턴 일치 정의 및 case 표현식을 모두 사용합니다. 추가적인 sugar syntax가 언어를 좀 더 정교하게 보이게하는 것은 사실이지만, 단순한 구문론적 변형으로 쉽게 설명 할 수 있는 복잡성의 표면적 종류입니다.

두 가지 작지만 중요한 문제는 guard에 관한 것입니다. 첫째, Miranda는 방정식의 맨 오른쪽에 guard를 배치 했으므로 수학에 사용된 공통 표기법을 대표하므로 다음과 같이 표현됩니다.

gcd x y = x,              // if x = y
        = gcd (x-y) y,    // if x > y
        = gcd x (y-x),    // otherwise

그러나 레이아웃 논의에서 앞서 언급했듯이 위원회는 프로그래머가 짧은 함수 정의를 작성해야한다는 생각에 사로 잡히지 않았으며 긴 정의의 맨 오른쪽에 guard을 배치하는 것은 나쁜 생각이라 판단했습니다. 그래서 우리는 그들을 정의의 왼쪽으로 이동 시켰습니다. 이것은 형식 매개 변수 (논리적으로 더 의미가 있음)의 패턴 바로 옆에 보호 장치를 배치하고 평가 순서를 암시하는 장소를 마련하십시오 (올바른 운영 직관력을 구축합니다). 이 때문에 우리는 기존의 수학 표기법보다 개선 된 디자인으로 설계를 보았습니다.

둘째, HaskellMiranda로부터 where 절이 표현식이 아닌 선언에 첨부되고 선언문의 오른쪽뿐만 아니라 guard를 대상으로 한다는 아이디어를 채택했습니다. 예를 들어, Haskell에서 다음과 같이 작성할 수 있습니다.

firstSat :: (a->Bool) -> [a] -> Maybe a
firstSat p xs | null xps = Nothing
              | otherwise = Just xp
              where
                xps = filter p xs
                xp = head xps

여기서 xps는 guard뿐만 아니라 XP 바인딩에 사용됩니다. 반대로 let 바인딩은이 하위 섹션의 시작 부분 근처에있는 filter의 두 번째 정의에서 볼 수있는 것처럼 표현식에 첨부됩니다. xp는 두 번째 절에서만 정의됩니다.

4.5 List comprehensions

List comprehensions은 map, filter를 편리하게 사용할 수 있는 방법을 제공합니다.

concatMap :: (a -> [b]) -> [a] -> [b] 
concatMap f xs = [ y | x <xs, y <f x ]

리스트 xs의 각 요소에 함수 f를 적용하고 결과리스트를 연결합니다. xs에서 선택된 각 요소 x는 두 번째 생성자에 대한 새 목록 (f x)을 생성하는데 사용됩니다.

List comprehensionsJohn Burstall의 학생이었을 때 John Darlington이 처음 제안했습니다. 표기법은 David TurnerKRC에서 "ZF expression"(Zermelo-Fraenkel 집합 이론에 따라 명명 됨)이라고 불렀던 것에 의해 널리 보급(generalised to lazy lists)되었습니다. Turner는 이 표기법을 그의 논문 "The semantic elegance of applicative languages"(Turner, 1981)에서 효과적으로 사용했습니다. Wadler는 그의 논문 "How to replace failure by a list of successes"(Wadler, 1985)에 "list comprehension"이라는 이름을 소개했습니다.

어떤 이유인지, List comprehensions은 지연 연산을 지원하는 언어에서 더 많이 보입니다. 예를 들어 MirandaHaskell에서는 발견되지만 SML이나 Scheme에서는 발견되지 않습니다. 그러나 Erlang에 존재하며 더 최근에는 Python에 추가되었으며 배열 내재로서 Javascript에 추가 할 계획이 있습니다.

5. Data type and pattern matching

Data typespattern matchingScheme을 제외하곤 대부분의 현대적인 functional languages에 기본으로 포함되었습니다. 기본적인 대수 유형(algebraic types)을 포함하는 것은 간단했지만 pattern matching, abstract types, tuples, new types, records, n+k patternsviews는 관심있는 주제였습니다.

functional programs을 algebraic types을 사용해서 pattern matching과 대수식을 사용해서 작성하는 스타일에 관한 구조 연구는 Burstall(Burstall, 1969)에 의해서 진행되었고, 프로그램 변형에 대한 연구는 Darlington 연구(Burstall and Darlington, 1977)가 진행되었습니다.

Algebraic types은 Burstall의 NPL(Burstall, 1977)과 Burstall, MacQueen, Sannella's Hope (Burstall 등, 1980)에 처음 등장했다. 초창기의 ML(Gordon et al., 1979)과 KRC(Turner, 1982)에서 빠졌지만 그들의 후기의 ML(Milner et al., 1997)과 Miranda(Turner, 1986)에 사용되었습니다. 조건부 guard가 있는 대수식은 KRC의 Turner(Turner, 1982)에 의해 소개되었습니다.

5.1 Algebraic types

다음은 algebraic types의 간단한 선언과 Haskellalgebraic types 형식의 기본 기능을 설명하는 함수입니다.

data Maybe a = Nothing | Just a

mapMaybe :: (a->b) -> Maybe a -> Maybe b
mapMaybe f (Just x) = Just (f x)
mapMaybe f Nothing = Nothing

Maybe 형식은 Nothing이나 (Just a) 값을 가집니다. 생성자는 pattern matching을 사용해서 Maybe 유형의 값을 결정하는데 사용할 수 있습니다.

algebraic types은 가독성을 크게 향상 시킵니다.

data Tree a = Leaf a | Branch (Tree a) (Tree a)

size :: Tree a -> Int
size (Leaf x) = 1
size (Branch t u) = size t + size u + 1

Haskell은 '곱의 합(sum of products)'으로 표기하였습니다.

일반적으로 algebraic type은 하나 이상의 값의 합을 지정합니다. 개별 값은 0개 이상의 필드값의 결과입니다. 완전히 비어있는 유형이 될 수 있는 대안의 합을 허용하는 것이 유용했을 수도 있지만, 그런 type은 인정하지 않습니다.

Haskell의 생성자 이름이 항상 대문자로 시작한다는 규칙 때문에 변수(예 : x, t 및 u)를 구문에서 구분 할 수 있게 했습니다. 패턴이 단일 식별자로 구성되어있는 경우 이것이 변수와 일치하는지 또는 인수가 없는 생성자인지 (즉 해당 생성자 만 일치하는지 여부)를 구분하기가 어려울 수 있습니다. Haskell은 type 생성자(Tree와 같은)와 type 변수(a와 같은)에 적용하기 위해이 규칙을 확장했습니다. 이런 통일된 규칙은 드물었습니다.

5.2 Pattern matching

lazy languages에서 pattern matching의 의미는 복잡합니다. 왜냐하면 laziness는 변수와 첫번째 일치(평가를 강요하지 않음) 또는 생성자(강제 평가) 중 어느 것이 프로그램의 의미를 변경할 수 있는지 프로그램 종료 여부와 관계없음을 의미합니다.

SASL, KRC, Hope, SML, Miranda에서 대수식과 일치는 처음부터 일치하는 대수식을 사용하여 위에서 아래로 정렬됩니다. 또한 SASL, KRCMiranda에서 매치는 왼쪽에서 오른쪽으로 시작합니다. 이는 lazy 언어에서 중요합니다. 일치하지 않는 패턴이 발견되면 일치가 다음 대수식으로 바뀌고, 잠재적으로 비 종결이나 오른쪽에 더 일치하는 오류를 피할 수 있습니다. 결국, 이러한 선택은 길이를 고려하고 다른 가능성을 거부 한 후에 Haskell에 대해서도 마찬가지로 이루어졌다.

Left-to-right 매칭하는 것은 구현하기 쉽고 guard와 잘 어울리며 다른 대안에 비해 표현력이 뛰어납니다. 그러나 다른 대안들은 방정식의 순서가 중요하지 않은 semantics을 가지고 있었고, 이것은 등호 추론을 돕는다(Hudak, 1989) 참조). 결국, 프로그래머가 제한적으로 사용할 수있는 것을 선택하는 것보다 더 널리 사용되는 Top-to-bottom 디자인을 채택하는 것이 더 바람직하다고 생각되었습니다.

5.3 Abstract types

Haskell에서 특별한 구조 대신 모듈 시스템이 데이터 추상화를 지원하는 데 사용됩니다. 대수 형식을 도입한 다음 형식을 내보내고 생성자를 숨김으로써 추상 데이터 형식을 구성합니다. 다음은 그 예입니다.

module Stack( Stack, push, pop, empty, top, isEmpty ) where
    data Stack a = Stk [a]
    push x (Stk xs) = Stk (x:xs)
    pop (Stk (x:xs)) = Stk xs
    empty = Stk []
    top (Stk (x:xs)) = x
    isEmpty (Stk xs) = null xs

5.4 Tuples and irrefutable patterns

발산하는 (또는 Haskell의 에러 함수를 호출하는) 표현식은 일반적으로 모든 유형에 속하는 값인 "bottom"값을 갖는 것으로 간주됩니다. 튜플의 의미에 대해 흥미로운 선택이있다 : ⊥와 (⊥, ⊥) 고유 값이 있는가? 의미 론적 의미론의 전문 용어에서, 해제 된 의미 체계는 두 값을 구별하지만, 해제된 의미 체계는 그것들을 같은 값으로 취급합니다.

구현에서는 두 값이 다르게 표현되지만, 상한 의미에 따라 프로그래머와 구별 할 수 있어야합니다. 그들이 구별 될 수있는 유일한 방법은 패턴 일치이다.

결국 우리는 튜플과 대수 데이터 유형을 모두 해제 된 의미론으로 만들기로 결정 했으므로 패턴 일치가 항상 평가를 유도합니다. 그러나 다소 불안한 절충안에서 우리는 물결 무늬 형태의 게으른 패턴 일치를 다시 도입했습니다.

물결표 "~"는 매칭을 게으르게 만들어 x 또는 y가 요구되는 경우에만 (x, y)에 대한 패턴 일치가 수행되도록합니다. 즉,이 예에서 b가 참일 때. 또한 let과 where 절의 패턴 일치는 항상 게으르기 때문에 g도 작성할 수 있습니다.

5.5 Newtype

튜플에 대해 위에서 설명한 동일한 선택은 하나의 생성자가있는 대수 형식에 대해 발생했습니다. 이 경우에는 튜플과 마찬가지로 의미를 해제할지 여부를 선택할 수 있습니다. Haskell 1.0에서, 단일 구조체를 가진 대수 형태는 해제 된 의미를 가져야한다고 결정되었다. Haskell 1.3 이후부터는 하나의 생성자와 단일 구성 요소를 가진 새로운 대수 타입을 소개하는 두 번째 방법이 있었고, 무한 확장 성이있었습니다. 이를 소개하는 주된 동기는 추상 데이터 유형과 관련이있었습니다. 위의 스택에 대한 Haskell의 정의가 스택의 표현을리스트와 동형 적이 지 않게 만드는 것은 불행한 일이다. 리프팅은 Stk ⊥와는 다른 새로운 보텀 값 ⊥를 추가했기 때문이다. 이제 위 Stack의 데이터 선언을 다음 선언으로 바꿈으로써이 문제를 피할 수 있습니다.

5.6 Records

초기 버전의 Haskell에서 가장 명백한 누락 부분 중 하나는 이름없는 필드를 제공하는 레코드가 없다는 것입니다. 레코드가 실제로 매우 유용하다는 점을 고려할 때 레코드를 생략 한 이유는 무엇입니까?

가장 큰 이유는 명백한 "올바른"디자인이 없다는 것입니다. 레코드 확장, 연결, 업데이트 및 다형성을 다양하게 지원하는 많은 수의 레코드 시스템이 있습니다. 그것들 모두는 타입 시스템 (예 : 행 다형성 및 / 또는 서브 타입)에 복잡한 영향을 미치기 때문에 충분히 복잡합니다. 이 여분의 복잡성은 형식 클래스를 사용하여 레코드의 성능을 적어도 일부 인코딩 할 수 있다는 것을 알게되어 특히 바람직하지 않은 것처럼 보였습니다.

Haskell 1.3 디자인이 진행될 즈음에, 1993 년에 데이터 구조의 명명 된 필드에 대한 사용자의 압력이 강해서 결국 Mark Jones가 제안한 미니멀리스트 디자인을 채택하게되었습니다. Haskell 1.3의 구문 기록 )는 정규 대수 데이터 형식에 대해 동등한 연산을 수행하기위한 단순한 구문 설탕입니다. 레코드 다형성 작업이나 하위 유형 지정은 지원되지 않습니다.

이 최소한의 설계로 인해보다 정교한 제안서가 공개되었으며, 그 중 가장 잘 기록 된 것은 TRex (Gaster and Jones, 1996) (6.7 절)입니다. 새 레코드 제안은 형식 클래스를 사용하여 레코드를 인코딩하는 독창적 인 방법과 함께 Haskell 메일 링리스트에 정기적으로 계속 나타납니다 (Kiselyov et al., 2004).

5.7 n+k patterns

Wadler에 의해 Haskell에 대한 n + k 형식의 패턴이 제안되었는데, 처음에는 Go ̈ del의 불완전 성 증명 (Go ̈ del, 1931)에서 핵심 패턴을 보았고, 핵심은 반복 방정식을 사용하여 코딩 된 논리의 증명 검사기입니다 Haskell 사용자에게는 익숙하지 않은 스타일로 보입니다. 그들은 이전에 Darlington의 NPL (Burstall and Darlington, 1977)과 (부분적으로 Wadler의 부처에서) Miranda에 편입되었다.

이 겉보기에 무해한 구문의 비트는 많은 논란을 일으켰습니다. 일부 사용자는 n + k 패턴을 필수적인 것으로 간주했습니다. 자연수에 대한 함수 정의를 허용했기 때문입니다 (위의 그림 참조). 그러나 다른 사람들은 Int 타입이 실제로 자연수를 나타내지 않았다고 우려했습니다. 사실, Haskell에서 숫자 리터럴 (0, 1 등)이 오버로드 되었기 때문에 fib의 유형이 다음과 같이 일관성이있는 것처럼 보였습니다.

프로그래머는 언제나 그렇듯이, 위의 Int -> Int와 같이 덜 일반적인 형식을 지정할 수 있습니다. Haskell에서 행렬에 fib를 완벽하게 적용 할 수 있습니다! 이로 인해 패턴 일치의 복잡성이 크게 증가했으며, 이제는 오버로드 된 비교 및 산술 연산을 호출해야했습니다. 통사론적인 견해조차도 결과:

사실, 이러한 합병증으로 Haskell위원회의 대다수가 n + k 패턴을 제거 할 것을 제안했습니다. Haskell의 디자인에서 말 거래의 아주 작은 부분 중 하나는 보고서의 편집자 인 Hudak이 Wadler가 n + k 패턴을 제거하는 것에 동의하도록 설득하려했을 때 발생했습니다. Wadler는 다른 기능이있는 경우에만 제거에 동의 할 것이라고 말했습니다. 결국 n + k 패턴이 유지되었습니다.

5.8 Views

Wadler는 패턴 매칭의 편리함과 데이터 추상화의 이점 사이에 긴장감이 있으며, 이러한 긴장을 완화시키는 프로그래밍 언어 기능으로서의 견해를 제시했다. 뷰는 두 개의 데이터 유형 사이의 동형을 지정하며, 두 번째 유형은 대수적이어야하며 두 번째 유형의 생성자가 첫 번째 유형과 일치하는 패턴으로 나타나도록 허용합니다 (Wadler, 1987). 이 초기 제안에 대한 몇 가지 변형이 제안되었으며, Chris Okasaki (Okasaki, 1998b)는 이들에 대한 훌륭한 검토를 제공합니다.

Haskell의 원래 설계에는 뷰가 포함되어 있으며, 모듈에서 내 보낸 생성자와 뷰를 구별 할 수 없다는 개념에 기반하고있었습니다. 이로 인해 수출 목록 및 파생 된 유형 수업이 복잡 해졌고 1989 년 4 월 Wadler는 견해를 제거하여 언어를 단순화 할 수 있다고 생각했습니다.

뷰가 삭제 된 시점에서 Peyton Jones는 Haskell의 실험 확장에 뷰를 추가하기를 원했고 Haskell 1.3에서 뷰를 포함하는 자세한 제안은 Burton과 다른 사람들에 의해 제시되었습니다 (Burton et al., 1996). 그러나 뷰는 언어로 되돌아 가지 않았고 일부 구현에서 사용할 수있는 많은 확장 기능 중에 나타나지 않았습니다.

Haskell의 후계자 인 Haskell에는 지금 논의 중이거나 비슷한 기능을 포함하는 이야기가 있지만, "시도되고 진실한"기준을 충족시키지 않기 때문에 포함되지 않을 것입니다.

6. Haskell as a type-system laboratory

Type class(타입 클래스)는 의심 할 여지없이 Haskell의 가장 두드러진 특징입니다. 초기에 Wadler와 Blott(Wadler와 Blott, 1989)가 비교적 작은 문제(수치 연산과 평등을위한 연산자 오버로딩)에 대한 원칙적인 해결책으로 설계 과정에서 제안되었습니다. 시간이 지남에 따라 타입 클래스는 다양하고 흥미 진진한 방식으로 일반화되기 시작했습니다. 그 중 일부는 1997년 논문 "Type classes: exploring the design space"(Peyton Jones et al., 1997)에 요약되어있습니다.

타입 클래스에 Haskell이 수많은 타입 시스템과 관련된 것들이 설계 및 구현 할 수 있는 실험실이 되었다는 것입니다. 예를 들어, polymorphic recursion, higher-kinded quantification, higher-rank types, lexically scoped type variables, generic programming, template meta-programming 등이 있습니다. 나머지 부분에서는 유형 클래스로 시작하는 Haskell 유형 시스템의 주요 아이디어의 개발 역사를 요약합니다.

6.1 Type classes

타입 클래스의 아이디어는 간단합니다.

class Eq a where (==) :: a -> a -> Bool
                 (/=) :: a -> a -> Bool
instance Eq Int where
    i1 == i2 = eqInt i1 i2 
    i1 /= i2 = not (i1 == i2)

instance (Eq a) => Eq [a] where 
    [] == [] = True
    (x:xs) == (y:ys) = (x == y) && (xs == ys) 
    xs /= ys = not (xs == ys)

member :: Eq a => a -> [a] -> Bool 
member x [] = False 
member x (y:ys) | x==y = True
                | otherwise = member x ys

Eq Int의 경우, eqInt는 Int 타입에서 equality를 정의하는 기본 함수라고 가정합니다. member의 타입 시그니처는 한정된 Bool 형태를 사용합니다. member는 클래스 Eq의 인스턴스인 모든 타입 a에 대해 a -> [a] -> Bool 유형을 선언합니다. 클래스 선언은 클래스의 메서드 (이 경우 두 개 즉, (==) 및 (/ =))와 해당 형식을 지정합니다. 타입은 적절한 인스턴스 타입에서 각 클래스의 메소드에 대한 구현을 제공하는 인스턴스 선언을 사용하여 클래스의 인스턴스로 작성됩니다. 타입 클래스의 특히 매력적인 기능은 유형 지향 변환에 의해 소위 "dictionary-passing style"로 변환 될 수 있다는 것입니다.

data Eq a = MkEq (a->a->Bool) (a->a->Bool)
eq (MkEq e _) = e
ne (MkEq _ n) = n

dEqInt :: Eq Int
dEqInt = MkEq eqInt (\x y -> not (eqInt x y))
dEqList :: Eq a -> Eq [a]
dEqList d = MkEqel(\x y -> not(el x y))
    where el [] [] = True
    el (x:xs) (y:ys) = eq d x y && el xs ys
    el _ _ = False

member :: Eq a -> a -> [a] -> Bool
member d x [] = False
member d x (y:ys) | eq d x y = True
                  | otherwise = member d x ys

언어 디자인의 일부로 타입 클래스가 채택되어 곧바로 equality (Eq)와 ordering (Ord)을 지원하는데 사용되었습니다. 문자열을 값으로 변환하거나 읽는 것 (Read와 Show); 열거 형 (Enum); 산술 연산(Num, Real, Integral, Fractional, Floating, RealFrac and RealFloat) 숫자 연산 및 배열 인덱싱 (Ix). 숫자 연산을 분류하는 데 사용되는 형식 클래스의 다소 어려운 집합은 대체 순도 (Ring과 Monoid와 같은 더 많은 클래스를 제안한)와 실용주의 (더 적은 수의 제안) 사이의 약간 불안한 절충을 반영했습니다.

대부분의 정적 유형 언어에서 타입 시스템은 일관성을 검사하지만 타입을 고려하지 않고 프로그램이 어떻게 실행되는지 이해할 수 있습니다. Haskell에서는 그렇지 않습니다. 프로그램의 동적 의미는 반드시 타입 검사기에 의해 타입 클래스 오버로드가 해결되는 방식에 달려있다. 타입 클래스는 매우 강력하고 편리한 메카니즘임이 입증되었지만, "뒤에서" 더 많은 일이 일어나기 때문에 프로그램이 어떻게 동작하는지에 대해 추론하기가 더 어렵습니다.

타입 클래스는 극도로 우연적이었습니다. Haskell의 첫 번째 릴리즈에의 표준 라이브러리에 13개의 유형 클래스가 있다는 사실은 얼마나 신속하게 퍼져 있음을 나타낸다.

6.2 The monomorphism restriction

초기 단계에서 논쟁의 주요 원인은 소위 "monomorphism restriction"이었습니다. genericLength에는 다음과 같은 오버로드 된 유형이 있다고 가정합니다.

genericLength :: Num a => [b] -> a

f xs = (len, len)
    where
        len = genericLength xs

len은 한 번만 계산되어야하는 것처럼 보입니다. 그러나 실제로는 두 번 계산할 수 있습니다. 왜냐하면 len을 유추 할 수 있기 때문에 len : :( Num a) => a; dictionary-passing style을 사용하지 않을 경우 len은 len이 발생할 때마다 한 번 호출되는 함수가 되며 각 함수는 다른 유형으로 사용될 수 있습니다.

Hughes는 이러한 방식으로 자동으로 계산을 복제하는 것은 용납 할 수 없다고 강력하게 주장했고, 많은 논쟁 끝에 위원회는 이제 악명 높은 monomorphism restriction을 채택했다. 간략하게 말하자면 함수처럼 보이지 않는 정의 (즉, 왼쪽에 인수가 없음)는 모든 오버로드 된 유형 변수에서 단일 양식이어야 합니다. 이 예제에서 규칙은 두 유형 모두에서 동일한 유형의 len을 강제로 사용하므로 성능 문제가 해결됩니다. 프로그래머는 다형성 동작이 필요한 경우 len에 대해 명시적인 형식 시그니처를 제공 할 수 있습니다.

monomorphism restriction은 새로운 Haskell 프로그래머를 예기치 않게 또는 모호한 오류 를 발생시킵니다. 대안으로 많은 논의가 있었습니다. GHC 컴파일러는 플래그(-fno-monomorphism-restriction)를 제공합니다. 그러나 진정으로 만족할만한 대안은 찾지 못했습니다.

6.3 Ambiguity and type defaulting

우리는 유형 클래스에서 두 번째로 어려움을 겪었습니다. 즉 모호성입니다.

show :: Show a => a -> String
read :: Read a => String -> a

f :: String -> String 
f s = show (read s)

여기서 show는 Show 클래스의 모든 유형 값을 String으로 변환하고 read는 class 클래스의 모든 유형에 대해 reverse를 수행합니다. 따라서 f는 잘 형식화 된 것처럼 보이지만 중간 표현식의 유형을 지정하는 것은 없습니다 (read s). Int s 또는 Float a 값을 파싱해야합니까? 어떤 것을 선택해야 하는지는 말할 것도 없고 프로그램의 의미에 영향을 줍니다. 이런 프로그램은 애매 모호하며 컴파일러에 의해 거부됩니다. 그런 다음 프로그래머는 형식 서명을 추가하여 사용할 형식을 말할 수 있습니다.

f :: String -> String
f s = show (read s :: Int)

프로그래머는 특수 최상위 기본 선언에 타입 목록을 지정할 수 있으며 이러한 타입은 모든 제한 조건을 충족 할 때까지 순서대로 시도됩니다. 몇 가지 제한된 상황을 제외한 모든 상황에서 임의적 인 선택을 하지 않으려고 합니다. 결과는 모든 경우에서 동일하기 때문에 문제가 되지 않지만 타입 시스템에서 이를 알 수 있는 방법은 없습니다. 따라서 GHC는 대화식 버전인 GHCi에 대한 기본 규칙을 더욱 완화합니다.

6.4 Higher-kinded polymorphism

타입 클래스에서 예상치 못한 첫번째 개발은 예일 대학의 Mark Jones가 타입 대신에 타입 생성자에 대한 클래스를 매개 변수화하는 것을 제안했을 때였습니다. 생성자 클래스라고하는 아이디어였습니다(Jones, 1993).

Jones의 논문은 1993년에 나왔는데 같은 해 모나드가 I/O로 유명해졌다. 타입 클래스가 직접적으로 지원되는 모나드는 모나드를 훨씬 더 접근 가능하고 대중적으로 만들었다. 동시에 모놀리 식 I/O의 유용성은 더 높은 유형의 다형성의 채택을 보장했습니다. 그러나 더 높은 종류의 다형성은 독립적 인 유용성을 가지고있다. 높은 유형에 대해 매개 변수화 된 데이터 유형을 선언하는 것은 전적으로 가능하며 때로는 매우 유용하다. 또한 중첩 된 데이터 유형을 처리하기 위해 상위 유형의 변수에 대해 계량화 된 함수가 필요할 수도 있습니다 (Okasaki, 1999; Bird and Paterson, 1999).

유형 추론은 처음에는 higher-order unification를 필요로 하는 것으로 보인다(Huet, 1975). 그러나 더 높은 유형의 구조체를 해석되지 않은 함수로 취급하고 유형 수준에서 람다를 허용하지 않으면 Jones의 논문 (Jones, 1993)은 일반적인 higher-order unification만으로 충분하다는 것을 보여줍니다. 솔루션은 조금 특별합니다. 예를 들어, 데이터 유형 선언의 유형 매개 변수 순서는 중요 할 수 있지만 우수한 전력 대 중량 비율을 갖습니다. 돌이켜 볼 때, 더 높은 종류의 정량화는 전통적인 Hindley-Milner typing의 단순하고 우아하고 유용한 일반화이다(Milner, 1978). 이 모든 것들이 1996년에 출판된 Haskell 1.3 Report로 확고해졌습니다.

6.5 Multi-parameter type classes

WadlerBlott의 초기 제안은 단일 매개 변수로 타입 클래스에 중점을 두었지만 타입 클래스는 여러 매개 변수로 일반화 될 수 있음을 알았습니다.

class Coerce a b 
    where coerce :: a -> b

instance Coerce Int Float where 
    coerce = convertIntToFloat

단일 매개 변수 타입 클래스는 타입에 대한 술어로 볼 수 있지만, 다중 매개 변수 클래스는 유형 간 관계로 볼 수 있습니다

다중 매개 변수 타입 클래스는 초기의 몇 가지 타입 클래스의 유형(Jones, 1991, Jones, 1992, Chen et al., 1992)에서 논의되었으며 Jones의 언어 Gofer에서 구현되었습니다. 1991년 첫번째 릴리스에서 위원회는 그것을 포함하는 것에 반대했다. 단일 매개 변수 타입 클래스는 초기 보수적 인 디자인 목표를 훨씬 뛰어 넘는 큰 문제였고 처음에 해결 한 문제 (평등과 숫자 연산 과부하)를 해결했습니다. 그 이상으로 나아가는 것은 어둠 속으로 나아가는 단계가 될 것입니다. 우리는 형식 추론의 중첩, 합류 및 결정 가능성에 대한 질문에 불안했습니다. 위와 같이 강요를 정의하기는 쉽지만 형식 유추가 실제로 사용 가능하게 만들지는 확실하지 않습니다. 결과적으로 Haskell 98은 단일 매개 변수 제한을 유지했습니다.

그러나 시간이 갈수록 사용자 압력은 다중 매개 변수 유형 클래스를 채택하고 GHC는 1997년에 이를 채택했습니다 (버전 3.00). 그러나 다중 매개 변수 유형 클래스는 기능 종속성의 출현 때까지 실제로 자체적으로 제공되지 않았습니다.

6.6 Functional dependencies

다중 매개 변수 유형 클래스의 문제점은 모호한 유형을 작성하는 것이 매우 쉽다는 것입니다. 예를 들어 Num 클래스를 일반화하려는 다음과 같은 시도를 생각해보십시오.

class Add a b r where 
    (+) :: a -> b -> r

instance Add Int Int where ...
instance Add Int Float Float where ...
instance Add Float Int Float where ...
instance Add Float Float Float where ...

프로그래머가 입력 형식에 따라 결과 형식을 선택하여 여러 가지 형식을 추가 할 수 있습니다. 아아, 사소한 프로그램조차 모호한 유형이 있습니다. 예를 들어 다음을 고려하십시오.

n = x + y

여기서 x와 y는 Int 유형입니다. 어려운 점은 컴파일러가 n의 유형을 파악할 방법이 없다는 것입니다. 프로그래머는 (+)의 인수가 모두 Int 인 경우 결과도 같지만 그 의도는 다음과 같은 인스턴스 선언이없는 경우에만 암시된다는 것을 의미합니다.

instance Add Int Int Float where ...

2000년에 Mark Jones는 문제를 해결하는 "“Type classes with functional dependencies"를 발표했습니다(Jones, 2000). 아이디어는 데이터베이스 커뮤니티에서 기술을 빌려 클래스의 매개 변수 사이에 명시적인 함수 종속성을 선언하는 것입니다.

class Add a b r | a b -> r where ...

"a b -> r"은 a와 b를 고정하면 r을 수정하여 모호성을 해결해야한다고 말합니다. 그러나 그것이 전부는 아니었습니다. 다중 매개 변수 클래스와 함수 종속성의 조합이 타입 수준에서 계산을 허용하기 위해 밝혀졌습니다.

data Z = Z  
data S a = S a

class Sum a b r | a b -> r

instance Sum Z b b
instance Sum a b r => Sum (S a) b (S r)

여기서, Sum은 연산이 없는 3-매개 변수 클래스입니다. Sum ta tb tc의 관계는 tc 유형이 ta와 tb의 합계의 Peano representation(타입 수준에서) 인 경우 보유됩니다. 인스턴스 선언의 형태에 대한 다른 Haskell 98의 제한을 자유화함으로써 논리 프로그래밍 스타일에서 타입 레벨에서 임의의 계산을 작성할 수 있다는 것이 밝혀졌습니다. 이것은 Haskell 메일 링리스트의 많은 트래픽을 발생시켰습니다(Hallgren, 2001; McBride, 2002; Kiselyov et al., 2004). 또한 이러한 프로그램을 직접적으로 발췌하는 방법을 제안하는 일련의 논문을 제시했습니다(Neubauer et al., 2001; Neubauer et al., 2002; Chakravarty et al., 2005b; Chakravarty et al., 2005a).

Jones의 원본 논문은 functional 의존성에 대한 비공식적인 설명만을 주었지만, (Haskell과 마찬가지로) 구현되고 널리 사용되는 것을 막지는 못했습니다. 명백한 단순성에도 불구하고, functional 종속 관계는 특히 local universalexistential quantification와 같은 다른 확장들과 결합 될 때 특히 까다롭다. 형식화된 설계 영역에 관한 연구는 여전히 진행 중이다(Glynn et al., 2000; Sulzmann et al., 2007).

7. Monads and input/output

type classes를 제외하고 monadsHaskell에서 가장 특별한 특징 중 하나이다. 모나드는 원래 Haskell 디자인에 포함되지 않았습니다. 왜냐하면 Haskell이 처음 만들어졌을 때, 모나드는 category theory의 모호한 특징이었기 때문이다. 프로그래밍에 대한 영향은 대부분 인식 할 수 없었습니다. 이 섹션에서는 Haskell의 입/출력에 대한 설명하고 한편으로는 다른 모나드에 대해 설명합니다

sd: 본 논문의 Figure 3 ~ 6을 꼭 확인하자.

7.1 Streams and continuations

Haskell위원회(Haskell Committee)는 언어를 순수하게 유지하기로 결정했으므로 부작용이 없음을 의미합니다. 따라서 I/O 시스템의 설계가 중요한 문제였습니다. 그러나 순수했기 떄문에 표현력을 잃고 싶지 않았습니다.

당시 해결책으로 제시 된 두 가지 주요 경쟁자는 streamscontinuations 입니다. 둘 다 이론적으로 완성되어 있으며, 상당한 표현력을 제공하는 것처럼 보였고 둘 다 순수하게 유지됩니다. 이러한 접근 방식의 세부 사항을 연구 할 때 우리는 기능적으로 동일하다는 사실을 깨달았습니다. 즉, 연속으로 스트림 I/O를 완전히 모델링 할 수 있었고, 그 반대의 경우도 있었습니다. 따라서 Haskell 1.0 보고서에서 우리는 먼저 스트림의 관점에서 I/O를 정의했지만 연속성에 기반한 완전히 동등한 설계를 포함했습니다. I/O를 위한 세번째 모델이 논의되었는데, 이 "world-passing" 모델은 Haskell에게 결코 심각한 경쟁자가 아니 었습니다. 왜냐하면 우리는 상태에 대한 "단일 스레드" 액세스를 보장 할 수 있는 쉬운 방법이 있기 때문입니다

7.2 Monads

Haskell에서 모나드는 매우 유용하지만 처음에는 그 개념이 꽤 어렵습니다. 모나드는 수많은 응용이 있기 때문에 사람들은 모나드를 특정 관점에서만 설명하는 경향이 있는데, 그러면 모나드를 완벽히 이해하는 데 혼란을 줄 수도 있습니다. 역사적으로 보면 모나드는 Haskell에서 입출력을 수행하기 위해 도입되었습니다. 파일 읽고 쓰기 같은 작업에 미리 정의된 실행 순서는 중대한 사항이고 모나딕 연산은 내재된 inherent 순서를 따른다. 앞서 간단한 입출력에서 do 표기를 이용한 연쇄와 I/O를 논의한 바 있습니다. 사실 do는 모나드의 편의 구문일 뿐입니다.

모나드는 입출력에 한정되지 않는습니다. 모나드는 예외, 상태, 비결정성(non-determinism), 연속성(continuation), 코루틴, 그 외에 수많은 것을 지원한다. 사실 모나드의 다재다능함덕에 이 중 어느 것도 Haskell 언어의 일부로 내장될 필요가 없었습니다. 이것들은 대신 표준 라이브러리에 정의되어 있다.

모나드는 세 가지에 의해 정의된다. 1) 타입 생성자 M, 2) return 함수1, 3) "bind"라 부르는 (>>=) 연산자 이다. 타입 생성자는 M = Maybe이고 return과 (>>=)는 아래와 같이 정의됩니다.

return :: a -> Maybe a
return x  = Just x
(>>=)  :: Maybe a -> (a -> Maybe b) -> Maybe b

m >>= g = case m of
    Nothing -> Nothing
    Just x  -> g x

Maybe는 모나드고 return은 하나의 값을 Just로 감싸서 반환한다. (>>=)은 m :: Maybe a 값과 g :: a -> Maybe b 함수를 취합니다. m이 Nothing이면 하는 일 없이 결과도 Nothing이다. 반대로 Just x의 경우 Just로 감싼 x에 g가 적용되고 Maybe b를 결과로 내놓는다. 그 결과는 g가 x에 하는 일에 따라 Nothing일 수도 있다. 종합하면, m에 포함된 값이 있으면 이 값에 g를 적용하고 그 결과가 Maybe 모나드에 다시 들어가 반환됩니다.

return과 (>>=)가 어떻게 작동하는지 이해하는 첫 걸음은 어떤 값과 인수가 모나딕인지 아닌지 추적하는 것입니다. 다른 많은 경우와 마찬가지로 타입 시그너쳐가 그 과정의 안내자가 될 것입니다.

모나드는 꽤 많은 functional 프로그램을 구성하는 데 큰 도움이 된다는 것을 알게되었습니다. 예를 들어, GHC의 타입 검사기는 state transformer, exception monadstate reader monad를 결합하는 모나드를 사용합니다. 모나드는 종종 조합하여 사용되며, 하나의 레벨을 추상화함으로써 Haskell에서 모나드 변환기를 만들 수 있습니다(Steele, 1993; Liang et al., 1995; Harrison and Kamin, 1998). Liang, Hudak, Jones의 논문은 모듈형 해석기가 Haskell에서 모나드 인터프리터를 사용하여 작성 될 수 있음을 보여 주었지만, Gofer에서만 지원되는 타입 클래스 확장이 필요했습니다. 이것은 타입 클래스와 모나드 변형 라이브러리의 개발에 대한 혼란을 불러 일으킨 사례 중 하나였습니다. 모나드 인터프리터의 유용성에도 불구하고 모나드는 좋은 모듈 방식으로 구성되지 않고 여전히 열려있는 연구 문제입니다(Jones and Duponcheel, 1994; Lu ̈th and Ghani, 2002).

Haskell에서는 서로 다른 시간에 모나드에 대한 두 가지 형태의 syntactic sugar가 등장했습니다. Haskell 1.3은 John Launchbury의 lazy imperative programming(Launchbury, 1993)에서 파생 된 Jones의 do notation을 채택했습니다. 그 결과, Haskell 1.4는 do not-notationmonad comprehensions(Wadler, 1990a)을 지원했습니다. 대부분의 사용자는 do 표기법을 선호하였고, 이러한 결과는 monad comprehensions는 초보자가 이해하기 어려울 수 있음을 의미하므로 Haskell 98에서는 monad comprehensions이 제거되었습니다.

7.3 Monadic I/O

비록 Wadler의 Moggi의 아이디어는 ​​입/출력의 문제를 다루지 않았지만 Glasgow의 다른 사람들은 곧 모나드가 입출력을 위한 이상적인 프레임워크를 제공한다는 것을 깨달았습니다. 핵심 아이디어는 type IO a의 값을 수행 할 때 타입 a 값을 전달하기 전에 입력 및 출력을 수행 할 수있는 "계산"으로 취급하는 것입니다. 예를 들어, readFile에 타입을 지정할 수 있습니다. 따라서 readFile은 Name을 취하고 수행 될 때 파일을 읽고 해당 내용을 String으로 반환하는 계산식을 반환하는 할 수 있습니다.

readFile :: Name -> IO String

(io Error e)이 실패하여 예외 e가 발생합니다. (catch m h)은 m을 실행합니다. 그것이 성공하면 그 결과는 catch의 결과입니다. 그러나 실패하면 예외가 h로 전달됩니다.

ioError :: IOError -> IO a
catch :: IO a -> (IOError -> IO a) -> IO a

이러한 타입은 고정 요청 및 응답 타입으로 작성할 수 없습니다. 그러나 큰 이점은 개념적입니다. 실패 및 성공 지속에 대한 세부적인 측면보다 계산 측면에서 추상적으로 생각하는 것이 훨씬 쉽습니다. 모나드는 이러한 세부 사항을 추상화하고 앞으로 변경하기 쉽습니다. 독자는 IO 모나드에 대한 튜토리얼 소개와 함께(Peyton Jones, 2001) 다양한 발전 사항을 찾을 수 있습니다.

8. Haskell in middle age

Haskell이 실제 응용 프로그램에 사용됨에 따라 초기 설계의 실수에 대한 댓가를 치루고 있으며, 여전히 빠르게 변경되고 있습니다. 따라서 우리는 역사적인 관점에 대해서 개략적으로 작성하였습니다.

8.1 The Foreign Function Interface

많은 응용 프로그램은 Haskell에서 다른 언어로 작성된 프로시저(procedures)를 호출하는 기능이며, 그 반대도 필요합니다. I/O monad가 사용되면서 다양한 ad-hoc 방법이 소개되었습니다.

예를 들어, GHC의 첫 번째 릴리스에서는 모나딕(monadic) 프로시저(procedures)에서 C 코드를 포함 할 수 있었고 Hugs는 C 함수를 Haskell로 확장할 수 있는 방법을 사용했습니다.

Haskell이 C 프로시저를 호출하는 방법에 대한 독립적인 구현에 대한 노력이 점차 커졌습니다. 이른바 FFI(Foreign Function Interface)는 C를 최소한의 것으로 취급하기 때문에 C를 사용해서 다른 것을 호출 할 수 있습니다. 이것은 기반으로 Blessed Addenda라는 아이디어가 나왔고, 구현에 관한 내용은 Haskell 98 보고서의 부록에 설명되어 있습니다. 다양한 언어를 확장 기능을 제공합니다. 2001-2003년 Manuel Chakravarty가 FFI Addenda를 주도했으며, 2003년에 버전 1.0의 30페이지 분량으로 발표했습니다. 이 표준화 노력과 덕분에 관련된 작업을 손쉽게 처리할 수 있는 도구인 Green Card(Nordin et al., 1997), H/Direct(Finne et al., 1998), C2Hs(Chakravarty, 1999a)등을 사용할 수 있습니다.

관련 업무는 모두에게 열려 있었지만 Haskell 언어의 개발과 종류가 다르기 때문에 Manuel Chakravarty이 프로세스를 주도하고 사양의 편집자로서의 활동했습니다.

8.2 Modules and packages

Haskell의 모듈 시스템에 대한 작은 논쟁이 있었습니다. 당시에는 ML의 모듈 시스템이 잘 구축되고 있었기 때문에 Haskell에 채택 할 것인지에 대한 격렬한 토론을 예상했습니다. 하지만 ML의 모듈 시스템에 대해 충분히 알고 있지 않았거나, 타입 클래스와 ML의 모듈을 결합하는데 합리적인 선택이 아니었음에 대한 암묵적인 합의가 있었던 것 같습니다. 어쨌든, 우리는 매우 단순한 설계를 선택했습니다. Haskell의 모듈 시스템은 네임 스페이스(namespace control mechanism)의 제어 메커니즘으로 선택하였습니다. 이것의 가장 큰 장점은 모듈 시스템이 타입 클래스와 완전히 별개로 설계되었지만 수년 동안 까다로운 문제가 밝혀지지 않았습니다(Diatchki et al., 2002).

모든 모듈은 구현과 인터페이스로 구성되었습니다. 인터페이스의 구문(syntax)과 의미(semantics)에 관해 많은 논의가 있었습니다. 모듈이 인터페이스 중 하나에 정의 된 엔티티를 외부로 내보낼 때 인터페이스와 구현간에 정보를 복제하는 문제, 어떤 모듈이 궁극적으로 엔티티를 정의 하는지를 인터페이스에서 추론 할 수 있는지 여부, 컴파일러가 인터페이스에서 원하는 것과 프로그래머가 쓸 수 있는 것 사이의 선택 등이 논의되었습니다. 결국 Haskell 1.4는 언어의 공식적인 부분으로서 인터페이스를 완전히 폐기했습니다. 대신 인터페이스 파일은 별도의 컴파일 가능한 결과물로 간주됩니다.

8.2.1 Hierarchical module names

Haskell이 널리 사용됨에 따라 모듈의 이름공간이 단순하다는 것은 점점 불편해졌습니다. 예를 들어, 두 개의 콜렉션 라이브러리가있는 경우, 둘 다 모듈 이름으로 Map을 사용할 수 없습니다. 이것은 Malcolm Wallace가 Java에서 빌린 디자인을 사용하여 다중 컴포넌트 계층의 모듈 이름(예: Data.Map)을 사용하도록 노력했고, GHC, Hugsnhc에 의해 신속하게 구현되었으며 이후 변경되지 않았습니다.

8.2.2 Packaging and distribution

모듈은 프로그램 구성의 합리적인 단위를 형성하지만 배포는 아닙니다. 개발자는 관련 모듈 그룹을 문서, 라이센스 정보, 다른 패키지의 종속성에 대한 세부 정보, 소스 파일, 빌드 정보 등을 포함하여 "패키지"로 배포하려고 합니다. 이 중 어떤 것도 Haskell 언어 디자인의 일부는 아닙니다. 2004년에 Isaac Jones는 Haskell 패키지의 구성 및 배포를 지원하는 Cabal이라는 시스템을 구현을 주도했습니다. 그 후 David Himmelstrup은 사람들이 Cabal 패키지를 찾고 다운로드 할 수 있게 해주는 Cabal 패키지 서버인 Hackage를 구현했습니다. Haskell이 이러한 배포 및 검색 방법을 도입하는데 추진력을 얻는데 15년 이상이 걸렸습니다.

8.2.3 Summary

이 모든 발전의 결과는 소박한 모듈 시스템입니다. 모듈 시스템은 실용적인 프로그래밍 도구라 할 수 있으며, 아마도 이것은 매우 좋은 선택이었다고 판단됩니다.

8.3 Libraries

잘 구현된 라이브러리의 중요성이 부각되는데 오랜 시간이 걸리지 않았습니다. Haskell 초기 보고서에는 표준 라이브러리를 정의하는 부록이 포함되어 있었지만, Haskell 1.3(1996년 5월)에서는 표준 라이브러리 코드의 양이 언어 정의와 함께 별개의 보고서로 제공되는 범위까지 증가했습니다.

Haskell 98 Language and Libraries Report의 240페이지 중 140개는 언어 정의이며 100개는 라이브러리를 정의합니다. 그러나 실제 응용 프로그램은 훨씬 더 풍부한 라이브러리를 필요하며, Haskell 언어 구현을 기반으로 비공식적인 라이브러리가 만들어지기 시작했습니다. 처음에는 GHChslibs라는 라이브러리 번들을 배포하기 시작했지만 상호 구현 호환성에 대한 사용자의 희망에 따라 Hugs, GHCnhc 팀은 2001년에 공통된 오픈 소스 라이브러리를 작성하기 위한 작업을 진행해서 현재까지 계속됩니다.


추가자료