최근 수정 시각 : 2023-12-31 02:03:55

Haskell

프로그래밍 사이트 선정 프로그래밍 언어 순위 목록
{{{#!wiki style="margin: 0 -10px -5px; word-break: keep-all"
{{{#!wiki style="display: inline-block; margin: 0 0 -5px; min-width: 25%"
{{{#!folding ⠀[ IEEE Spectrum 2023 ]⠀
{{{#!wiki style="margin:-6px -1px -10px"
<rowcolor=#fff> 스펙트럼 부문 상위 10개 프로그래밍 언어 직업 부문 상위 10개 프로그래밍 언어
1 Python 1 SQL
2 Java 2 Python
3 C++ 3 Java
4 C 4 JavaScript
5 JavaScript 5 C++
6 C# 6 C#
7 SQL 7 C
8 Go 8 HTML
9 TypeScript 9 Shell
10 HTML 10 SAS
}}}}}}}}}
⠀[ Stack Overflow 2023 ]⠀
||<tablewidth=100%><width=2000><-4><bgcolor=#FFA500><tablebgcolor=#fff,#222> 2023년 Stackoverflow 설문조사 기준 인기 상위 25개 프로그래밍 언어 ||
1 JavaScript 14 Rust
2 HTML, CSS 15 Kotlin
3 Python 16 Ruby
4 SQL 17 Lua
5 TypeScript 18 Dart
6 Bash 19 어셈블리어
7 Java 20 Swift
8 C# 21 R
9 C++ 22 Visual Basic
10 C 23 MATLAB
11 PHP 24 VBA
12 PowerShell 25 Groovy
13 Go
⠀[ TIOBE 2024 ]⠀
||<tablewidth=100%><width=2000><-4><bgcolor=deepskyblue><tablebgcolor=#fff,#222> 2024년 2월 기준 검색어 점유율 상위 20개 프로그래밍 언어 ||
1 Python 11 Fortran
2 C 12 Delphi / Object Pascal
3 C++ 13 MATLAB
4 Java 14 Assembly language
5 C# 15 Scratch
6 JavaScript 16 Swift
7 SQL 17 Kotlin
8 Go 18 Rust
9 Visual Basic 19 COBOL
10 PHP 20 Ruby
{{{#!wiki style="margin:0 -10px -5px; min-height: 26px"
{{{#!folding [ 21위 ~ 50위 펼치기 · 접기 ]
{{{#!wiki style="margin: -6px -1px -10px"
21 R 36 Transact-SQL
22 SAS 37 VBScript
23 Classic Visual Basic 38 Haskell
24 Prolog 39 TypeScript
25 Ada 40 Logo
26 Perl 41 Bash
27 (Visual) FoxPro 42 Scheme
28 Dart 43 Solidity
29 D 44 ABAP
30 F# 45 ML
31 Scala 46 X++
32 Objective-C 47 PL/SQL
33 Lua 48 GAMS
34 Julia 49 PowerShell
35 LISP 50 RPG
}}}}}}}}} ||
⠀[ PYPL 2024 ]⠀
||<tablewidth=100%><width=2000><-4><bgcolor=green><tablebgcolor=#fff,#222> 2024년 2월 기준 검색어 점유율 상위 20개 프로그래밍 언어 ||
1 Python 11 Rust
2 Java 12 Go
3 JavaScript 13 Kotlin
4 C/C++ 14 MATLAB
5 C# 15 Ada
6 R 16 Dart
7 PHP 17 Ruby
8 TypeScript 18 PowerShell
9 Swift 19 VBA
10 Objective-C 20 Lua
}}} ||
프로그래밍 언어 목록 · 분류 · 문법

main = putStrLn "Hello, world!"

1. 개요2. 등장배경과 역사3. 특징
3.1. Currying3.2. 순수 함수형 언어3.3. 느긋한 계산3.4. 강력한 타입 추정3.5. 대수적 데이터 타입3.6. Parametric polymorphism3.7. 모나드
4. 수학과의 관련성5. 안 써요6. 하스켈로 만들어진 프로그램들7. 기타

1. 개요

파일:Haskell 로고.svg
하스켈의 로고. 람다 대수에서의 λ와 모나드에 사용하는 bind 연산자인 >>=를 겹쳐놓은 이미지다.

공식 홈페이지

순수 함수형 프로그래밍 언어.[1] I/O와 같이 필요한 경우가 아니면 Side Effect가 없는 순수 함수로만 만들어졌다.

가장 많이 쓰이는 하스켈 컴파일러로는 GHC[2]가 있으며, 여기에 패키지 매니저를 추가한 플랫폼이 Stack이다. Stack을 설치하면 GHC도 함께 설치되기 때문에, 특별한 이유가 없을 경우 Stack을 설치하면 된다.

2. 등장배경과 역사

하스켈은 1985년 등장한 느긋한 계산으로 작동하는 함수형 언어 미란다[3]의 뒤를 이어, 상용 프로그래밍 언어 따위가 순수 함수형 언어를 대표하는 것이 마음에 안드는 함수형 언어와 컴퓨터 아키텍처 연구 학술회에서 1987년부터 설계를 시작했다. 언어의 이름인 하스켈은 논리학자 하스켈 브룩스 커리(Haskell Brooks Curry)의 이름에서 가져왔다.[4] 목표는 순수 함수형 프로그래밍 언어의 원형을 정의하는 것으로 학술조직이 주도한 언어답게 디자이너가 다 타려면 미니버스가 필요할 정도로 많고, 개발속도는 티스푼 공사만큼이나 느리다는 특징이 있었다(...). 하여간 최초 논의로부터 3년 뒤인 1990년 4월 1일[5], 1.0 버전[6]의 보고서가 발표되었고, 1.1~1.4 버전을 거쳐, 1999년에 가장 널리 알려진 버전인 하스켈 98 보고서가 나왔다.[7] 2021년 시점에서 최신 정의는 여전히 하스켈 2010이다. 다만 하스켈 2010 역시 그리 바뀐것은 없다. 2016년 4월 Haskell 2020 위원회가 출범했지만 Haskell 2020은 결국 나오지 않았고 위원회도 좌초된 상황. 하스켈 2010 이후의 발전은 컴파일러인 GHC 차원의 기능 확장에 의존하고 있다. 일반적으로는 이와 반대로 언어가 먼저 만들어진 뒤 컴파일러가 언어를 구현한다.

3. 특징

메인스트림 언어인 C/C++/C#/Java/Python 등은 각 언어마다의 특징들을 갖고 있긴 하지만, 사실 언어적으로 과거 사용되던 언어들에 비해 큰 혁신을 가져와서 유명해진 언어들은 아니다. 컴퓨터 언어적 관점에서 혁신과 함께 어느정도 유명세를 탄 언어는 저들보다는 차라리 Fortran, Simula, Lisp, Prolog 정도를 꼽을 수 있고 Haskell은 Purity, Lazy evaluation, Monad 등 당시 기준으로는 좀 극단적[8]으로 볼 수 있는 언어 디자인으로 유명하였다.

3.1. Currying

수학/논리학자인 하스켈 커리(Haskell Curry)는 오늘날 함수형 언어에서 가장 중요한 컨셉 중 하나로 볼 수 있는 Currying으로 유명한데, 이 Currying은 사실 하스켈만의 특징이라기보다는 거의 모든 함수형 언어가 공유하는 특징이기도 하다. Currying은 다인자 함수의 변수(parameter)를 쪼개기로 생각하면 간단하다. 즉, 다변수 함수(여러 개의 값을 묶어서 한꺼번에 입력으로 받는 함수)를 일변수 함수(값을 하나씩 입력으로 받는 함수) 여러개로 쪼개는 것이다.[9] 예를들어, f(x,y)라는 함수를 g_x (y)로 바꾸는것이다. 여기서 g_x 함수는 이름처럼 고정된 게 아니라, x 값에 따라 정해진다. 따라서 g_x (y)에는 y를 받아 결과값을 돌려주는 g_x (y) 함수와, x를 받아 g_x 자체를 돌려주는 함수라는 2개의 일변수 함수가 중첩되어 들어 있다. 혹은 (g(x))(y)라고 생각해도 된다.

보다 구체적인 예를 들어 보자. 3*2에서 *를 두개의 인자를 받는 함수라고 생각하고 보통 함수와 같은 방식으로 쓴다면 *(3,2)이라고 쓰면 될 것이다. 즉 * 함수가 3과 2의 쌍을 한번에 받아서 6을 돌려주게 되는 것이다. 그런데, 입력값을 받아서 그 입력값의 3배를 돌려주는 것도 함수이므로, 그 함수를 (×3)이라고 표시해 보자. 그러면 (×3)(2) 라는 표현도 가능하다. 같은 방법으로, 입력값을 받아서 그 입력값의 4배를 돌려주는 함수를 (×4)라고 표시한다면, (×4)(3) 같은 식도 가능해진다. 이런 식으로 일반화해서 입력값을 받아서 그 입력값의 m배를 돌려주는 함수를 (×m)이라고 써 보자.

그러면 *(m,n)=(×m)(n) 이라는 일반식이 성립한다. 이 식의 우변에 일변수 함수 1개가 있다고 생각하기 쉽지만, 그렇지 않다. 왜냐하면 ×는 m을 입력으로 받아서 (×m)이라는 함수를 출력으로 돌려주는 함수이기 때문이다. 따라서 좌변에는 이변수함수 *, 우변에는 일변수함수 ×와 (×m)이 있다고 보아야 한다.[10]

이런 식으로, 모든 이변수함수는 일변수함수 하나와 그 일변수함수를 결과값으로 내놓는 다른 일변수함수의 조합으로 분해가 가능하다. 이런 것을 currying이라고 하며, 타입을 이용하여 다음과 같이 생각해 볼 수도 있다.

*(m,n)은 (int, int) → int 라는 타입을 가지고, 이는 * 함수가 정수의 쌍을 받아서 정수를 돌려주는 함수라는 의미이다. 한편 (×m)(n)의 타입은 int → (int → int)가 되고, 이는 × 함수가 정수를 받아서 함수를 돌려주는 함수인데 ×가 돌려준 함수는 정수를 받아서 정수를 돌려주는 함수라는 것을 의미한다.[11]

이렇기 때문에, 하스켈에서는 그냥 * 3 2 라고 쓰고, *과 3 사이에서 끊어 읽으면 *(3,2) 가 되고 3과 2 사이에서 끊어 읽으면 (×3)(2) 가 된다는 식으로 처리한다. * 3 2 라는 표기 자체가 currying을 반영하고 있으며, 이런 currying 덕에 여러 가지 함수를 사용해서 코드를 짜면서도 코드가 간결해지고, 풍부한 의미를 담을 수도 있게 된다.[12] 덧붙여 위의 * 3 2 는 그냥 설명을 위한 예시이고, 실제 하스켈에서는 (*) 3 2로 쓰거나 더 간단하게 *을 중위 연산자로 취급하여 3*2로 표기한다.

Currying과 연관된 집합론적 내용도 생각해 볼 수 있는데, 다음과 같다. A×B를 앞자리는 A의 원소, 뒷자리는 B의 원소인 순서쌍의 집합이라고 정의하고 D^C를 C에서 D로의 함수의 집합으로 정의하면, C^(A×B)와 (C^B)^A 사이에는 매우 적절한 일대일 대응이 존재한다. 이에서 집합의 기수 또는 농도(Cardinal number, cardinality) 의 지수법칙의 일부도 증명된다.

Currying 은 type inference 알고리즘에 의해 자동으로 수행되므로, 프로그래머가 골치썩일 필요 없이 알아서 적용된다. Currying 은 언뜻보기에 별 쓸모없어보이지만, 사실 이게 빠진 함수형 언어는 포인터 없는 C 언어라 봐도 무방할 정도로 핵심적인 위치를 갖는다.그래서 언어 이름도 하스켈

3.2. 순수 함수형 언어

보통 순수 함수형 언어의 가장 큰 특징으로는 부작용(Side Effect)이 없는 것을 꼽는다. 하지만, 사실 이것은 함수형 언어보다는 순수 선언형 언어(Declarative language)의 특징에 가깝다. 함수형 언어는 순수 선언형 언어의 하위 카테고리라 볼 수 있다.

순수 함수형 언어에서 함수는 수학에서의 함수와 같아서 부작용(또는 부수효과. Side Effect)이 없는 것을 의미한다. 즉, 순수 함수형 언어에서 함수의 결과값은 파라미터(parameter)로 넘겨진 입력값에 의해서만 결정된다. 여기서 말하는 부작용이란, 같은 입력임에도 불구하고 출력이 달라지는 것을 말한다. 예를 들어 자동차를 클래스로 구현했을 때, 주어진 파라미터만큼 자동차의 속도를 증가시키고 현재 속도를 출력하는, 절차지향적으로 작성된 accelerate() 함수를 가정해 보자. 예를 들어, accelerate(10)는 자동차의 속도를 10 증가시킨다. 자동차가 정지해 있을 때 accelerate(10)이 호출되면 자동차의 속도는 10 증가하므로 현재 속도는 10이고, 따라서 현재 속도인 10이 출력된다. 이때 한 번 더 accelerate(10) 함수를 호출하면 출력값은 20이 된다. 이처럼 state를 가지는 상태머신의 경우, 출력값은 입력 뿐만 아니라 머신의 상태 역시 출력을 좌우할 수 있다. 즉, 같은 입력인데도 결과가 달라지는 것이 바로 부작용이다.

하지만 순수한 함수들로 구성된 프로그램은 부작용이 없어 실행 순서의 영향에서 자유롭기 때문에 병행성을 가지기 좋다. 또한 루프문이 없고 제어문도 아주 적으며, 대부분의 경우 제어문을 사용할 필요가 없다. 대부분의 분기는 패턴 매칭과 가드에 의해 만들어진다. 그래서 OOP 쓰던 사람이 Haskell 처음 입문하면 정말 속터진다.

하스켈은 System.IO.Unsafe 모듈의 unsafePerformIO[13] 시스템 레벨로 뭐 할 때 빼고는 누구도 안 쓴다. 사실 일반적인 프로그램에서 쓰면 안되는 함수들이다. 등을 제외하면 순수 함수형 언어로 규정할 수 있다.

3.3. 느긋한 계산

느긋한 계산(Lazy Evaluation)은 계산이 필요한 순간까지 계산을 미루어 둔다는 의미다. 따라서 그때 그때 필요한 만큼만 계산하는 것도 가능하다. 이 느긋한 계산의 개념으로 인하여 하스켈은 프로그램에 무한의 개념을 쉽게 적용할 수 있다. 예를 들어 어떤 길이의 정수 제곱 리스트가 필요하다면 아래처럼 무한한 리스트를 만들게 해도 상관 없다.
-- 무한한 정수의 제곱 리스트를 만드는, 하스켈에서는 권장되는 방법(List Comprehension이라고 부른다.)
-- (보면 알겠지만, 수학에서의 집합 { x² | x∈{1, ..} } 와 유사하다.)
square = [x^2 | x ← [1..]]


유사한 배열을 만드는 JavaScript 코드.
#!syntax javascript
// 전형적인 무한루프
var square = []
var i = 1
while (true) {
  square.push(i*i);
  i += 1;
}

// ECMAScript 6의 제너레이터(Generator)를 이용한 버전
function* square_gen(){
  var i = 1;
  while(true){
    yield i*i;
    i++;
  }
}
var square = square_gen();
console.log(square.next().value);
console.log(square.next().value);
console.log(square.next().value);

일반적인 프로그래밍 언어에서는 직접 구현하기 상당히 귀찮지만, 하스켈에서는 기본적으로 값이 필요해지기 전까지 계산을 미루고 있다가 값을 요청할 때 계산을 해서 보내주기 때문에 아무 문제가 없다. 오히려 이렇게 정의해두면 필요할 때 필요한 만큼 동적으로 리스트를 생성하므로 유연한 프로그래밍을 위해 필요한 경우에는 무한을 사용하는 쪽이 권장된다.

계산 말고 패턴매칭에서도 느긋한 패턴매칭이라는 개념이 있는데, 느긋한 패턴매칭이란 패턴이 안맞아도 일단 참으로 가정해서 통과시키고, 그 패턴이 실제 사용될때가 되어서야 해당 패턴의 변수에 값을 대입하는것이다. 문법적으로는 ~ 를 앞에 붙여 ~pattern 정도로 사용하지만, 몇몇 경우에는 ~ 를 따로 붙이지 않아도 암묵적으로 적용된다.

3.4. 강력한 타입 추정

하스켈은 함수에 입력, 출력되는 타입에 대해 엄격하다. 즉, 하스켈에서 사용되는 모든 함수는 유일한 principal type 을 가지며, 그것이 실제로 그러한지 컴파일 타임에 체크를 하고 그렇지 않을 경우, 에러를 뱉는다. 이는 컴파일 타임에 상당한 수의 버그를 잡아낼 수 있음을 의미하며, 런타임시 그것들을 체크할 필요가 없으므로 런타임 성능향상에도 이점이 있다.

하스켈은 강력한 Hindley-Milner 타입 시스템을 사용하여 대부분의 경우, 타입을 명시적으로 지정하지 않아도 함수에서 사용된 데이터와 연산자 및 기타 정보를 통해 해당 함수의 타입을 자동으로 연역한다.[14] 물론, 예외상황이 있기때문에, 타입을 명시적으로 적어야만 하는 경우도 있다. 추상화 레벨을 낮추어 최적화하고 싶거나 관습적으로 논리적 버그를 막기 위해, 적을 필요가 없더라도 쓰는 경우도 있다.

3.5. 대수적 데이터 타입

대수적 데이터 타입은(Algebraic Data Type, ADT) 하스켈에서 새로운 데이터 타입을 정의하는 방법이다. C의 struct, enum, union 등과 유사하지만, 훨씬 유연한 사용이 가능하다. 다음의 예를 보자.

data Point = Point Int Int
data Bool = True | False
위는 정수 순서쌍 Point와 불 타입 Bool[15]을 정의하는 하스켈 코드이다. PointBool은 각각 C에서 structenum을 사용해 구현할 수 있다. 등호 왼쪽에 오는것은 타입의 이름이며, 등호 오른쪽에 오는것은 데이터 생성자이다. 데이터 생성자는 함수의 일종이며, 패턴매칭을 통해서 분해할 수 있다. 위의 예에서 Point타입은 Point :: Int -> Int -> Point 라는 생성자를 가지며, Bool타입은 True :: BoolFalse :: Bool의 두개의 생성자를 가진다.

한편, 하스켈에서 데이터 타입은 다른 데이터 타입을 파라메터로 받는것이 가능하다.
data Maybe a    = Nothing | Just a
data Either a b = Left a | Right b
data Pair a b   = Pair a b
data List a     = Nil | Cons a (List a)
Maybe는 nullable한 값을 표현하는 타입으로, Swift등의 Optional타입과 같다. Either와 Pair는 각각 sum type과 product type이라 불리는 것으로, Eithera, b중 한가지 값만 가질 수 있는 타입을 표현하며, Paira, b의 값을 동시에 가지는 타입을 표현한다. List는 lisp의 리스트 처럼 재귀적으로 정의되는 리스트 자료구조이다. Maybe, EitherList 타입은 C의 union과 유사한 방식으로 구현 되지만, 한 값을 여러가지 타입으로 다루는 것을 금지하므로 union와 달리 안전하게 사용할 수 있다. Maybe등은 그 자체로는 타입이 아니며, Maybe Bool 처럼 다른 타입을 적용해 타입을 얻을 수 있는데, 이처럼 다른 타입을 인자로 받아 새로운 타입을 만드는 타입을 타입 생성자라고 부르며 이는 타입 레벨 에서의 함수로 생각할 수 있다.

이러한 타입들을 구분짓기 위해서 하스켈은 kind라는 개념을 가지고 있는데, kind는 간단히 말해 타입의 타입이라고 볼 수 있다. 위에서 정의한 타입들은 Bool :: Type, Maybe :: Type -> Type, Either :: Type -> Type -> Type 등의 kind를 가진다.

이것이 끝이 아니라, 다음처럼 타입생성자를 파라메터로 받는 타입도 정의할 수 있다.
data Apply f a = App (f a)
Apply :: (Type -> Type) -> Type -> Type

또한 ghc 컴파일러는 이러한 타입시스템을 더욱 유연하게 확장하는 GADT등의 compiler extension을 제공한다.

3.6. Parametric polymorphism

OOP에서 일반적으로 사용되는 다형성에는 ad hoc polymorphism과 subtype polymorphism이 있다. ad hoc polymorphism은 함수 오버로딩과 같이 타입에 따라 다른 구현이 존재하는 것을 말하며, subtype polymorphism은 클래스의 상속관계를 통해 구현되는 다형성을 말한다. parametric polymorphism과 typeclass는 하스켈이 자랑하는(?) OOP 스타일 다형성의 대항마이다.

parametric polymorphism은 그 이름처럼, 다형성을 가진 함수를 타입 변수를 입력으로 받아 평범한 함수를 출력으로 내놓는 함수처럼 보는것이다.[16] 간단히 말하자면, 유저가 타입을 직접 정의할때 타입변수 a 같은것을 사용할 수 있고, 이것을 이용해서 하나의 타입선언으로 여러개의 타입에 맞게 사용가능하게끔 원소스 멀티유즈를 한다는 개념이다. 예를 들어
const :: a → b → a
const x y = x
는 타입 변수 a, b를 받아서 x :: ay :: b를 받아 x를 리턴하는 함수를 리턴하는 함수이다. 이 함수의 타입을 조금더 명시적으로 쓰면 const :: forall a b. a -> b -> a 인데, curry-howard correspondence에 따르면 universal quantifier ∀ 또한 함수처럼 여길 수 있다. 이는 앞에서 언급한 parametric polymorphism이 타입을 변수로 받는 함수처럼 여긴다는 맥락과 일치한다. 한편, 실제로 하스켈에서 다형성 함수를 사용할 때에는 이 타입 변수들이 자동으로 추론되어 적용되기 때문에 명시할 필요는 없다.[17]

그런데 하스켈의 타입 시스템은 어떤 형태로도 down casting을 허용하지 않는다. 따라서 하스켈에서 f :: a -> a의 가능한 유일한 구현은[18] f x = x, 즉 id함수 뿐이다. 이처럼 parametric polymorphism만을 가지고는 유용하게 사용하기 어렵기 때문에[19], 하스켈은 typeclass를 통해 ad hoc polymorphism을 지원한다.

하스켈이 down casting을 금지하는 것은 언어 디자인적인 이유가 있는데, 하스켈은 down casting을 금지하므로서 ad hoc polymorphism을 typeclass의 인스턴스 정의에 가둔다. 즉 typeclass의 함수가 아니면서 다형적으로 작성된 함수는 항상 똑같은 방식으로(uniformly) 작동하며, 이를 parametricity가 보존된다고 표현한다. parametricity를 보존하는 것은 또한 컴파일 이후에 타입 정보를 완벽하게 지우는 것을 가능하게 한다.

typeclass는 type들의 집합(사실 class)이라고 생각할 수 있다.[20] 예를 들어, 원소간 대소비교가 가능한 타입들에 대해 정의하고 싶다면, 대소비교 함수 lessThan :: a -> a -> Bool을 구현한 타입들 Ord a 로 묶는다. 그러면 Ord를 구현한 임의의 타입들에 대해 작동하는 함수 sort :: Ord a => List a -> List alessThan함수를 사용해 구현하는 것이 가능하다. '=>' 부분을 논리식에서의 implication 즉, if ~ then ~ 으로 읽는다면 'Ord a 이면 List a -> List a 이다' 정도로 볼 수 있다.

OOP 보다 유연한 시스템이기 때문에 그 방법과 접근방식만 살짝 다를 뿐, OOP 를 네이티브 OOP 프로그래밍 언어에 가까운 수준으로 간단히 에뮬레이션 하여 사용하는것이 가능하다. 예를 들어,
class (Eq a) => Ord a where ...

Ord a 라는 타입 클래스를 정의하면서, 동시에 이것은 Eq a여야 함을 전제하고 있기때문에 Ord aEq a의 부분집합[21]임을 말하는것이고, 이는 Ord a 클래스의 타입들은 Eq a 클래스의 모든 메소드를 사용가능하다는 것이다. 즉 OOP에서의 상속과 유사해진다.

typeclass는 OOP에서 보이는 interface와 유사한 개념이지만, 몇가지 차이점이 있다. 가장 두드러지는 차이점은 대부분의 OOP언어에서 interface가 타입 선언과 interface의 구현을 동시에 해야하는 것에 비해, typeclass의 instance는 코드의 아무 위치에서나 선언할 수 있다는 점이다. 이는 서로 다른 라이브러리에서 정의된 typeclass와 데이터 타입의 인스턴스를 만드는등 훨씬 유연한 사용을 가능하게 한다. 또한 interface가 Type kind에 대한 interface를 선언하는 것만이 가능한 것에 비해, 하스켈의 typeclass는 임의의 kind에 대한 class를 선언하는 것이 가능한다. 아랫문단의 Monad또한 Type->Type kind에 대해 정의된 typeclass이다.

하스켈은 기본적으로 dynamic dispatch를 지원하지 않지만, existential type이라는 compiler extension을 통해 비슷한 기능을 제공한다. existential type은 원래의 타입에 대한 정보를 잃으면서 특정 타입클래스의 인스턴스 정보만을 유지할은 수 있게 해준다. 예를들어 exist a. Show a => a타입은 이 타입이 Show인스턴스를 가진다는것은 알 수 있지만, 그 타입이 본래 무슨 타입인지는 알수 없으며 패턴매칭 또한 불가능하다. OOP와 비교하자면 up casting은 가능하지만 down casting은 허용하지 않는 셈이다. 따라서 existential type은 parametricity를 해치지 않는다.

3.7. 모나드

파일:상세 내용 아이콘.svg   자세한 내용은 Haskell/모나드 문서
번 문단을
부분을
참고하십시오.
>>=[22]
모나드는 그냥 자기 함자 범주의 모노이드일 뿐인데, 대체 뭐가 어렵다는 거야?
- 1990, 필립 와들러 (하스켈 설계 책임자)[23]
지금까지 좋은 점만 나열했는데, 그럼에도 하스켈 점유율이 바닥을 치는 이유가 있고, 그 이유는 물론 여러가지가 있을 수 있겠지만, 지금 이 항목에서 이야기하는 모나드가 커다란 부분을 차지하고 있다고 해도 과언이 아니다. 모나드는 수학의 범주론(Category theory)에서 사용되는 개념을 가져와 사용하는 것이다. 사실 하스켈을 배우던 사람 중 상당수가 모나드를 수학적으로 이해하려다 멘붕 후 접는다고 봐도 될 정도로 개념적 이해까지의 난이도가 높다.[24] 하지만, 애시당초 수학의 모나드와는 미묘하게 다른데다 실제 사용례 위주로만 습득해서만 써도 별 문제 없으며 쉽게 쓰라고 do 같은 문법도 지원해주므로 수학적인 개념이 이해가 안 간다고 그다지 난감해 할 것은 없다.

사실 설명만 잘 해도(번역본) 포인터보다 이해하기 쉬운 것이 모나드이다.

가령 흔히 쓰이는 리스트, 자바스크립트의 Promise도 모나드다. 그중에 리스트를 예시로 들어보자. 오류가 있는 값은 빈 배열 [], 올바른 값은 숫자 하나가 든 배열로 표시하기로 했다고 해 보자.
-- '--' 문자는 하스켈의 주석
나누기 :: Integral a => a -> [a] -- 나누기의 타입은 정수형 a를 받아 정수형 a 리스트를 반환하는 함수
나누기 0 = [] -- 0으로 나누면 오류값을 뜻하는 [] 반환
나누기 x = [24 `div` x] -- 백틱(`)으로 감싸면 div 24 x에서 24 `div` x같이 중위 표현이 가능해진다.

이런 식으로 0으로 나누기 같은 오류를 예외(exception)를 던질 필요 없이 표현할 수 있다. 이 규칙을 따라 입력받은 숫자를 2배 곱해주는 곱2 함수를 만들면
곱2 :: Num a => a -> [a]
곱2 x = [x * 2]

배열1 = [2]
값1 = head 배열1 -- 2. head는 배열의 첫째 값을 반환하는 함수
배열2 = 곱2 값1 -- [4]
값2 = head 배열2 -- 4
배열3 = 곱2 값2 -- [8]

연산을 할 때마다 값이 배열 안에 들어가기 때문에 사용이 번거로워진다. 이를 돕기 위한 함수를 만들어보자.
배열에 :: [t] -> (t -> [a]) -> [a]
[] `배열에` _ = []
[x] `배열에` f = f x

[1] `배열에` 곱2 -- [2]
[1] `배열에` 곱2 `배열에` 곱2 -- [4]
[1] `배열에` 곱2 `배열에` 곱2 `배열에` 곱2 -- [8]

배열에 쌓인 값이더라도 편리하게 쓸 수 있게 되었다. 그런데 어딘가 비슷한 것 같다.
(>>=) :: Monad m => m a -> (a -> m b) -> m b
바로 >>= 연산자의 타입이 배열에 함수와 똑 닮았다.
[1] >>= 곱2 -- [2]
[1] >>= 곱2 >>= 곱2 -- [4]
[1] >>= 곱2 >>= 곱2 >>= 곱2 -- [8]

이렇게 값을 담는 상자가 모나드이고, 상자 안의 값을 꺼내 연산을 하고 다시 상자 안에 담아주는 함수>>=이다.
더 자세히 설명한 링크

막연히 어렵다 하면 대중적인 관점에서 프로그래밍 언어의 최대 난적으로 불리우는 C언어의 포인터같은 것인가 생각이 들텐데, 짧은 지문에서 모나드의 정확한 개념적 설명은 무리고, 여기서는 대충 '무엇이 어려운지' 감을 잡기 위해 범주론에서 모나드란 게 무엇인지 개념을 훑어보도록 한다.

범주(Category)는 몇 가지 규칙을 만족시키는 대상(Object)과 그 대상 사이의 사상(Morphism)[25]으로 정의된다. 그런데 저 규칙이란 것이 결합법칙 성립, 항등사상[26]의 존재 2개밖에 없다. 이렇게 규칙이 아주 일반적이어서 사실 거의 모든 것들이 저 규칙에 해당된다. 프로그래밍 언어도 예외가 아니다. 순수 함수형 프로그래밍 언어에서 데이터 타입을 대상으로 놓고, 그 데이터 타입 사이의 함수(프로그램)들을 사상으로 놓으면 이것도 하나의 훌륭한 범주가 된다. 여기서, 다시 두개의 범주 사이의 구조를 생각해볼 수 있는데, 범주론에서는 몇가지 규칙을 만족시키는 저런 구조를 함자(Functor)라 한다.[27] 이 규칙 역시 아주 관대해서 함수형 언어들에서 스탠다드로 사용되는 map 같이 함수 자체를 인풋으로 받는 고차함수(higher-order function) 몇몇이 이에 해당된다고 볼 수 있다. 여기서, 다시 두개의 함자 사이의 변환을 생각해볼 수 있겠는데, 범주론에서는 몇가지 조건을 만족하는 저런 변환을 자연 변환(Natural transformation)이라 한다. 이 조건 역시 상당히 관대한데 unit 같은 함수가 이런 조건을 만족시킨다. 여기서 다시 저 자연 변환이 관련된 몇가지 조건을 만족하는 함자 쌍을 수반함자(Adjoint functor)라 하는데, 모나드(Monad)는 바로 이 수반함자쌍의 Composition 이다. 여기까지 오면 뭔소린지는 몰라도 뭐가 어려운건지 대충 이해는 갈 것이다. 그냥 함수의 함수의 함수의... 식으로 order 가 올라가고, order 가 올라갈 때마다 조건이 붙고... 최종적으로 그래서 그게 대체 무엇인가 오리무중에 빠지는 난점이 있다. (이것은 모나드를 상당히 어렵게 설명한 것이므로 크게 걱정하지 않아도 된다. 훨씬 더 쉽게 추상화하여 설명이 가능하니 찾아보기 바람)

다행히, 함수형 언어에서 사용되는 모나드 개념은 범주론을 적극적으로 사용하기보다는 아이디어만 빌려온 수준이며, 때문에 이런 쪽 전공이 아니라면 쓸데없이 시간만 잡아먹을 수학적 이해는 그냥 포기하고 사용례로 실용적인 부분만 습득하는 것도 적극적으로 권장된다.

실용적인 관점에서 모나드의 가장 중요한 용도는 Side-Effect가 있는 Impure Function을 Pure Function인 것처럼 다루는 것이다. 대략적으로 설명하자면, Side-Effect는 보통 외부 상태(이하 State)가 있고 이것에 접근하여 생긴다. 반대로 Side-Effect가 없는 Pure Function은 모든 정보가 인수로 전달되어 외부 상태에 접근하지 않는다. 그렇다면 어떤 함수에 인수로 State를 전달하고 그에 대한 결과로 State를 리턴한다면 외부 상태에 접근을 하지 않아도 되며 pure function이 된다. 이 때 State 외에도 결과값 a도 있을 것이다. 즉, 입력이 State면 출력이 (a, State)가 되는 것이다.(이것을 State→(a, State)라고 나타낸다.) 이러한 함수는 다양하게 있을 수 있으며 그 결과값도 a 뿐만 아니라 여러가지가 있을 것이다. 그런데 하스켈은 순수 함수형 언어이기 때문에 함수를 합성하는 방식(f 와 g를 합성하면 g(f(x))가 되는 것처럼)으로 작성을 해야한다. 그런데 만약 f가 State→(a, State), g가 State→(b, State)라면 f의 출력과 g의 입력 타입이 맞지 않게 된다. 이 때 f의 결과값인 (a, State)를 a와 State로 분리해서 a는 g와 결합하여 새로운 함수 g'를 만들고 State를 이 새로운 함수 g'에 전달하는 방식을 사용하게 된다. 이렇게 결합을 해주는 것이 bind(Haskell에서 >>=연산자)라고 하고 결합되는 것을 monad라고 한다. 여기서 끝이 아니라 필요하다면 함수를 더 잇는 것도 가능하며, 이렇게 여러 개를 이으면 거대한 하나의 Pure Function이 된다. (예를 들어서 위의 경우 f와 g를 결합하면 입력이 State, 출력이 (b, State)인 함수가 된다.)

이것을 조금 다른 관점에서 보자면 어떤 외부 상태 State에 대해 f, g가 순차적으로 실행하는 순수한 함수가 생긴다는 것이다. 다르게 말하자면 명령형 언어처럼 순차적으로 함수를 호출한다는 것이다. 그러다보니 하스켈에서는 모나드를 이용하여 이러한 기능을 구현하게 된다.

즉, 하스켈은 순수 함수형 언어이면서 동시에 모나드를 이용해 정의되는 명령형 언어라는 서브언어를 내부에 갖고있는셈이다. 그렇다면 반대로, C++ 이나 파이썬같이 명령형 언어이면서 함수형 표현을 지원하는 언어와 다른 것은 무엇인가... 생각이 들법도 한데, 모나드를 이용해 하스켈 내부에 정의되는 명령형 언어 시스템은 순수성(Purity)를 훼손하지 않아 참조 투명성(Referential transparency)가 유지되며 동시에 명령형 언어 부분과 함수형 언어 부분이 엄격하게 구분된다. 일반적으로, 명령형 언어 베이스에 함수형 표현을 추가한 언어들은 말그대로 저런 '표현'정도를 지원하는 정도라, 저런 구분이 존재하지 않고 섞이는 경향이 있다.

사실 모나드는 함수형 패러다임을 수용하는 언어라면 모두 이를 구현할 수 있다. 하스켈에서도 모나드는 언어 레벨에서 구현한 구조가 아니라, 수많은 타입 클래스 구현 중 평범하고(?) 조그마한(!) 하나이다.[28] 다만 하스켈에서는 이를 보다 적극적으로 언어의 특징으로 내세워서 적용하고 있을 뿐이다. 그 덕에 많은 사람들이 멘붕을 하고 있기는 하지만... 여담으로 모나드를 실용적으로 구현하여 사용하는 대표적인 분야가 바로 JavaScript로 작성된 여러 프레임웍이나 라이브러리 중에서 Ajax를 처리하는 부분이다. 특히 Promise와 ES7에 추가된 async/await 문법은 하스켈의 모나드와 do이다.[29] 여기에 모나드를 적극 적용하는 이유는 다른 것이 아닌, 이것을 적용하면 노가다 양이 줄고, 사용하기 편하며, 보기도 좋기 때문.

4. 수학과의 관련성

위의 내용을 읽어 보았다면 알겠지만 각종 수학 이론을 이용해서 언어를 만들어 보자는 의도로 만들어진 언어이므로 수학과의 관련성은 어느 정도 있다. 그렇다고 해서 하스켈로 프로그래밍을 하기 위해 기저의 수학을 잘 알고 있어야 하는 것은 아니며, 실제로도 많은 하스켈 프로그래머들이 하스켈의 바탕이 되는 전공수학 및 논리를 따로 배우지 않고도 능히 현업에 종사하고 있다. 알면 도움되지만, 모른다고 해서 문제가 되는 것은 아니다. 쉽게 생각하면 한없이 쉽고 어렵게 생각하면 한없이 어려워진다고 보는 것이 맞다.

프로그래밍을 수학, 특히 수리논리에서 배운사람은 보통 람다대수(λ-calculus) 혹은 class of recursive functions 라는 함수체계를 통해 배운다. 당연히 이 시스템도 사이드이펙트는 전혀 없으며[30] 심지어 built-in 조건문이나 루프문도 없다. 하지만 수학답게(?) 여러가지 트릭으로 존재하는 알고리듬은 모두 표현이 가능하다. 하스켈에도 람다대수가 어느 정도 반영되어 있으며, 아예 λ식이라는 개념도 있다.[31] 함수형 언어 하면 재귀부터 떠올리게 마련이고, 재귀에만 어느정도 익숙해지면 문제가 없을것같은 상상을 하는 경우가 많은데, 재귀보다 마스터하기 어려운것이 람다대수 의 η-reduction 과 partial application 을 이용한 여러가지 트릭들이다. 보통 함수를 "정의"할때 x y 등의 파라미터(parameter)를 명시하는데, η-reduction 과 partial application을 사용하면 많은 경우 저런 파라미터를 명시적으로 작성하지 않고 함수 기호만 갖고 함수를 정의하는게 가능해진다(point-free). 또한, (익숙한 사람들에 한해서) 상당히 직관적이며 코드 길이도 괴상할 정도로 축약되는 경우가 많아 유용성도 꽤 되는편이지만, 익숙하지 않은 프로그래머의 눈에는 거의 암호문이 되어버리므로, 함수형 언어를 사용하려면 제대로 알아두는 것이 좋다.

하스켈에서 사용하는 타입 시스템은 inductive structure[32] 를 갖으므로, 수학에서 사용되는 type theory 와 보다 유사하다. 하스켈의 끝판왕이라 불리는 monad 역시 수학의 category theory 에서 가져왔으며, Co- 로 시작되는 여러가지 개념들도 역시 수학에서 쓰이는것들이다.[33] 그리고, 가장 중요한 함수형 구조 자체가 위에 언급한 수학에서 알고리듬을 표현할때 자주 쓰인다. 덕분에 수학(수리논리)와 친근하다면 함수형 언어가 결코 낯설지 않을것이나, 모른다면 익숙해지기까지 진입장벽도 은근히 높은편이고 언어를 깊게 이해하기도 상당히 힘들어진다. 다만, 언어 자체에 대한 깊은 이해가 없더라도 프로그래밍을 하기에 큰 무리는 없으므로 부담을 갖지는 말자. 애초에 C++Java 같은 곳에 사용되는 컨셉들도 이론적으로 깊게 들어가면 수학 이상으로 충분히 복잡하다. 하지만, 프로그램을 만들려는 목적으로 그런 이론들을 잘 알지 못하고 개발을 한다고 해도 결과물을 만들어 낼 수 없는것은 아니다. 물론, 여기에는 각 언어 제작자들이 최대한 관련지식 없이 직관적으로 프로그래밍이 가능하도록 디자인을 하는것도 한 몫 한다. 하스켈 역시 저런 이론적인 내용이 아니라, 함수형 표현이나 재귀 등에 익숙해지는 것이 관건이다.

정리하자면, 컴퓨터의 탄생에는 20세기 초반의 수리논리학의 개념들이 영향을 주었는데, 하스켈은 더 나아가서 20세기 후반 이후[34]의 수학 및 수리논리학의 개념들까지 이용하고 있다고 보면 된다. [35]

5. 안 써요

파일:Haskell.jpg
Haskell 팬들에게 Java는 덩치만 크고 무너지기 쉽고, C는 (포인터 잘못 다뤘다간 터지는) 시한폭탄 같고, PHP는 사고가 터지면 대형참사급이고, Ruby는 (동적 타입 언어이니) 까 보기 전까지는 모른다고 인식된다는 것. 반면에 Haskell은 전체적으로 인식이 천재 아인슈타인. LISP는 뭐...

파일:haukellxkcd.png
Avoid success at all costs.[36]
- Simon Peyton Jones[37]
인기는 두말할 것도 없이 바닥이다. 표현력이 좋고, 코드가 간결하며, 알고리즘의 구현이 직관적이고, 부작용이 없고, 동시성이 보장되는 건 좋은데 애초에 함수형 언어라는 게 일반적인 프로그래밍 언어와 체계가 너무나 달라서 쉽게 익숙해지기도 어렵고 현실적으로 사용하기 어려운 점이 많다. 그 결과로 시도하는 사람 자체가 적어서 잘 아는 사람이 많지 않으니 사용 예가 적고, 사용 예가 적으니 배우려는 사람이 적다...의 무한 반복. 당장 개중에 쉬운편에 속하는 위의 무한 제곱 리스트 코드도 LISPErlang을 공부한 사람이 아닌 이상 별다른 학습 없이 이해하기가 어렵다. 그나마 LISP가 함수형 언어 중에서 비중이 크다 보니 상당히 밀리고, 산업계에서는 Java와 호환되며 OOP도 지원하는 Scala가 함수형 언어로서 훨씬 인기를 끌고 있다.

오래된(?) 프로그래밍 격언중에 You can write Fortran in any language라는 게 있다. 포트란은 최초의 (명령형)하이레벨 언어로 여겨지는 프로그래밍 언어인데, 오늘날 세대에게는 보통 C 언어가 저 위치에 들어간다. 말하자면, 엄청나게 많은 기능을 탑재해서 거의 새로운 언어라고 봐도 될 정도인 C++에서도 그런거 깡무시하거나 아주 기본적인 구색만 맞춰놓고 실상은 C 식으로 코딩을 하는 사람이 많고, 역시 마찬가지로 큰 혁신을 가져왔던 Java에서도 그냥 C 식으로 코딩을 하는[38] 프로그래머가 많다는 소리다. 바꿔말하면, 대부분의 프로그래머들은 하스켈이나 기타 다른 패러다임의, C 식으로 코딩이 불가능한 언어는 거들떠도 보지 않는다는 소리. 사실 여러가지 다 고려하면서 '제대로' 프로그래밍하는 것과 대충 돌아가게만 하는 것과의 난이도 차이는 상상을 초월할 정도로 크지만, 대부분 오너의 눈에는 들어가는 비용만 크게 보이고 실제 프로그램은 그냥 돌아가기만 하면 별반 차이가 없어보이니 당연한 결과라 하겠다.

또한, 언어 자체에 대한 비판도 있는데, 역시 함수형 언어인 SML의 저자인 Robert Harper가 정면으로 나서 비판을 하였다. Laziness와 Type class 시스템에 대한 비판인데, 흥미가 있는 사람은 직접 읽어보자. laziness type class 다만, 이 사람은 CS 교수이면서 관심분야는 proof theory, category theory 등 수리논리 쪽으로 쏠려있어, 수리논리 전공으로 꽤 지식을 갖고 있어야 이해가 가능하다는게 함정. 물론 CS 자체를 응용수리논리학으로 보는 사람들도 있을 정도로 한걸음만 넘어가면 부딪히는 가까운 분야이기는 하다.

구미권에서도 인기는 바닥을 긴다. 게다가 함수형 언어에서 자주 사용되는 재귀문은 루프문에 비해 코드를 짧고 간결하게 만들어 가독성을 높여주는 장점이 있지만, 그 재귀코드를 짜는 것은 상당한 두뇌노동을 필요로 하는 단점도 있다.(수학에서 아름답고 간단한 공식일수록 탄생하는 과정(증명)은 괴로운 경우가 많은 것과도 어느정도 상통한다.) 이도 물론, 재귀코드에 많이 익숙해지면 어느정도 해결될 문제기는 하지만 아무리 그래도 일반적인 Procedural language처럼 아무 생각 없이 일단 써내려가고 실행-디버깅으로 완성하는 방식은 불가능하다. 사실 서로 재귀(Mutual recursion)와 간접 재귀(Indirect recursion)를 자연스럽게 생각하며 쓸 수 있다면, 그냥 써내려가면서 그때그때 테스트 돌리며 프로그램을 짤 수 있다. 그게 가능하게 뇌개조를 시키는 것이 문제일 뿐. 혹자는 프로그램 구조를 완전히 머리속에 디자인한 후에야 코딩이 가능한 재귀코드가 오히려 프로그래밍 습관에 도움이 된다고 하지만, 이미 C/C++/Java 등에 익숙한 사람들에게는 오히려 커다란 진입장벽이 될 뿐이다. 현재 Haskell은 제대로 된 입문서적을 찾기도 힘든 상황이니... 그나마 코드에 수학적인 요소가 많고, 코어는 넘쳐나는 High Frequency Trading Firm 같은 금융계에서 소규모적으로 쓰는 정도.

그 외에 프로페셔널한 목적보다는 프로그래밍 자체를 좋아하는 애호가 혹은 C/C++/Java 등을 메인언어로 사용하는 프로그래머들이 함수형 언어 혹은 함수형 패러다임에 대한 컨셉을 이해하기 위해 자주 선택하는 언어이다. 수학과 관련성이 높기 때문에 논리학과 프로그래밍의 상관관계 및 타 언어에 비해 흥미로운 백그라운드 이론들이 많이 존재하며 재미있는 트릭도 많이 적용가능하고, 수학적인 관점에서 보다 아름다운(?) 코드를 만드는 것이 가능하기 때문이다. 무엇보다 현재 등장한 메이저 함수형 언어 중에선 가장 엄격하게 함수형 패러다임을 따르는 언어이기 때문에[39] 좀 색다른 것을 추구하는 프로그래머들도 많이 찾는다. 가끔 대수기하학이나 호몰로지 대수, 또는 수리논리학 같은 대학원 테크트리를 밟는 수학과 대학원생들이 범주론을 공부하다가 이 물건을 건드리며 주화입마 입덕하는 경우도 있지만, 애초에 수학 전공자들도 소프트웨어 개발 직군으로의 취업을 염두에 두고 진득하게 프로그래밍을 공부한다면 수리논리학으로 접근하게 되는 Haskell을 건드리느니 차라리 머리를 비우고 Python, JavaScript 같은 일자리 구하기에 좋은 테크트리로 첫걸음을 떼는 것이 적절하기 때문에 Haskell을 통한 소프트웨어공학 입문은 딱히 권장되지는 않는 편이다.

6. 하스켈로 만들어진 프로그램들

당연하겠지만, 윈도우보다는 프로그래밍 애호가들의 장인 유닉스 쪽에서 많이 사용된다. 대표적인것은 darcs 라는 버전 관리 시스템과 xmonad 라는 타일링 윈도우 매니저이다.

xmonad는 dwm 의 하스켈 버전이다. dwm 의 특징은 1000 줄 미만의 단일 C 프로그램으로, 세팅도 헤더파일을 변경하는식으로 이루어지는 하드코어 윈도우 매니저인데, xmonad 도 역시 하스켈 소스파일을 직접 변경하는식으로 세팅이 이루어지는데, 하스켈이라는 고생산성 언어를 사용한덕에 dwm 에 비해 지원하는 기능이 훨씬 많다. 다만, 사용하기 위해서는 200 메가가 넘는 ghc 라는 의존이 딸려오기 때문에 꺼리는 사람들도 종종 있는편. xmonad 의 코어는 매우 주의깊게 디자인되었고, Coq 라는 Theorem prover(수학적 증명 보조 프로그램, 4색정리도 이 프로그램의 도움으로 증명되었다.)까지 사용하여 검증하는것으로 유명하다.

darcs 는 하스켈 프로그램답게 구조와 디자인면에서는 많은 찬사를 받는 버전 컨트롤 시스템이다. 하지만, 퍼포먼스에서 C 로 짜여진 git 에 비해 많이 밀리기 때문에 현재는 알다시피 Git에 거의 점령당한 상태. 하스켈 컴파일러인 GHC조차 매우 거대해진 몸집을 감당하지 못해 2011년 darcs에서 Git으로 넘어갔다. 새로운 이론적 배경·디자인을 가진 Camp가 준비되고 있다. Camp는 아직 희망이 보이지 않고, 그에 반해 darcs 는 http://darcs.net/Benchmarks에서 볼 수 있는 것처럼 지속적으로 퍼포먼스가 개선되고 있다. Git에게 밀리는 것은 darcs는 GitHub만큼 훌륭한 접근성을 제공하는 open source server가 없기 때문(...)이다.

문서 상호 변환 프로그램으로 프로그램/라이브러리로서 Pandoc이 있다. 매우 널리 쓰이는 프로그램이지만, 주로 backend로 사용되기 때문에 일반 유저는 그런게 있는지는도 모른다. 소스를 직접 compile하다보면 가끔 요구되는 것을 볼 수 있다.

Haskell로 만든 운영체제로, House가 있다. 함수형 언어로 시스템 프로그래밍을 하는 일종의 도전과제 비슷한 개념이지만 꽤 잘 작동한다. 관심있는 사람은 받아서 분석해보는 것도 괜찮을듯. 다만 현재의 컴퓨터 구조로는 아직 함수형 언어의 장점과 성능을 살리기 어렵기에 느려터진 것은 감수해야 한다.

2015년에 공개된 오픈소스 프로젝트로는 페이스북에서 공개한 스팸 필터[40]인 Haxl가 있다. 개발 책임자의 말을 빌면, 기존의 C++ 베이스로 작업하던 FXL[41]이라는 물건으로는 물밀듯이 밀려오는 스팸을 도저히 감당할 수 없어서 포기하고 하스켈로 개발을 시작했다고 한다. 결과적으로 실제 상황에서 20~30% 정도의 성능 향상을 얻을 수 있었고, 특히 복잡한 문제일수록 더 빨라졌다고 한다.[42] 다만 하스켈 개발자는 구하기 어려워서, 언젠가 Haxl을 C++로 포팅하는 수순을 밟을지도 모른다고. 실제로 이번 시스템 개발과정에만 하스켈 언어·컴파일러 톱 디자이너만 두명이나 참가했다(...). 인터뷰 # # 하스켈로 뭘 했는지 간단히 살펴볼 수 있는 슬라이드

2017년 10월 1일 발행된 암호화폐 에이다의 블록체인 카르다노가 하스켈로 작성되었다. 카르다노가 디앱의 스마트컨트랙트를 필수로 요구하는 플랫폼 블록체인을 지향하는데도 불구하고, 그걸 하필 하스켈로 만들었기에 업데이트용 하드포크가 항상 지연되어왔다는 지적을 5년간 받고 있다.[43]

2019년 GitHub에서 개발한 소스코드 분석 프로그램 Semantics#가 하스켈로 작성되었다. 왜 하스켈을 선택했는지 여기에 잘 나와있다.

7. 기타

하스켈 스크립트는 GHC로 완전히 기계어 컴파일을 할 수 있고 GHCi의 가상기계 인터프리터로도 작동 가능하다. 또한 스크립트 중 .lhs라는 확장자는 일반 텍스트가 메인이고 코드를 별도로 처리해서 작성했다가 컴파일 하는 방식이다. 기본적으로 뭘 만들어도 코드 자체는 상대적으로 짧게 만들어지기 때문에 주석이 메인인 코드 형식이 생긴 것이다. 다만 haddock을 통한 문서화가 주류가 되면서 .lhs는 예제코드를 만들기 위한 용도 정도로나 사용되고 있다.

하스켈을 배울 수 있는 웹사이트로는 http://learnyouahaskell.com/chaptershttp://book.realworldhaskell.org/read/ 등이 있다. 다만 이곳의 내용은 프로그래밍 경험이 있는 사람을 대상으로 쓰여 있으므로 초심자가 이것만 보고 하스켈을 접하기에는 어려움이 크다.

하스켈 전용 IDELeksah[44] 또한 하스켈로 개발됐다. macOS용 IDE인 Haskell for Mac앱 스토어에서 구입할 수 있다. 주로 텍스트 파일을 작성하고 ghc로 컴파일하는 번거로움이라곤 하지만 테스트할 땐 :l 하나면 끝이다을 일반 개발 환경처럼 편하게 해준다. IntelliJ IDEA에도 IntelliJ-Haskell이라는 이름의 플러그인이 있다. IntelliJ 플랫폼에 힘입은 강력한 코드 컴플리션 기능과 리팩토링 기능을 제공한다. 그 외에 Visual Studio Code, Emacs 등의 텍스트 에디터 역시 플러그인 형식으로 하스켈 코드 편집을 지원한다.


[1] 정확히는 디버깅 용도를 위해 존재하는, 일반적으론 쓰면 안되는 함수들을 제외하면 순수하다.[2] Glasgow Haskell Compiler[3] 상업적으로 어느정도 성공을 거두었다.[4] 참고로 이 아저씨의 퍼스트네임 뿐만 아니라 미들네임과 성을 가지고도 프로그래밍 언어가 나와있다.(...)[5] 일부러 만우절을 골라서 발표했다. 그 이후로도 하스켈 98 보고서나 관련된 책들을 만우절에 발표하곤 했다.[6] 당시에는 IO 처리에 악명높은(?) 모나드 대신 Stream model이 사용되었다.[7] 2003년, 하스켈 98 보고서의 개정판이 나왔지만 개념적으로 바뀐 사항은 없고, 일부 작은 오류에 대한 수정과 애매한 표현등을 다듬은 정도였다. 즉 언어의 개정판이 아니라 어디까지나 보고서의 개정판. 논문 5p를 참조.[8] 요즘에는 Purity(Immutable), Lazy evaluation 등은 Apache Spark의 주요 특징으로서 내세우는 등 극단적이라고까지 보기는 어려워졌다.[9] 실제로 하스켈은 함수의 파라미터를 하나만 선언할 수 있다. 그 이상의 갯수의 파라미터를 가진 함수는 배후에서 커링으로 처리된다.[10] 그래서 수학적으로 엄격하게는 *(m,n)=(×(m))(n) 이 맞다. 그렇지만 가독성 문제가 있어 생략.[11] 이처럼, 함수의 출력이 함수가 되는 것을 얼마나 적절히 활용할 수 있느냐가 하스켈로 얼마나 괜찮은 코드를 짤 수 있는가를 말해주는 한 요소가 된다.[12] 여러 개의 함수를 사용하면서 f(x), g(x,y)와 같은 방법으로 쓴다면 앞 주석의 (×(m))(n) 처럼 괄호가 한없이 불어나게 마련인데, currying 덕에 괄호를 최대한으로 생략하면서도 코딩할 수 있게 된다. 따라서 여러 개의 함수를 사용하더라도 덜 복잡하게 보이고, 따라서 여러 가지 함수 여러 개를 쓰고 싶은 만큼 마음껏 쓰는 것도 할 만해진다.[13] 하스켈 위키에서는 아예 'Dark side of IO monad' 단락에 포함시켜 놨다(...).[14] C++11이나 다른 많은 언어들(스칼라 등)에 도입된 타입추론 기능은 지역 추론 기능으로, 하스켈이나 ML의 전역 추론과는 타입 추론이라는 것을 제외하고는 목적, 작동 방식 등 많은 면에서 다르다.[15] 대수학에서의 그 불 대수 이다.[16] 이 개념은 상당히 강력해서 C++의 템플릿을 포함한 다양한 프로그래밍 언어들에 전파되었다. 대표적인 용례가 제네릭 프로그래밍의 꽃이라고 불리는 STL이다.[17] 이를 명시적으로 적용할 수 있는 컴파일러 확장도 존재한다.[18] 사실 undefined의 존재로 인해 엄밀히 말하면 유일하지 않다.[19] 어떤 값에 아무런 제약이 없다면, 그 값을 가지고 무엇을 할 수 있는지 및 무엇을 할 수 없는지를 결정할 수가 없으므로 함수가 받은 그대로 돌려주는 일밖에 할 수 없는 것이다.[20] 수학적으로 모임(class)은 집합의 상위개념이다. 수학에서 모든 집합의 집합같은 것은 존재하지 않지만, 모든 집합의 클래스 개념은 받아 들인다.[21] 물론 엄밀히는 부분 모임, 즉 서브클래스(subclass)가 된다.[22] bind라고 부른다.[23] ...가 한 말로 알려져 있지만, http://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html에서 농담으로 써놓은 것이 퍼진 것이고, 실제로는 범주론 교과서 MacLane, Categories for the Working Mathematician에 나오는 글귀다.[24] 학부 수준 이상 넘어가면 수학과 과목 중 가장 어려워지는 게 대수학이다.[25] 함수를 추상화한 것이 사상이므로 그냥 함수라고 생각해도 된다. 정확히는 집합들을 대상으로 하는 사상이 함수이다.[26] 역시 항등함수(입력값을 그대로 돌려주는 함수)로 생각해도 좋다.[27] 하스켈에도 Functor 타입 클래스가 존재한다. 다만 하스켈의 Functor는 범주론에서 다루는 자기함자(Endofunctor)에 해당하는데, 이는 하스켈에서 다루는 범주가 Hask하나 뿐이기 때문에 모든 함자가 자기함자이기 때문이다.[28] 실제로 모나드와 그 메소드를 정의하는 코드는 코멘트를 제외하면 8줄, 100글자도 안된다. 물론 Functor, Applicative등을 계승하고 있지만, 그 또한 모나드만을 위해서 만들어진 것도 아니니 과장은 아니다. 게다가 정의도 매우 짧다.이 8줄 정도 밖에 안되는 정의를 이해하지 못해서 많은 사람들이 죄절하고 있는 것이다. 읽어보면 당연한 내용만 정의되어 있고, 거기에다가 이해를 돕기 위한 주석이 코드의 4배 이상이다.[29] 사실 JavaScript의 Promise는 모나드 법칙을 만족하지 않는다. 하스켈을 제외한 언어에서 모나드 법칙까지 만족하는 진짜 모나드를 사용하는 경우는 드물다.[30] 말그대로 수학의 함수이다.[31] 사실 하스켈뿐 아니라, 모든 함수형 언어의 이론적 기반이 람다대수 라고 봐도 과언은 아니다.[32] 수학적 귀납법을 떠올리면 간단하다. 베이스가 되는 객체들이 있고, 그 객체들을 이용하여 A, B 가 타입이면 A→B 도 타입이다같은 정의를 주면, A 가 타입이면 A→A 가 타입이고, A→A 가 타입이면 (A→A)→A 혹은 (A→A)→(A→A) 가 타입이고...하는식으로 죽죽 나간다. 이런 정의를 inductive definition 이라 칭하며, 이런 시스템은 inductive structure 라고 한다. 가장 대표적인 예시는 당연히 자연수이다.[33] Co- 로 시작되는것들은 mathematical dual 이라 하며, '대비되는 개념'정도로 이해할 수 있다. 예를 들어, data 의 dual 인 Codata, 재귀(Recursion)의 dual 인 Corecursion, 귀납의 dual 인 Coinduction 등이 있다. dual 을 구하는 방법은 그에 대한 정확한 수학적 정의를 논리식으로 풀어서 변환하는 방법이나 Category theory 로 정의한 후, arrow 의 방향을 바꿔서 얻어지는경우가 많다.[34] 범주론이 대수학, 위상수학을 넘어 공리적 집합론에 버금가는 중요성을 갖는 수학기초론에 가까운 분야로 대두된 것은 아무리 빨리 잡아도 1960년대였다. 니콜라 부르바키가 현대수학을 집대성할 때에도 범주론을 적극적으로 써먹어야 하느냐 마느냐로 키배가 끊이지 않았던 시절이었으며, 현재도 범주론은 공업수학 같은 속성 코스가 아닌 수학과 학부 고학년생 내지는 대학원생들이 공부하는 고급과정에서나 처음 등판하는 내용이다.[35] 정적 타입 함수형 프로그래밍을 입문할 때 처음 배우는 기본 타입클래스 중 하나인 Applicative Functor가 2008년이 되어서야 수학적으로 발견되었을 정도이다.[36] 두 가지로 해석할 수 있다. 본래 의도한 의미는 Avoid (success at all costs)이고, 직역하면 "'모든 비용 면에서 성공하는 것'을 피해라"이다. 좀 더 풀어서 말하면 '(언어의) 성공을 위해 모든 것을 타협하려고 하지 마라.' 는 뜻이다. 인기와 범용성을 위해 안전함, 표현력, 효율성 등 하스켈이 가진 특징을 희생해서는 안 된다는 의미. 다르게는 수단과 방법을 가리지 말고 성공을 피해라(...) 라는 의미도 된다.[37] 하스켈의 비공식적 모토로 널리 알려진 발언[38] 이를 시쳇말로 '씨자바'라고 한다.[39] 산업계 쪽을 중시하는 타 함수형 언어들은 Imperative/OOP 등도 첨가하여 묘하게 짬뽕된 경향이 강한데 반해, 하스켈은 보다 순수한 아카데믹 성향이 강하다.[40] 공개된 부분은 필터의 일부인 데이터 액세스 라이브러리. 스팸 필터 중에서 가장 부하가 큰 부분이라고. 당연하지만 스팸 필터 그 자체는 공개 안 했다.[41] 2013년부터 사용되었다. 이 또한 순수 함수형 언어의 특징을 따온 DSL이었다. 하루에도 몇번이나 코드가 갱신되기 때문에 인터프리팅을 통해서 실행하고 있었지만 너무 느렸다고. 대신 컴파일해서 실행·교체(hot code swapping: CPU나 하드디스크를 실행중에 바꿔끼는 묘기 hot swapping의 소프트웨어 버전)할 수 있는 하스켈에는 상대가 되지 않았다고.[42] 공개된 정보에 따르면 x3 이상[43] 카르다노바 만들어진지 무려 4년반만에 꼴랑 디앱 하나가 돌아가고 있다. 그 이름은 탈중앙화 거래소 역할의 디앱인 선데스왑이다. 그 사이에 이더리움은 디앱 1만개를 확보하였다.[44] Haskel을 반대로 쓴 것.