Haskell 2010 언어 보고서 — 1장 Introduction(서론)

Haskell 2010 언어 보고서 — 1장 Introduction(서론)

이번 장에서는 Haskell 2010 Language Report의 서론을 함께 읽어볼게요. 보고서 전체의 큰 그림, 즉 Haskell 프로그램이 어떻게 구조화되는지부터 시작해서 언어의 핵심(kernel), 값과 타입, 그리고 이름(namespace) 규칙까지 한 번에 훑게 됩니다. 코드 문법 자체를 배우기 전에 '언어가 어떤 철학으로 설계되었는지'를 먼저 잡아두면, 뒤의 장들에서 나오는 세부 규칙들이 훨씬 자연스럽게 들어와요.

특히 이 장은 보고서가 "프로그램의 의미를 어떻게 정의하는가"라는 관점을 정해주는 부분이에요. 평범한 기능 언어와 달리 Haskell이 순수 함수형(purely functional) 언어로 불리는 이유와, 문법적 설탕(syntactic sugar)을 'kernel'이라는 작은 언어로 번역해서 설명하는 방식까지, 앞으로 계속 짚고 갈 개념들이 여기 다 들어 있습니다.

출처: https://www.haskell.org/onlinereport/haskell2010/haskellch1.html

본문

Haskell은 프로그래밍 언어 설계의 최근 혁신을 많이 반영한, 범용(general purpose)의 순수 함수형(purely functional) 프로그래밍 언어예요. Haskell은 고차 함수(higher-order functions), 비엄격 의미론(non-strict semantics), 정적 다형적 타입(static polymorphic typing), 사용자 정의 대수적 데이터 타입(user-defined algebraic datatypes), 패턴 매칭(pattern-matching), 리스트 컴프리헨션(list comprehensions), 모듈 시스템(module system), 모나딕 I/O(monadic I/O) 시스템, 그리고 리스트·배열·임의 및 고정 정밀도 정수·부동소수점 수를 포함한 풍부한 기본 데이터 타입 집합을 제공해요. Haskell은 비엄격 함수형 언어에 대한 수년간의 연구가 마침내 결실을 맺어 하나로 응집된 결과물이라고 볼 수 있죠.

이 보고서는 Haskell 프로그램의 문법과, 그런 프로그램의 의미에 대한 비형식적인 추상 의미론(informal abstract semantics)을 정의해요. Haskell 프로그램을 어떻게 다루고, 해석하고, 컴파일하는지 같은 구체적인 방식은 '구현에 달려 있다(implementation dependent)'고 남겨두는데요. 여기에는 프로그래밍 환경의 성격이나, 정의되지 않은 프로그램(즉 형식적으로 ⊥로 평가되는 프로그램)에 대해 돌려주는 오류 메시지 같은 문제도 포함돼 있어요.

1.1 Program Structure — 프로그램 구조

이 절에서는 Haskell의 추상적인 구문·의미 구조와, 그것이 보고서의 나머지 구성과 어떻게 맞물리는지를 설명해요. Haskell 프로그램이 어떤 층위들로 쌓여 있는지 먼저 정리해볼게요.

  • 가장 위쪽에서 Haskell 프로그램은 **모듈(modules)**의 집합이에요. 모듈에 대한 자세한 설명은 5장에서 다루고요, 모듈은 대규모 프로그램에서 네임스페이스를 통제하고 소프트웨어를 재사용하는 방법을 제공해 줘요.
  • 모듈의 최상위는 **선언(declarations)**들의 모음으로 이루어져 있어요. 선언에는 여러 종류가 있고 모두 4장에서 설명해요. 선언은 보통 값(ordinary values), 데이터 타입(datatypes), 타입 클래스(type classes), 그리고 결합도(fixity) 정보 같은 것들을 정의해요.
  • 그 다음 한 단계 아래가 **식(expressions)**인데, 3장에서 다뤄요. 식은 값을 가리키면서 정적 타입(static type)을 지니고 있어요. 식은 바로 Haskell 프로그래밍 "in the small"(작은 규모의 프로그래밍)의 핵심이 돼요.
  • 가장 아래에는 Haskell의 **어휘 구조(lexical structure)**가 있어요. 이것은 2장에서 정의하는데, Haskell 프로그램이 텍스트 파일에서 구체적으로 어떻게 표현되는지를 담당해요.

보고서는 Haskell의 구문 구조에 대해 밑에서 위로(bottom-up) 진행돼요. 위에서 언급하지 않은 장들은 다음과 같아요. 6장에서는 Haskell의 표준 내장 데이터 타입과 클래스를 다루고, 7장에서는 Haskell의 I/O 기능(즉 Haskell 프로그램이 바깥세상과 소통하는 방식)을 다룹니다. 그 밖에도 Prelude, 구체 문법, literate programming, 파생 인스턴스의 명세, 그리고 대부분의 Haskell 컴파일러가 지원하는 pragma를 설명하는 장들이 몇 개 더 있어요.

본문 흐름 속에 나오는 Haskell 프로그램 조각 예시는 타자기(고정폭) 글꼴로 표시돼요.

let x = 1
     z = x+y
 in  z+1

프로그램 조각에서 임의의 Haskell 코드 조각을 나타내는 "구멍(hole)"은 이탤릭체로 쓰여요. 예를 들면 if e1 then e2 else e3처럼요. 보통 이탤릭체 이름은 기억하기 좋게 붙여져요. 식에는 e, 선언에는 d, 타입에는 t 같은 식으로요.

1.2 The Haskell Kernel — Haskell 커널

Haskell은 함수형 프로그래밍에서 널리 쓰이게 된 편리한 구문 구조들을 많이 받아들였어요. 이 보고서에서는 그런 **문법적 설탕(syntactic sugar)**의 의미를, 더 단순한 구조로의 번역을 통해 정의해요. 그 번역을 끝까지 적용하면 결국 Haskell의 작은 부분집합으로 쓰인 프로그램이 나오는데, 그 부분집합을 바로 Haskell **커널(kernel)**이라고 불러요.

커널 자체가 형식적으로 명세되진 않지만, 본질적으로는 간단한 표기 의미론(denotational semantics)을 지닌 람다 계산법(lambda calculus)에 약간의 간편 표기(sugar)를 얹은 변형이에요. 각 구문 구조가 커널로 어떻게 번역되는지는 그 구문이 소개될 때마다 함께 주어져요. 이렇게 모듈식으로 설계하면 Haskell 프로그램을 추론하기 쉬워지고, 언어 구현자에게도 유용한 지침이 돼요.

1.3 Values and Types — 값과 타입

식은 값(value)으로 평가되고 정적 타입(static type)을 지녀요. Haskell에서는 값과 타입이 섞이지 않아요. 다만 타입 시스템은 여러 종류의 사용자 정의 데이터 타입을 허용하고, 전통적인 Hindley-Milner 타입 구조를 쓰는 **파라메트릭 다형성(parametric polymorphism)**뿐 아니라, 타입 클래스(type classes)를 통한 ad hoc 다형성, 곧 **오버로딩(overloading)**까지 허용해요.

Haskell에서 오류는 의미론적으로 ("bottom")과 동등해요. 기술적으로 오류는 비종료(nontermination)와 구분이 불가능하기 때문에, 언어 자체에는 오류를 감지하거나 반응하는 메커니즘이 없어요. 다만 구현체들은 아마 오류에 대한 유용한 정보를 제공하려고 할 거예요. 자세한 내용은 3.1절을 참고하세요.

1.4 Namespaces — 이름 공간

Haskell에는 여섯 종류의 이름이 있어요. **변수(variables)**와 **생성자(constructors)**를 위한 이름은 값을 가리키고, 타입 변수(type variables), 타입 생성자(type constructors), **타입 클래스(type classes)**를 위한 이름은 타입 시스템과 관련된 실체를 가리키며, **모듈 이름(module names)**은 모듈을 가리켜요. 이름 붙이기에 대한 제약은 두 가지가 있어요.

  • 변수와 타입 변수의 이름은 소문자나 밑줄(_)로 시작하는 식별자예요. 나머지 네 종류의 이름은 대문자로 시작하는 식별자예요.
  • 같은 스코프(scope) 안에서 하나의 식별자를 타입 생성자의 이름이면서 동시에 클래스의 이름으로 쓰면 안 돼요.

제약은 이것뿐이에요. 예를 들어 Int는 한 스코프 안에서 모듈·클래스·생성자의 이름을 동시에 맡을 수 있어요.

더 알아보기 (Learn more)