계정: 로그인
AA 📝
11. 모듈

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


Haskell 프로그램은 모듈들의 모음으로 구성됩니다. Haskell에서 모듈은 이름공간을 제어하고 추상자료형을 만드는 두 가지 목적을 갖습니다.

모듈의 최상위 수준에는 지금까지 논의했던 다양한 선언들 중 어떤 것이라도 들어있습니다: 결합특성 선언, 자료 선언과 타입 선언, 클래스 선언과 인스턴스 선언, 타입 지정자, 함수 정의, 패턴 바인딩 등이 그것입니다. (곧 설명하게 될) 수입(import) 선언은 반드시 맨 처음에 등장해야 한다는 사실만 빼고는, 선언들은 어떤 순서로라도 나타날 수 있습니다. (최상위 수준의 스코프는 상호 재귀적입니다.)

Haskell의 모듈 설계는 비교적 보수적입니다: 모듈의 이름공간은 완전히 평평하고, 모듈들은 어떤 식으로도 ‘first-class’일 수 없습니다. 모듈의 이름은 영문자 혹은 숫자로 이루어져있으며, 첫 글자는 반드시 대문자로 시작해야 합니다. Haskell 모듈과 (전형적으로) 이 모듈을 지원하는 파일 시스템 간에는 아무런 정형적인 연계가 없습니다. 특히, 모듈 이름과 파일 이름 사이에는 아무런 관련이 없고, 아마도 둘 이상의 모듈이 하나의 파일 속에 들어갈 수도 있을겁니다. (심지어 하나의 모듈이 여러개의 파일에 흩어져 있을 수도 있습니다.) 물론, 구현물에 따라서는 모듈과 파일의 관계를 좀 더 엄격하게 만드는 관례를 따를 수도 있습니다.

기술적으로 말해서, 모듈이라는 것은 사실은 단지 키워드 module로 시작하는 하나의 커다란 선언일 뿐입니다; 다음은 Tree라는 이름의 모듈의 예입니다:

   1 module  Tree ( Tree(Leaf,Branch), fringe )  where
   2 
   3 data  Tree a    =  Leaf a | Branch (Tree a) (Tree a) 
   4 
   5 fringe                        :: Tree a -> [a]
   6 fringe (Leaf x)               =  [x]
   7 fringe (Branch left right)    =  fringe left ++ fringe right

타입 Tree와 함수 fringe는 낯설지 않아야합니다. 이미 2.2.1절에서 예제로 들었었습니다.

키워드 where때문에, 레이아웃은 모듈의 최상위 수준에서 작동하며, 따라서 선언문들은 모두 같은 (대개 첫) 컬럼에 정렬되어야 합니다. 모듈 이름이 그 모듈의 타입 이름과 같을 수 있다는 점에도 주의하시기 바랍니다.

이 모듈은 명시적으로 Tree, Leaf, Branch, 그리고 fringe수출(export)합니다. 만일 키워드 module 다음에 수출 목록을 빼먹으면, 모듈의 최상위 수준에 바인드되어있는 모든 이름들이 수출됩니다. (상기 예제에서는 명시적으로 모든 것을 수출하고 있으며, 따라서 그 효과는 똑같습니다.) 타입의 이름과 그 타입의 구성자들이 Tree(Leaf,Branch) 처럼 함께 묶여있다는 사실에 주목하십시오. 축약형으로서 Tree(..)라고 쓸 수도 있습니다. 구성자들의 부분집합을 수출하는 것도 가능합니다. 수출 목록에 있는 이름들은 수출되고 있는 모듈에 대해서 지역적(local)일 필요는 없습니다: 스코프 내에 있는 어떤 이름이라도 수출 목록에 등재될 수 있습니다.

Tree 모듈은 이제 다른 모듈로 수입(import)될 수 있습니다:

   1 module  Main (main)  where
   2 
   3 import Tree (Tree(Leaf,Branch), fringe)
   4 
   5 main    =  print (fringe (Branch (Leaf 1) (Leaf 2)))

모듈 안에 수입되거나 모듈로부터 수출된 다양한 아이템들을 entity라고 부릅니다. 수입 선언에 있는 명시적인 수입 목록에 주목하십시오: 이걸 빠트리면 Tree로부터 수출된 모든 엔터티들이 수입됩니다.

11.1 한정명 (Qualified Names)

모듈의 이름공간에 이름들을 직접 수입하는 일에는 명백한 문제점이 하나 있습니다. 만일 두입된 두 모듈이 같은 이름의 서로 다른 엔터티를 가지고 있다면 어떻게 될까요? Haskell은 한정명(qualified name)을 써서 이 문제를 해결합니다. 수입 선언에서는 수입된 이름에 수입 모듈의 이름을 전치사처럼 붙이기 위해 키워드 qualified를 사용할 수 있습니다. 이런 전치사 다음에는 사이에 끼어드는 공백 문자 없이 곧바로 이어서 ‘.’ 문자가 나옵니다.

한정사는 어휘소적 구문의 일부입니다. 따라서, A.xA . x는 명백히 서로 다릅니다: 전자는 한정명이고 후자는 (.) 함수의 중위 사용법입니다.

예를 들어, 앞서 소개한 Tree 모듈의 사용법은 다음과 같습니다:

   1 module  Fringe (fringe)  where
   2 
   3 import Tree (Tree(..))
   4 
   5 fringe                 :: Tree a -> [a]   -- fringe의 또다른 정의
   6 fringe (Leaf x)        =  [x]
   7 fringe (Branch x y)    =  fringe x
   8 
   9 module  Main  where
  10 
  11 import Tree (Tree(Leaf,Branch), fringe)
  12 import qualified Fringe (fringe)  
  13 
  14 main    =  do print (fringe (Branch (Leaf 1) (Leaf 2)))
  15               print (Fringe.fringe (Branch (Leaf 1) (Leaf 2)))

어떤 Haskell 프로그래머들은 수입된 모든 엔터티들에 한정사를 붙여서 항상 각각의 이름들의 출처를 명시적으로 밝히는 것을 선호합니다. 또 어떤 사람들은 짧은 이름을 선호하며, 절대적으로 필요한 경우에만 한정사를 쓰기도 합니다.

한정사는 같은 이름을 가진 서로 다른 엔터티들 간의 충돌을 해소하려고 사용하는 것입니다. 하지만, 만일 둘 이상의 모듈들로부터 동일한 엔터티가 수입된다면 어떻게 될까요? 다행스럽게도, 이런 종류의 이름 충돌은 허용됩니다: 하나의 엔터티는 충돌 없이 다양한 경로를 통해 수입될 수 있습니다. 컴파일러는 서로 다른 모듈들로부터 수입된 엔터티들이 실제로 같은 것인지 아닌지를 알고 있습니다.

11.2 추상 자료형 (Abstract Data Types)

이름 공간을 제어하는 문제 외에도, 모듈은 Haskell에서 추상 자료형(ADTs)을 구축하는 유일한 방법을 제공합니다. 예를 들어, 추상 자료형의 특징적인 요소는 표현형(representation type)이 숨어있다(hidden)는 점입니다: 추상 자료형에 대한 모든 연산들은 표현에 의존하지 않는 추상 수준(abstract level)에서 이루어집니다. 예를 들어, 비록 Tree 타입은 대개 추상화시킬 필요가 없을 정도로 충분히 단순하지만, 이에 대한 적절한 추상 자료형은 다음과 같은 연산들을 포함할 수 있습니다:

   1 data  Tree a       -- 타입 이름만 제공
   2 leaf           :: a -> Tree a
   3 branch         :: Tree a -> Tree a -> Tree a
   4 cell           :: Tree a -> a
   5 left, right    :: Tree a -> Tree a
   6 isLeaf         :: Tree a -> Bool

이를 지원하는 모듈은 다음과 같습니다:

   1 module  TreeADT (Tree, leaf, branch, cell, left, right, isLeaf)  where
   2 
   3 data  Tree a    =  Leaf a | Branch (Tree a) (Tree a) 
   4 
   5 leaf                  = Leaf
   6 branch                = Branch
   7 cell  (Leaf a)        = a
   8 left  (Branch l r)    = l
   9 right (Branch l r)    = r
  10 isLeaf (Leaf _)       = True
  11 isLeaf _              = False

수출 목록에서 Tree라는 이름이 (그에 해당하는 구성자 없이) 단독으로 등장한다는 사실에 주의하십시오. 따라서 LeafBranch가 수출되지 않는다고 하면, 모듈 밖에서 트리를 만들거나 분해하는 유일한 방법은 다양한 (추상) 연산들을 사용하는 것입니다. 물론, 이런 정보 은닉의 장점은, 나중에 이 타입을 사용하는 사용자들에게 영향을 미치지 않으면서 표현형을 변경할 수 있다는 점입니다.

11.3 그 밖의 특성들

이하는 모듈 시스템의 몇가지 다른 측면들에 대한 간략한 고찰입니다. 더 자세한 사항들에 대해서는 Haskell Report를 참조하시기 바랍니다.

비록 Haskell의 모듈 시스템이 비교적 보수적이긴 하지만, 값을 수입하고 수출하는 데에 관한 규칙들이 많이 있습니다. 이들 중 대다수는 명확합니다 — 예를 들어, 같은 스코프 안으로 같은 이름을 가진 서로 다른 두 엔터티를 수입하는 것은 올바르지 않습니다. 개중에는 그다지 명확하지 않은 규칙들도 있습니다 — 예를 들어, 타입과 클래스가 주어졌을 때, 프로그램 속 어디에서도 이 타입과 클래스의 조합에 대한 instance 선언문은 둘 이상 나올 수 없습니다. 더 자세한 내용을 원하시면 Haskell Report (§5)를 읽으시기 바랍니다.


이전 다음 위로
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.