다형성 (Polymorphism)

프로그래밍에서 활용되는 다형성은 크게 두 가지로 분류할 수 있다.

  • 인자 다형성 (parametric polymorphism)
  • ad-hoc polymorpism (주로 함수/연산자 오버로딩 해당)
    • 클래스와 상속을 지원하는 OOP 언어에서는 subtype polymorphism (주로 동적 디스패치, 오버라이딩 해당)을 따로 구분하기도 한다.

인자 다형성이란 넘어오는 인자의 타입이나 내용에 관계없이 일관된 동작을 하는 경우를 뜻한다. 대표적인 예로 넘어온 값을 그대로 돌려주는 항등함수를 들 수 있다.

In [1]:
f x = x
In [2]:
:type f
f :: forall t. t -> t
In [3]:
f "hello"
f 153
f 3.141592
"hello"
153
3.141592

다른 언어에서도 인자 다형성을 지원한다. 예를 들어 C++ 에서 항등함수 f를 정의하는 방법: http://cpp.sh/8avlb (C++ template 사용)

Java, C# 등에서도 제너릭(generic)이라는 기능을 통해 인자 다형성 기능을 지원 (Java 등에서 제너릭은 C++의 템플릿과 비슷한 문법을 사용하지만 기능이 미묘하게 다르다)

C++, Java, C# 등에서 인자 다형성을 활용하기 위한 문법이 Haskell과 같은 함수형 언어들보다 복잡한 이유는, 처음에는 이걸 필요없다고 생각해서 또는 만들기 어렵다고 생각해서 인자 다형성 없이 언어를 구현해 사용하다가 나중에 보니 없으면 안되겠다 싶어서 추가하느라 문법이 복잡해져 버린 것이다.

polymorphic 리스트

인자 다형성은 함수 정의에서 뿐 아니라 리스트와 같은 데이타 구조 정의에서도 매우 유용하다. 지금까지는 원소가 정수인 리스트만 다루었지만 경우에 따라서는 문자열의 리스트도 필요할 것이고 또 부동소수점수의 리스트도 필요할 것이고 또 다른 타입의 리스트도 필요할 것이다.

In [4]:
-- 타입마다 새로운 리스트 타입을 계속 정의하는 것은 좀 아니다

data ListI = NilI | ConsI Int    ListI     deriving Show
data ListS = NilS | ConsS String ListS     deriving Show
data ListD = NilD | ConsD Double ListD     deriving Show
-- ...
In [5]:
-- 인자 다형성을 활용한 일반적인 리스트 정의
-- (이거 하나면 위에것들 다 커버한다!)
data List a = Nil | Cons a (List a)   deriving Show
In [6]:
x1, x2 :: Int
x1 = 1
x2 = 2

:type (Cons x1 (Cons x2 Nil))
(Cons x1 (Cons x2 Nil)) :: List Int
In [7]:
s1, s2 :: String
s1 = "hello"
s2 = "world"

:type (Cons s1 (Cons s2 Nil))
(Cons s1 (Cons s2 Nil)) :: List String

전에 작성했던 append 함수도 사실은 polymorphic 함수이다. 똑같은 등식으로 된 함수 정의가 polymorphic 리스트 타입 (List a)에 대해서도 그대로 동작한다.

In [8]:
append Nil         ys = ys
append (Cons x xs) ys = Cons x (append xs ys)
In [9]:
:type append
append :: forall a. List a -> List a -> List a
In [10]:
append (Cons 1 (Cons 2 Nil)) (Cons 3 (Cons 4 Nil))
append (Cons "hello" (Cons "world" Nil)) (Cons "hi" (Cons "sky" Nil))
Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil)))
Cons "hello" (Cons "world" (Cons "hi" (Cons "sky" Nil)))

polymorphic 이진트리

리스트와 마찬가지로 이진트리도 polymophic하게 정의할 수 있다. 리스트에 대한 append를 polymorphic 타입을 갖는 함수로 정의될 수 있는 것과 마찬가지로, 이전에 정의한 sumTree, height 같은 함수들도 똑같은 등식으로 polymorphic 이진트리에 대해서 정의할 수 있다.

In [11]:
data Tree a = Null | Node a (Tree a) (Tree a)  deriving Show

하스켈에서 제공하는 리스트 라이브러리

  • List a 대신에 [a]
  • Nil 대신에 []
  • Cons 대신에 (:)

라이브러리 함수로 append의 기능을 하는 (++) 연산자.

In [12]:
:info []
In [13]:
1 : []
1 : 2 : 3 : 4 : []

[1]
[1,2,3,4]
Use list literal
Found:
1 : []
Why Not:
[1]
Use list literal
Found:
1 : 2 : 3 : 4 : []
Why Not:
[1, 2, 3, 4]
[1]
[1,2,3,4]
[1]
[1,2,3,4]
In [14]:
[1] == 1:[]
Use list literal
Found:
1 : []
Why Not:
[1]
True
In [15]:
[3,4,5] == 3:(4:(5:[]))
Use list literal
Found:
3 : (4 : (5 : []))
Why Not:
[3, 4, 5]
True
In [16]:
[1,2] ++ [3,4,5]
[1,2,3,4,5]
In [17]:
["aaa","bbb","ccc"]
["aaa","bbb","ccc"]
In [18]:
plusList []     ys     = []
plusList xs     []     = []
plusList (x:xs) (y:ys) = (x+y) : plusList xs ys
In [19]:
plusList [1,2,3] [1,2,3,4,5]
plusList [1,2,3,4,5] [1,2,3]
[2,4,6]
[2,4,6]

고차함수 (higher-order function)

다른 함수를 인자로 받거나 돌려주는 함수

In [20]:
plusList' = zipWith (+)
In [21]:
plusList [1,2] [1,2,3]
plusList' [1,2] [1,2,3]
[2,4]
[2,4]
In [22]:
multList = zipWith (*)
In [23]:
multList [1,2,3] [4,5,6,7]
[4,10,18]
In [24]:
zipWith' f []     ys     = []
zipWith' f xs     []     = []
zipWith' f (x:xs) (y:ys) = (f x y) : zipWith' f xs ys
In [25]:
multList' = zipWith' (*)

multList' [1,2,3] [4,5,6,7]
[4,10,18]
In [26]:
-- 합성함수
-- compose f g = \x -> f(g x)
compose f g x = f(g x)

:type compose
compose :: forall t t1 t2. (t1 -> t) -> (t2 -> t1) -> t2 -> t
In [27]:
f x = x + 1
g x = x * 2

h = compose f g

f(g 3)
h 3
7
7

팩토리알 프로그램

In [28]:
fact 0 = 1
fact n = n * fact (n - 1)
-- 짧게 쓰기 위해 위 코드는 n은 양수라고 가정했는데
-- n이 음수인지 검사하여 에러를 내 주거나 하는 것이 더 바람직하다
In [29]:
fact 3
fact 5
fact 20 :: Int
fact 21 :: Int
fact 21 :: Integer
6
120
2432902008176640000
-4249290049419214848
51090942171709440000

피보나치 함수

fib n이 n 번째 피보나치 수열의 수를 생성 (0번부터 시작한다고 가정)

In [30]:
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
-- 짧게 쓰기 위해 위 코드의 n은 양수라고 가정했는데
-- n이 음수인지 검사하여 에러를 내 주거나 하는 것이 더 바람직하다
In [31]:
fib 0
fib 1
fib 2
fib 3
fib 4
fib 5
fib 6
fib 7
fib 8
fib 9
1
1
2
3
5
8
13
21
34
55

고차함수 map

In [32]:
map fib [0,1,2,3,4,5,6,7,8,9]
[1,1,2,3,5,8,13,21,34,55]
In [33]:
:type map
map :: forall a b. (a -> b) -> [a] -> [b]
In [34]:
map (\x -> x*2) [0,1,2,3,4,5,6,7,8,9]
Avoid lambda
Found:
\ x -> x * 2
Why Not:
(* 2)
[0,2,4,6,8,10,12,14,16,18]
In [35]:
map (*2) [0,1,2,3,4]
[0,2,4,6,8]

IHaskell에서 제공하는 추가 기능

이 환경에서는 텍스트 형식 외에 이미지 형식, HTML, 간단한 LaTeX 수식 등을 출력할 수 있는 기능도 제공된다.

In [36]:
import IHaskell.Display

Display [html "<strong style='color:red'>hello</strong> <u style='color:blue'>world</u>"]
Display [html "$(\\lambda x.\\lambda y.x)$"]
hello world
$(\lambda x.\lambda y.x)$

더 많은 기능이 IHaskell 예제 노트북에 소개되어 있다.

http://nbviewer.jupyter.org/github/gibiansky/IHaskell/blob/master/notebooks/IHaskell.ipynb