계정: 로그인
AA 📝
2. 값, 타입, 기타

A Gentle Introduction to Haskell, Version 98
이전 다음 위로


Haskell은 순수한 함수형 언어기때문에, 모든 계산은 syntactic term인 표현식(expression)을 평가하여 이루어지고, 우리가 답으로 간주하는 추상화된 요소인 (value)을 산출합니다. 모든 값에는 그에 상응하는 타입이 있습니다. (직관적으로, 타입은 값들의 집합이라고 생각할 수 있습니다.) 표현식의 예로는 정수 5나 문자 'a' 같은 atomic value, \x -> x+1과 같은 함수, 그리고 리스트 [1,2,3]이나 페어 ('b',4)와 같은 복합적 값 등이 있습니다.

표현식이 값을 기술하듯이, 타입표현식은 타입값(혹은 그저 타입)을 기술하는 syntactic term입니다. 타입표현식의 예로는 Integer (무한 정밀도 정수)나 Char (문자), Integer->Integer (Integer에서 Integer로의 함수) 같은 atomic 타입들과 [Integer] (정수의 균일한 리스트), (Char,Integer) (문자-정수 쌍) 같은 복합 타입들이 있습니다.

모든 Haskell 값들은 ‘first-class’입니다. 즉, 이들은 함수의 인수로 전달될 수 있고, 결과값으로 반환될 수 있으며, 자료구조에 들어갈 수 있습니다. 반면에 Haskell의 타입들은 first-class가 아닙니다. 타입은 값을 기술하며, 값과 타입을 연관짓는 것을 typing이라고 합니다. 위에 예로 든 값과 타입들을 사용하여 아래와 같이 typing을 작성해봅니다:

::”는 “타입을 갖는다(has type)”고 읽습니다.1

Haskell에서 함수는 일반적으로 연속되는 등식들(equations)로 정의됩니다. 예를 들어 함수 inc는 한 개의 등식으로 정의합니다:

   1 inc n    =  n+1

등식은 선언(declaration)의 한 예입니다. 선언의 또다른 예로는 타입 지시문 (type signature) 선언(§4.4.1)이 있고, 명시적으로 inc의 typing을 선언할 수 있습니다:

   1 inc    :: Integer -> Integer

함수 정의에 대해서는 3장에서 자세히 기술합니다.

설명의 편의를 위하여, 표현식 e1이 평가되거나 ‘reduce’되어 또다른 표현식이나 값 e2가 될 때에는

라고 표현하도록 하겠습니다. 예를들어:

Haskell의 정적인 타입 시스템은 타입과 값 사이의 정형적인 관련성을 정의합니다. (§4.1.4) 정적인 타입 체계는 Haskell 프로그램이 type safe하다는 것을 보증해줍니다. 즉, 프로그래머가 모종의 방법으로 타입을 잘못 맞추지 않았음을 보증합니다. 예를들어, 일반적으로 두 개의 문자를 서로 더할 수는 없으므로 'a'+'b'라는 표현식은 틀린(ill-typed) 것입니다. 정적인 타입 체계를 갖는 언어의 주된 장점은 널리 알려져있다시피 모든 타입 오류가 컴파일할 때 감지된다는 점입니다. 물론 타입 시스템이 모든 오류를 다 잡아낼 수는 없습니다. 1/0 같은 표현식은 분명히 맞는 타입이지만 프로그램 실행 중에 평가 과정에서 오류를 내죠. 하지만 역시 타입 시스템은 컴파일 단계에서 많은 프로그램 에러들을 찾아주고, 프로그램에 대해 추론(reasoning)하는데 도움을 주며, 컴파일러가 좀 더 효율적인 코드를 만들 수 있게 해줍니다. (실행 단계에서의 타입 검사 등이 필요치 않습니다.)

타입 시스템은 사용자가 지정한 타입 지정문이 옳다는 것도 보증해줍니다. 사실은 Haskell의 타입시스템이 충분히 강력하기 때문에 (나중에 설명할 약간의 예외만 빼고) 전혀 아무런 타입 지정문도 작성할 필요가 없습니다. 타입시스템이 올바른 타입을 추정(infer)해줍니다. 그럼에도 불구하고 아까 inc 함수에 대해서 했던 것처럼 신중하게 타입 시그너춰를 붙여주는 것이 좋습니다. 이는 매우 효율적이고 훌륭한 문서화 방법이며, 프로그램의 오류를 빛의 세상으로 인도하는 데에 도움을 줍니다.

IntegerChar처럼 특정 타입을 지칭하는 경우에는 첫 글자를 대문자로 씁니다만 inc처럼 값을 지칭하는 경우에는 대문자를 쓰지 않습니다. 이는 단순한 관례가 아니라 Haskell의 구문 규칙 때문입니다. 사실 첫 글자 외에 다른 문자들도 대소문자를 구분해야 하며, foo, fOo 그리고 fOO는 모두 다른 identifier입니다.

2.1 Polymorphic Types

Haskell은 polymorphic 타입—모든 타입에 대해 모종의 방식으로 universally quantified된 (∀) 타입—을 구체적으로 포함합니다. Polymorphic 타입 표현식은 타입의 계열(type family)을 표현합니다. 예를들어 (forall a)[a]는 모든 타입 a에 대해서 a의 리스트의 타입 계열입니다. 정수의 리스트 (예를들어 [1,2,3]), 문자의 리스트 (['a','b','c']), 심지어 정수의 리스트의 리스트 등도 모두 이 타입 계열에 속합니다. (하지만, [2,'b']는 유효한 예가 아닙니다. 2'b'를 모두 포함하는 단일 타입은 없기 때문이죠.)

위에서 나온 a와 같은 identifier를 타입 변수(type variable)라고 하고, Int와 같은 구체적인 타입과 구별하기 위해 첫자를 소문자로 씁니다. 게다가 Haskell에는 univerally quantified type만 존재하기 때문에, 이 quantifier(∀)의 기호를 명시적으로 써 줄 필요가 없으며 위에서 든 예의 경우 그냥 간단히 [a]라고만 쓰면 됩니다. 다시 말해서, 모든 타입 변수들은 암묵적으로 universally quantified 되어있습니다.

리스트는 함수형 언어에서 흔히 사용되는 자료구조이며 polymorphism의 원리를 설명하는 좋은 매개체가 됩니다. Haskell에서 [1,2,3]이라는 리스트는 실제로는 1:(2:(3:[]))이라는 리스트의 축약형이며, 여기서 []는 빈 리스트고 :는 첫번째 인수를 두번째 인수(리스트)의 맨 앞에 추가하는 중위 연산자입니다. (:[]는 각각 Lisp의 consnil과 비슷합니다.) :는 우측우선결합(right associative)이기 때문에, 이 리스트는 그냥 1:2:3:[]라고 써도 됩니다.

리스트에 적용하는 사용자 정의 함수의 예로서 리스트의 원소의 갯수를 세는 문제를 생각해봅니다:

   1 length           :: [a] -> Integer
   2 length []        =  0
   3 length (x:xs)    =  1 + length xs

이 정의는 거의 말 그대로입니다. 이 등식은 “빈 리스트의 길이는 0이다. 첫번째 원소가 x, 나머지 뒷부분 리스트가 xs인 리스트의 길이는 xs 리스트의 길이에 1을 더한 것이다”라고 읽습니다. (이름 붙이는 관례 한 가지에 주목하시기 바랍니다: xsx의 복수형이며, 그렇게2 읽어야합니다.)

비록 직관적이긴 하지만, 상기 예제는 앞으로 설명할 Haskell의 중요한 한 측면인 패턴 매칭에 관해 강조해줍니다. 등식의 좌변에는 []라든가 x:xs 같은 패턴들이 있습니다. 함수가 적용될 때 이런 패턴들이 적절한 직관적 방법을 통해 실제 파라미터에 대응됩니다. ([]는 빈 리스트에만 대응하고, x:xs는 최소한 하나의 원소를 갖는 리스트에 성공적으로 대응되면서 x를 첫번째 원소에, xs를 나머지 리스트에 각각 연계(binding)시킵니다.) 대응이 성공적으로 이루어지면 등식의 우변이 평가되어 함수 적용의 결과로 반환됩니다. 대응에 실패하면 다음번 등식을 대응시켜보고, 만일 모든 등식이 대응에 실패하면 에러가 발생합니다.

패턴 매칭에 의한 함수 정의는 Haskell에서는 매우 흔한 일으므로 허용되는 수많은 패턴들에 대해 익숙해질 필요가 있습니다. 4장에서 이 문제에 관해 다시 다룹니다.

함수 length도 polymorphic 함수의 한 예입니다. 이 함수는 [Integer], [Char], [[Integer]]어떤 타입의 원소를 갖는 리스트에도 다 적용될 수 있습니다.

리스트에 관한 유용한 함수가 두 개 더 있는데 나중에 사용하게 됩니다. 함수 head는 리스트의 첫번째 원소를 리턴하며 함수 tail은 첫번째 원소를 제외한 나머지 리스트를 리턴합니다:

   1 head           :: [a] -> a
   2 head (x:xs)    =  x
   3 
   4 tail           :: [a] -> [a]
   5 tail (x:xs)    =  xs

함수 length와는 다르게 상기 함수들은 모든 인수값에 대해서 정의되어있지 않습니다. 이런 함수들은 빈 리스트에 적용되면 런타임 에러를 냅니다.

Polymorphic 타입을 통해, 어떤 타입들은 그들이 정의하는 값의 집합이 더 크다는 의미에서 분명히 어떤 다른 타입들보다 더 일반적임을 알 수 있습니다. 예를들어 [a]라는 타입은 [Char]보다 더 일반적입니다. 다시 말해서, [Char] 타입은 a를 적절히 치환함으로써 [a] 타입으로부터 파생될 수 있습니다. 이러한 일반화 과정에 관해서 Haskell의 타입 체계에는 두 가지 중요한 성질이 있습니다: 첫째, 올바른 타입의 (well-typed) 모든 표현식은 유일한 최우선 타입 (unique principal type)을 갖는다는 것이 보증되며 (나중에 설명함), 둘째, 최우선 타입은 자동적으로 추정될 수 있다는 것입니다. (§4.1.4) C 언어 같은 monomorphically typed language와는 달리, polymorphism은 표현력을 증가시키고 타입 추정 기능은 프로그래머로부터 타입에 관한 부담을 덜어준다는 것을 알 수 있습니다.

표현식이나 함수의 최우선(principal) 타입은 직관적으로 ‘그 표현식의 모든 실제 인스턴스를 다 포함하는’ 가장 일반적인 타입입니다. 예를들어 head 함수의 최우선 타입은 [a]->a입니다. [b]->a, a->a 혹은 심지어 a까지도 다 올바른 타입이지만 너무 일반적이고, 반면에 [Integer]->Integer 같은 것은 너무 구체적입니다. 유일한 최우선 타입의 존재는 Hindley-Milner 타입 시스템의 가장 두드러진 특징이고, 이 Hindley-Milner 타입 시스템은 Haskell, ML, Miranda (‘Miranda’는 Research Software, Ltd.의 등록상표입니다), 그리고 몇몇 다른 (대개 함수형) 언어들의 타입 시스템의 기반을 이룹니다.

2.2 사용자 정의 타입

몇가지 예(§4.2.1)를 통해 소개할 data 선언을 사용해서 Haskell에서 새로운 타입을 정의할 수 있습니다.

Haskell의 중요한 기정의(predefined) 타입으로 진리값에 관한 타입이 있습니다:

   1 data  Bool    =  False | True

여기서 정의되고 있는 타입은 Bool이고, TrueFalse라는 정확히 두 가지 값을 갖습니다. Bool 타입은 무항(nullary) 타입 생성자(type constructor)의 예이고, TrueFalse는 역시 무항의 자료 생성자 (data constructor), 혹은 그저 간단히 생성자(constructor)입니다.

비슷하게, 색상에 관한 타입을 정의할 수도 있습니다:

   1 data  Color    =  Red | Green | Blue | Indigo | Violet

BoolColor는 모두 유한한 갯수의 무항 자료 생성자로 이루어진 열거형(enumerated) 타입의 예입니다.

다음은 오직 한 개의 자료 생성자로만 이루어진 타입의 예입니다:

   1 data  Point a    =  Pt a a

Point 같은 타입은 생성자가 한 개이기 때문에 종종 튜플 (tuple) 타입이라고 불리며, 타른 타입들(이 경우 두 가지)의 데카르트 곱(cartesian product)일 뿐입니다. (튜플은 다른 언어에서 말하는 record와 비슷한 것입니다.) 반면에, Bool이나 Color 같은 다중 생성자 타입은 (disjoint) union 혹은 sum type이라고 불립니다.

하지만 더 중요한 것은 Point가 polymorphic 타입의 예라는 사실입니다: 어떠한 타입 t에 대해서도 Pointt를 좌표 타입으로 사용하는 cartesian point 타입을 정의합니다. 타입 t로부터 새로운 타입 Point t를 생성해내므로, 이제 Point는 명백히 단항 타입 생성자입니다. (같은 맥락에서 아까 위에서 든 예를 보면 []도 역시 타입생성자입니다. 어떠한 타입 t가 주어져도 여기에 []를 ‘적용(apply)’해서 새로운 타입 [t]를 만들 수 있습니다. Haskell 구문으로는 [] t[t]로 쓸 수 있습니다. 마찬가지로 ->는 타입생성자입니다: 두 개의 타입 tu가 주어지면 t->ut 타입의 원소를 u 타입의 원소에 대응시키는—t를 정의역, u를 공변역으로 하는—함수의 타입입니다.)

이항 자료생성자 (binary data constructor) Pt의 타입은 a -> a -> Point a이며 따라서 아래 타이핑(typing)들은 모두 유효합니다:

반면 'a'1이 서로 다른 타입이기 때문에 Pt 'a' 1 같은 표현식은 잘못된(ill-typed) 것입니다.

자료생성자를 적용하여 을 만드는 것과 타입생성자를 적용하여 타입을 만드는 것을 구별하는 것은 매우 중요한 일입니다. 전자는 실행시에 일어나는 일이자 우리가 Haskell로 어떻게 뭔가를 계산하는가 하는 문제고, 후지는 컴파일할 때 일어나는 일이자 타입시스템이 type safety를 보증해주는 과정의 일부입니다.

Point 같은 타입생성자와 Pt 같은 자료생성자는 서로 분리된 namespace에 존재합니다. 그렇기 때문에 다음과 같이 타입생성자와 자료생성자에 같은 이름을 사용할 수 있습니다:

   1 data  Point a    =  Point a a

처음엔 조금 헛갈려 보일 수도 있지만, 이렇게 하면 타입과 그 타입의 자료생성자 사이의 관계를 더욱 명확하게할 수 있습니다.

2.2.1 재귀적 타입

타입은 아래 이진 트리에서처럼 재귀될 수 있습니다:

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

여기서 정의한 polymorphic 이진 트리 타입은 그 원소가 a 타입의 값을 갖는 말단 노드거나 혹은 (재귀적으로) 두 개의 서브트리를 갖는 내부 노드(branches)입니다.

이런 data 선언을 읽을 때 Tree는 타입생성자고 반면에 BranchLeaf는 자료생성자라는 점을 다시 한 번 상기하시기 바랍니다. 이 생성자들 간의 연관 관계 외에도, 상기 선언은 다음과 같이 BranchLeaf의 타입을 정의하고 있습니다:

위 예에서 정의한 타입으로 인해, 이 타입을 사용하는 흥미로운 (재귀) 함수를 정의할 수 있었습니다. 예를 들어, 트리의 모든 단말 노드들의 원소를 좌에서 우로 훑어서 리스트에 담아 리턴하는 함수 fringe를 정의한다고 해 봅시다. 일단 새로 정의할 함수의 타입을 적어두는 것이 도움이됩니다. 이 경우 타입은 Tree a -> [a]가 되겠죠. 즉, 함수 fringe는 어떠한 타입 a에 대해서도 a의 트리를 a의 리스트에 대응시키는 polymorphic 함수입니다. 적절한 정의는 다음과 같습니다:

   1 fringe                        :: Tree a -> [a]
   2 fringe (Leaf x)               =  [x]
   3 fringe (Branch left right)    =  fringe left ++ fringe right

여기서 ++는 두 개의 리스트를 결합해주는 중위 연산자입니다. (이 연산자의 전체 정의는 9.1절에 나옵니다.) 오래 전에 들었던 length의 예에서처럼 fringe 함수도 패턴 매칭을 사용해서 정의했습니다. 다만 차이가 있다면 이번에는 Leaf라든가 Branch 같은 사용자정의 생성자들이 연루되었다는 점이죠.

정규 인수들은 소문자로 시작하므로 쉽게 구분됩니다.

2.3 타입 동의어 (Type Synonyms)

Haskell은 편의상 타입 동의어 (type synonyms), 즉, 흔히 사용되는 타입의 이름을 정의할 수 있게끔 하고 있습니다. 타입 동의어는 type 선언문을 사용해서 만듭니다. (§4.2.2) 몇 가지 예를 들면:

   1 type  String     =  [Char]
   2 type  Person     =  (Name,Address)
   3 type  Name       =  String
   4 data  Address    =  None | Addr String

타입 동의어는 새로이 타입을 정의하지 않으며 단지 이미 존재하는 타입에 새로운 이름을 부여할 뿐입니다. 예를들어 Person -> Name이라는 타입은 (String,Address) -> String과 완전히 같습니다. 대개 새로운 이름은 원래 이름보다 짧지만 이것이 타입 동의어의 유일한 목적은 아닙니다: 타입 동의어는 좀 더 기억하기 편하므로 프로그램의 가독성을 증가시킵니다. 사실 위 예제는 이 점을 보여주고 있습니다. 심지어 polymorphic 타입에도 새로운 이름을 줄 수 있습니다:

   1 type  AssocList a b    =  [(a,b)]

이것은 a 타입의 값을 b 타입의 값과 연관시키는 ‘연관 리스트(association lists)’의 타입입니다.

2.4 내장 타입이라고해서 특별할 것은 없습니다

앞에서 리스트나 튜플, 정수, 문자 같은 ‘내장’ 타입들을 소개했었습니다. 그리고 새로운 사용자 정의 타입이 어떻게 정의될 수 있는지도 살펴봤습니다. 특수 구문에 대해서는 논외로 한다면, 과연 내장 타입들은 어떤 측면에서든 사용자 정의 타입에 비해서 뭔가 특별한 점이 있을까요? 대답은 아니오입니다. 특수 구문은 단지 편의성과 역사상 관례와의 일관성 때문이지, 의미론 (semantics) 상 중요한 점은 없습니다.

만일 정의 과정에서 특수 구문을 사용할 수 있다고 가정한다면 이런 내장 타입들의 타입 선언이 어떻게 보일지를 생각해봄으로써 이 점을 강조할 수 있습니다. 예를들어 Char 타입은 다음과 같이 작성됩니다:

   1 data  Char    = 'a' | 'b' | 'c' | ...     -- 이것은 올바른 Haskell
   2               | 'A' | 'B' | 'C' | ...     -- 코드가 아닙니다!
   3               | '1' | '2' | '3' | ...
   4 

위와 같은 생성자 이름은 구문상 유효하지 않습니다. 이걸 고쳐쓰면 아래와 비슷하게 써야 할 것입니다:

   1 data  Char    = Ca | Cb | Cc | ...
   2               | CA | CB | CC | ...
   3               | C1 | C2 | C3 | ...
   4 

비록 이런 생성자들이 더 간명하긴 하지만, 이들은 문자를 표현하는 데에 있어서 무척 비관례적입니다.

어느 경우든, 이렇게 ‘가짜 Haskell (pseudo-Haskell)’ 코드를 써 보는게 특수 구문을 이해하는데에 도움을 줍니다. 이제 Char는 단지 많은 수의 무항 생성자들로 이루어진 열거형 타입일 뿐이라는 것을 알 수 있습니다. Char에 관해서 이런 방법으로 생각해봄으로써, 다른 모든 타입들의 생성자들에 대해서 기대하는 바와 마찬가지로 문자에 대해서도 함수를 정의할 때 분명하게 패턴을 매치시킬 수 있게됩니다.

위 예제는 Haskell에서 주석을 사용하는 방법도 보여줍니다. --라는 문자열과 그 뒤에 이어서 나오는 문자열은 그 행이 끝나는 곳까지 모두 무시됩니다. Haskell에서는 {-...-} 형태의 내포형 (nested) 주석도 쓸 수 있으며, 어디에든 나타날 수 있습니다. (§2.2)

비슷한 방법으로, Int (고정 정밀도 정수)와 Integer도 정의할 수 있습니다:

   1 data  Int        =  -65532 | ... | -1 | 0 | 1 | ... | 65532    -- more pseudo-code
   2 data  Integer    =        ... -2 | -1 | 0 | 1 | 2 ...

여기서 -6553265532는 각각 주어진 구현물에서 가능한 최소, 최대 고정 정밀도 정수입니다. IntChar보다 훨씬 큰 나열형이지만 여전히 유한합니다! 반면에, Integer의 pseudo-code는 무한한 나열을 표현하고자 하는 의도입니다.

튜플 역시 다음과 같이 간단하게 정의할 수 있습니다:

   1 data  (a,b)        =  (a,b)        -- more pseudo-code
   2 data  (a,b,c)      =  (a,b,c)
   3 data  (a,b,c,d)    =  (a,b,c,d)
   4  .                     .
   5  .                     .
   6  .                     .

위에서 각각의 선언은 특정한 길이의 튜플 타입을 정의하고 있으며, (...)는 표현식 구문 (자료 생성자로서) 역할과 타입표현식 구문 (타입 생성자로서) 역할을 겸합니다. 마지막 선언문 아래에 나오는 수직으로 나열된 점들은 그러한 선언이 무한히 있다는 점을 나타내고자 하는 것이며, Haskell에서는 어떠한 길이의 튜플도 허용된다는 사실을 반영하고 있습니다.

리스트도 더 흥미롭고 쉽게 다룰 수 있으며, 재귀적입니다:

   1 data  [a]    =  [] | a : [a]     -- more pseudo-code

이제 오래전에 리스트에 관해 서술했던 내용이 분명해집니다: []는 빈 리스트고 :는 중위 리스트 생성자입니다. 따라서 [1,2,3]1:2:3:[]과 같을 수 밖에 없습니다. (:는 right associative합니다.) []의 타입은 [a]이고 :의 타입은 a -> [a] -> [a]입니다.

여기서 ‘:’가 정의되는 방법은 실제로 합당한 구문입니다. — 중위 생성자가 data 선언문에서 허용되고, 이들이 반드시 ‘:’로 시작해야만 한다는 사실 (‘:’가 당연히 만족시키는 성질) 때문에 (패턴 매칭을 위해서) 이들은 중위 연산자들과 구별됩니다.

이 시점에서 위 정의들이 명백히 보여주는 리스트와 튜플의 차이점에 대해 주의깊게 살펴보아야 합니다. 특히 원소가 균일하고 임의의 길이를 갖는 리스트의 재귀적 특성과, 원소가 균일하지 않고 고정된 길이를 갖는 (특정) 튜플 타입의 비재귀적인 특성에 주의하십시오. 튜플과 리스트의 타이핑 규칙도 이제 명백합니다:

2.4.1 리스트 조건제시법과 산술 수열

Lisp 계열의 방언들처럼 Haskell에서도 리스트는 너무나 흔하며, 다른 함수형 언어들에서처럼 Haskell에도 리스트 생성을 도와주는 syntactic sugar가 더 있습니다. 방금 논의한 리스트 생성자 외에도 Haskell은 리스트 조건제시법(list comprehension)이라고 알려진 표현식을 제공합니다. 리스트 조건제시법은 다음 예를 통해 설명합니다:

이 표현식은 직관적으로 “xs의 모든 원소 x에 대한 f x들의 리스트”라고 읽을 수 있습니다. 집합 표기3와 비슷해 보이는 것은 우연이 아닙니다. x <- xs라는 구문은 generator라고 하며,4 아래와 같이 하나 이상도 허용됩니다:

이 리스트 조건제시법은 두 리스트 xsys데카르트 곱(cartesian product)을 만듭니다. 원소들은 마치 generator가 ‘중첩’된 것처럼 왼쪽에서 오른쪽으로 선택됩니다. (맨 우측의 generator가 가장 빨리 원소를 변화시킵니다.) 따라서 만일 xs[1,2]ys[3,4]라면 결과는 [(1,3),(1,4),(2,3),(2,4)]가 됩니다.

Generator 외에도 guards라 부르는 논리 표현식이 허용됩니다. Guards는 형성된 원소에 대해서 제한을 겁니다. 예를들어 모두가 좋아할만한 정렬 알고리즘은 다음과 같이 간단히 정의할 수 있습니다:

   1 quicksort  []       =  []
   2 quicksort (x:xs)    =  quicksort [y | y <- xs, y<x ]
   3                     ++ [x]
   4                     ++ quicksort [y | y <- xs, y>=x]

리스트 사용을 지원하는 또다른 요소로서 Haskell에는 산술 수열(arithmetic sequences)이라는 특수 구문이 있으며, 몇 가지 예로써 설명하겠습니다:

산술 수열에 대해서는 3.4절의 “무한 리스트”와 8.2절에서 좀 더 이야기하겠습니다.

2.4.2 문자열

내장 타입에 대한 또다른 syntactic sugar의 예로서, "hello"라는 상수 문자열은 실제로는 ['h','e','l','l','o']라는 문자들의 리스트의 단축형입니다. 사실, "hello"의 타입은 String이고 String은 (앞서 예를 든 바와 같이) 미리 정의된 타입 동의어입니다:

   1 type  String    =  [Char]

이는 미리 정의된 polymorphic 리스트 함수들을 문자열에 대해서 적용할 수 있음을 의미합니다. 예를 들어:


  1. 이 설명은 어디까지나 유럽어 계열권 사람들을 위한 것일 뿐, 한국어에 딱 맞는 설명은 아닙니다. — 역자 주 (1)

  2. 아마도 [ekses]가 아닌 [eksiz]? — 역자 주 (2)

  3. { f(x) | xxs } — 역자 주 (3)

  4. 단, 이 번역문에서 ‘생성자’라고 번역한 단어는 generator가 아니라 모두 constructor입니다 — 역자 주 (4)


이전 다음 위로
A Gentle Introduction to Haskell, Version 98

Copyright © 1999 Paul Hudak, John Peterson and Joseph Fasel

Permission is hereby granted, free of charge, to any person obtaining a copy of “A Gentle Introduction to Haskell” (the Text), to deal in the Text without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Text, and to permit persons to whom the Text is furnished to do so, subject to the following condition: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Text.