gimmesilver's blog

Agbird.egloos.com

포토로그



하스켈 클래스 하스켈 스프링노트

 하스켈은 아시다시피 순수 함수형 언어입니다. 이 말인 즉슨 객체 지향 언어와는 정반대되는 대척점에 위치한다는 뜻입니다. 아마도 이 세상의 프로그래밍 언어들을 그 패러다임에 따라 줄을 세운다면 하스켈은 함수형 진형의 가장 끝부분에 위치할 것입니다. 아마도 반대편 끝에는 스몰토크가 위치해 있겠죠...(C++는 저쪽 구석에서 땅바닥에 쭈구리고 앉아 남들이 알아보기 힘든 희한한 기하학 무늬를 그리며 혼자 놀고 있지 않을까요? ^^)

 그런데 하스켈에는 알고보면 객체 지향 언어가 가진 특성과 유사한 것들이 제법 있습니다. 그중에 대표적인 것이 바로 클래스입니다. 예, 그렇습니다. 놀랍게도 하스켈에는 클래스가 있습니다. 게다가 클래스 상속도 됩니다. 하스켈에서 클래스는 다음과 같이 선언합니다.

  1. class ThisIsClass a where 
  2. foo :: a -> String  

 좀 낯설죠? 이건 ThisIsClass 라는 이름을 가진 클래스를 선언한 코드이며 이 클래스에는 foo 라고 하는 멤버 함수가 선언되어 있습니다. 물론 객체 지향 언어에서 정의하는 클래스와는 좀 다릅니다. 일단 하스켈의 클래스에는 멤버 변수가 없습니다. 뭐 사실 하스켈에는 (비 함수형 언어에서 말하는) 변수라는게 없으니 당연하겠죠. 그럼 이 클래스로 무얼 할 수 있을까요? 이 클래스를 바로 사용할 수는 없고 해당 클래스의 인스턴스를 만들어줘야 합니다. 인스턴스는 아래와 같이 만듭니다.

  1. instance ThisIsClass Integer where 
  2. foo a = "foo's argument is " ++ show a 

 이제 위 클래스는 아래와 같이 사용할 수 있습니다.

  1. foo 3 
  2. => "foo's argument is 3"

 만약 Integer 타입이 아닌 값을 인자로 foo를 호출하면 아래와 비슷한 에러 메시지가 발생합니다.

  1. foo 'a'
  2. => No instance for (ThisIsClass Char) arising from use of 'foo' at ...

 말그대로 'a'라는 Char타입의 값은 ThisIsClass 의 인스턴스가 아니기 때문에 foo 의 인자가 될 수 없다는 뜻입니다. 물론 Char도 ThisIsClass 의 인스턴스로 선언하면 됩니다.

  1. instance ThisIsClass Char where 
  2. foo a = "(foo::Char)'s argument is " ++ show a 

 이렇게 놓고 보면 하스켈 클래스의 멤버 함수는 마치 C++나 자바의 함수 오버로딩과 비슷합니다. 그리고 실제 하스켈 클래스는 오버로딩의 용도로 사용합니다. 하스켈은 기본적으로 타입 추론을 제공하기 때문에 일반 함수들은 모두 C++의 함수 템플릿과 유사합니다. 즉, 어떤 타입에서도 동일한 절차의 작업을 수행할 수 있죠. 이런 것을 parametric polymorphism이라고 합니다. 이렇게 하면 세부적인 타입에 신경쓰지 않고도 마치 동적 타입 언어처럼 쉽게 함수를 정의할 수 있는 장점이 있는 반면 각 타입에 맞는 적절한 세부 절차를 별도로 제공할 수 없다는 단점이 있습니다. 예를 들어 C++에서는 + 연산자를 숫자 타입에 대해서는 덧셈을 수행하고 문자열 타입에 대해서는 두 문자열을 합치도록 재정의할 수 있습니다. 비슷하게 하스켈은 클래스를 이용하면 동일한 함수에 대해서 이렇게 타입별로 다른 동작을 정의할 수 있습니다.

  1. class Adder where 
  2. add :: a -> a -> a 
  3.   
  4. instance Adder Integer where 
  5. add = (+) 
  6.   
  7. instance Adder [Char] where 
  8. add = (++) 
  9.   
  10. add 1 3 
  11. => 4 
  12. add "hello " "world" 
  13. => "hello world" 


단 위 코드처럼 [Char] 타입을 인스턴스로 만들려면 ghc 컴파일러나 인터프리터의 경우 실행 시 -XFlexibleInstances 라는 옵션을 추가해야 됩니다.


 하스켈 클래스는 이처럼 오버로딩 기능을 줄 수 있는 외에도 지정된 타입들에 대해서만 사용이 가능한 함수를 정의할 수 있습니다. 예를 들어 비교 연산자인 == 의 경우 이 연산자는 실제 비교가 가능한 타입에 대해서만 사용이 가능할 것입니다. 이런 경우

  1. class Eq a where
  2. (==) :: a -> a -> Bool 

 이렇게 (==) 연산자를 클래스 함수로 정의하고 비교 연산이 필요한 데이터 타입에 대해서만 Eq 클래스의 인스턴스로 정의하면 (==) 연산자는 비교 가능 타입에 대해서만 안전하게 사용할 수 있게 됩니다. 실제 (==) 연산자는 이런 식으로 정의되어 있습니다. (==) 연산자의 타입은 다음과 같습니다.

  1. (==) :: (Eq a) => a -> a -> Bool 

 이것은 (==) 연산자가 Eq 클래스의 인스턴스 타입인 a 만을 인자로 받아 Bool 타입의 결과를 반환한다는 의미를 갖습니다.


 이렇게 특정 조건을 만족하는 데이터 타입에 대해서만 함수를 사용할 수 있도록 범위를 주는 하스켈 클래스의 특성은 마치 자바의 인터페이스를 연상시킵니다.  



덧글

댓글 입력 영역