Search Results for 'ml'

3 POSTS

  1. 2006.10.08 Dynamic Typing과 Test Driven Development (6)
  2. 2006.10.07 ML 공부를 위한 참고 자료 (1)
  3. 2006.10.05 Parametric Polymorphism과 Dynamic Typing
Dynamic Typing과 관련된 글을 블로그에 Static Type Checking이란 제목으로 쓴 적이 있는데 여기서 다시 한 번 정리해 보고자 한다.

블로그를 돌아다니다보면 많은 사람들이 TDD가 Dynamic Typing하는 언어의 특징이라고 생각하고 있음을 알 수 있다. 특히 일부 파이썬/루비 팬들이 운영하는 블로그에서 TDD가 언급되면 어김없이 동적 타이핑 언어의 테스트 편리성(혹은 크게는 생산성)에 대한 이야기가 뒤따라 온다.

하지만 C/C++처럼 수십 년도 더 된 구세대 언어와, 소프트웨어 개발 방법론과 테스팅에 대한 어느정도 이해가 쌓인 후에 나온 파이썬/루비와 같은 언어를 정적/동적 타이핑 면에서만 놓고 비교하는 것은 공정하지 못한다. 사실 너무 불공평하다.

파이썬/루비와 정반대 선상에서 정적 타입핑의 극상을 달리는 친구들은 ML과 Haskell 같은 함수형 언어(functional)들인데, 이 놈들은 C/C++ 보다 더 엄격하게 컴파일 타임에 타입 검사를 한다. 하지만 이쪽 언어 사용자들의 주장에 따르면 ML과 Haskell은 절대 테스트하기 불편한 언어도 아니고, 코드 길이가 길거나 생산성이 떨어지는 언어도 아니다.

순수하게 타이핑 측면에서만 바라본다면, 동적 타이핑은 컴파일 타임에 해주는 검사가 적은만큼(상당히 귀납적이다) 테스트를 통해 더 많은 버그를 찾아내야 하므로, 테스트를 훨씬 더 철저하게 많이 해야 한다. 잘못된 타입 연산 등의 간단한 버그 조차도 테스트를 통해서만 발견할 수 있다는 것은 재앙이다(타입 검사는 연역적이다)

테스트의 부담이 더 큰 파이썬/루비 같은 동적 타이핑 언어가 테스트를 강조하는 것은 당연한 일이다. 같은 기능을 하는 다른 프로그램 보다 더 많은 테스트를 해야 프로그램이 제대로 동작하기 때문이다. 즉 원래 언어적인 측면에서 태생적인 약점이 있으므로, 후천적인 노력으로 개선을 한 셈이다.

파이썬과 루비 같은 동적 타이핑하는 스크립트 언어의 테스트 편리성은 동적 타이핑이라는 속성에 기인한 것이 아니다. 이는 언어의 추상화(abstraction) 수준과 관계가 있다. 추상화 수준이 높을수록 코드는 사람이 생각한 바(스펙)에 가까워지고, 알아보기 쉬워지며, 금방 만들 수 있다.

C 언어와 파이썬/루비의 추상화 수준은 어떻게 다를까? 간단한 통계로 C 언어 construct 하나가 보통 1-10개의 머신 인스트럭션으로 컴파일 되는 반면에, 파이썬과 루비의 construct 하나는 100-1000개 이상의 머신 인스트럭션으로 실행된다.

즉, 파이썬이나 루비 개발자가 그토록 강조하는 "가독성", "짧은 코드"는 절대로 동적 타이핑(흔히 스크립트 언어)의 특성이 아닌 셈이다. 강력한 정적 타이핑 언어인 헤스켈(Haskell)의 퀵 소트 코드를 본 적이 있다면, 타이핑은 생산성이나 테스트 편의와는 직접적인 상관 관계가 없다는 데 동의할 것이다.

qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)

위 Haskell 코드는 polymorphic하다. qsort에 넘길 수 있는 타입은 >= 연산자가 정의된 list이기만 하면 된다. Haskell이 이처럼 간결한 코드를 보여줄 수 있는 이유는 파이썬/루비와 같은 수준의 추상화 레벨이면서, type inference를 통해 불필요한 타입 어노테이션을 필요를 상당히 줄였기 때문이다.

이처럼 같은 추상화 수준이라면 당연히 동적 타이핑보다는 정적 타이핑이 유리하다. 비유하자면, 타입 이론은 '사람은 모두 죽는다' '그러므로 소크라테스도 죽는다'라고 말하는 것이고, 테스트는 'A도 죽었고, B도 죽었고, C도 죽었으니 아마 소크라테스도 죽을 것이다'라고 말하는 셈이다.
  1. Favicon of http://snaiper.tistory.com BlogIcon snaiper

    | 2006.10.05 14:09 | PERMALINK | EDIT | REPLY |

    글 잘 읽었습니다. 꽤나 많이 생각하신 흔적이 들어나네요.

    제 생각은 이렇습니다. 테스트 편리성이 언어의 추상화 정도와 관련있다는 얘기는 하셨는데...저는 약간 생각이 다릅니다. 예를 들어 저는 오래전에 T-SQL로 단위 테스트 코드를 작성한 적이 있습니다. 아시다시피 이것도 정적타입이고, 추상화 정도가 상당히 높습니다. 하지만 정적 타입이다 보니 테스트코드가 상당히 길어지고 코드 중복이 많이 발생했습니다. 테스트 코드 만드는 것이 상당히 고통이 되죠. 만약 Ruby 와 같은 동적 타입이라면 코드를 절반이하로 줄일 수도 있었겠지요.
    그래서 테스트 편리성이라는 건 추상화 정도도 정도지만 얼만큼 테스트 코드를 적게 중복없이 코딩할 수 있냐가 중점이 될겁니다. 이런 의미에서 Typing 의 다름이 장점이 될 수도 있을 겁니다.

    말씀하신 Haskell 은 저도 얼마전에 조금 공부해 본 지라 잘 모르겠습니다. 만약 이런 측면에서 가능하다면, 그것도 테스트 편리성이 높다고 하겠지요. 그렇다면 스크립트 언어 자체가 TDD에 더 좋다고 해야 할까요? ^^

  2. Favicon of http://www.gomdoong.net/skyul BlogIcon 아저씨

    | 2006.10.05 15:44 | PERMALINK | EDIT | REPLY |

    snaiper님 의견을 잘 읽었습니다. ^.^

    테스트 케이스 작성의 편리성이라는 측면을 본다면 말씀하신 것처럼 여러 가지 특성이 관련되어 있을 수 있습니다. 일반적으로 코드 작성이 쉽고 빠른 언어의 경우, 테스트 케이스 작성도 쉽고 빠른 경우가 많죠. 실제로 자바 같은 정적 타이핑 언어도 테스트 케이스를 작성할 때는 Groovy 같은 스크립터 언어를 사용하길 권하기도 합니다.

    다만 원글에도 언급한 것처럼 동적 타이핑 언어는 안정성을 거의 전적으로 테스트에 의존하고 있기 때문에 정적 타이핑 언어보다 훨씬 테스트 케이스 작성을 많이 해야 한다는 문제를 안고 있습니다. 정적 타이핑 언어는 내부 논리를 테스트하는 몇 가지 테스트 케이스만 필요한 반면에, 동적 타이핑 언어는 온갖 타이핑 언어에 대한 테스트가 다 추가되어야 하거든요.

    그리고 동적 타이핑 언어의 또 다른 장점으로 드신 '코드 중복'을 줄이는 특성은 동적 타이핑이란 특성에 기인한 것이 아닙니다. 말씀하신 부분은 Parametric Polymorphism(C++의 템플릿이나 Java의 Generic)의 장점에 가깝습니다. foo(x) 이렇게 선언할 경우 x가 어떤 타입이든 상관없다는 거죠. 하지만 이 경우에도 파이썬이나 루비처럼 런타임에 타입 체크가 가능한 것과 컴파일 타임에 최대한의 Type Inference를 통해서 타입 오류 알려주는 것은 큰 차이가 있습니다.

    snaiper님이 T-SQL을 사용하시면서 겪으신 정적 타이핑의 불편함은 정적 타이핑 자체의 속성이라기보다는 Type Inference/Parametric Polymorphism 등의 부족이 원인이 아닐까 싶습니다. 이런 부분을 최대한 해주는 언어일수록 현대 언어에 가까운 것이고요. 이런 측면에서 C/C++ 같은 고전적인 정적 타입 언어는 테스트 케이스 작성이 힘들죠.

    스크립트 언어의 여러 특성(높은 추상화 레벨, Polymorphism을 비롯한 각종 편의 기능들)이 테스트 케이스 작성에 도움을 주는 것은 사실이지만, 동적 타이핑 한다는 사실 자체는 테스트에 대한 부담감을 늘릴 뿐이라고 생각합니다.

    요지는 스크립트 언어가 테스트에 나쁘다는 것이 아니라, 스크립트 언어의 동적 타이핑이라는 특성은 '많은 테스트 케이스' 작성을 요구하는 속성을 가지고 있으며, 파이썬과 루비 같은 스크립트 언어는 다른 수많은 장점들로 테이스 케이스를 쉽게 작성할 수 있게 만들어 그런 약점을 메우고 있다는 것이죠.

  3. Favicon of http://snaiper.tistory.com BlogIcon snaiper

    | 2006.10.05 17:03 | PERMALINK | EDIT | REPLY |

    음..그렇게도 생각이 가능하겠군요.

    Parametric Polymorphism 입장에서 접근하면 확실히 해결됩니다만....문제는 항상 그게 모든걸 해결해주지 못합니다. 제가 Java 전문가는 아니라서 잘 모르겠습니다만 Java나 C# 의 Generics의 문법이나 사용을 보던지, 또는 C++ 에서 사용해본 경험에 따르자면 그게 모든 걸 해결해줄 수는 없습니다. 오히려 쓸데없이 복잡한 코드를 대가로 치루는 경우가 휠씬 많습니다. 그래서 그걸로 끝날 수 있다면 좋은데 테스트 코드상 그런건 오히려 생각의 오버헤드를 대가로 하는 경우가 많습니다. 그래서 그런 의미에서 동적 타입이 오히려 더 간단합니다. 심플한게 좋은거죠...^^
    - 아 haskell 은 잘 모르겠군요. 언듯 보기에는 없는듯 한데, 패러다임이 다르니 비교 대상이 될런지 잘 모르겠습니다. -

    그리고 테스트 케이스를 늘린다는 얘기는 ... 제 경험상으로는 버그는 타입의 문제보다는 로직상의 오류가 문제입니다. 말씀하신대로 완벽한 테스트를 위해서는 테스트 케이스가 많아져야 한다는 사실자체에는 확실히 동의할 수 있습니다만, 하지만 진짜로 테스트를 하는 목적에서 보자면 동적이나 정적이나 그리 차이는 없다고 보여집니다. 실제로 타입에 대한 문제는 굳이 테스트 케이스를 만들어서 테스트를 안 할지라도 개발 단계에서 왠만한 건 발견이 됩니다. 설사 개발 단계에서 지나친다고 해도 개발자 내부 테스트와 QA 까지 거치면 이런 건 거의 발견이 되죠. 그러니까 허술한 듯 보여도, 실제로 사용하기에는 큰 문제가 없더라는 얘깁니다.

    글고 더 중요한 건 로직상의 오류이고 이건 발견하기 쉽지 않다는 건 이미 알고 계시겠죠? 그래서 정적 타입이라고 해서 부담감이 더 줄고, 동적이라고 해서 더 늘지는 않는다는 겁니다. 그래서 정적 타입이 더 좋을 거라는 생각에는 그리 쉽게 동의가 되지는 않는다는 얘깁니다. 그런 연유로 이미 알고 계시겠지만 Bruce Eckel 이나 Robert C. Martin 도 그런 결론에 다다른게 아닌가 합니다.

  4. Favicon of http://www.gomdoong.net/skyul BlogIcon 아저씨

    | 2006.10.05 23:45 | PERMALINK | EDIT | REPLY |

    의견 감사합니다^^

    Parametric Polymorphism과 Dynamic Typing에 대한 이야기는 글이 길어져서 별도의 글에 실었습니다. http://www.gomdoong.net/skyul/69

    논리 버그를 잡아내는 것이 중요하다는 사실에는 동의합니다 하지만 실제로 프로그래머를 괴롭히는 수 많은 버그들은 전부 타입 오류입니다. 화상 탐사선이 대기권에 돌입하다가 폭발하고, 많은 돈 들여 만든 프로젝트가 소프트웨어 결함으로 실패하는 경우를 분석해 보면 논리야 수 백 수 천에 걸쳐 검증했지만 정수 오버플로우나 단위 불일치(unit mismatch) 같은 사소한 타입 오류가 남아있어서 발생한 문제였습니다.

    스나이퍼님 말씀대로 타입 오류를 테스트와 QA를 거쳐서 쉽게 발견해 낼 수 있다(타입 오류는 별로 대다한 버그가 아니라고 쳐도)고 하더라도 검출을 위해서는 여전히 테스트 비용을 지불해야 합니다.

    이렇게 생각하면 간단하지 않을까요? 동적 타이핑을 하던 정적 타이핑을 하던 논리 오류는 어차피 잡아야 할 문제이며, 정적 타이핑은 타입 오류를 잡는 데 추가 비용이 거의 들지 않는데 비해서 동적 타이핑은 타입 오류도 테스트를 통해 잡아야 한다는 거죠. 논리(logic) 용어에 따르면 정적 타이핑은 Sound/Complete한 반면에 테스트는 Sound하지도 Complete도 하지 않기 때문입니다.

    Bruce Eckel 아저씨의 논리는 동적 타이핑을 하기 때문에 '생산성이 엄청나게 향상되고' 남는 시간에 테스트를 더해서 정적 타이핑 보다 나을 수 있다는 얘긴데, 제가 원 글에서 했던 얘기는 파이썬의 생산성이 좋다는 점에는 동의하지만 파이썬이 보여주는 생산성이라는 게 동적 타이핑과는 큰 상관 관계가 없다는 게 요지였죠.


    ---

    SNAIPER님 논지의 핵심은 사실 타입 시스템이라는게 말이 많고(verbose) 복잡해서 올바르게 쓰려면 드는 노력이 크기 때문에 득보다 해가 더 많다는 건데, C++이나 자바 등을 보면 사실 맞는 말이기에 저도 수긍 합니다.

    이 논지는 실제로 타입 시스템에 대한 주된 비판의 근거이기도 하죠. 필요없이 복잡한데 비해서 별로 보장해 주는 게 없으니깐요. 하지만 그에 대한 대안으로 동적 타이핑은 임시 방책에 불과하고, 궁극적으로는 타입 시스템에 대한 연구가 맞는 방향이라고 봅니다^^... 좋은 의견 주셔서 많이 생각해 보게 되었네요. 감사합니다.

  5. Favicon of http://www.hybrid.pe.kr/tt BlogIcon Hybrid

    | 2006.12.31 21:53 | PERMALINK | EDIT | REPLY |

    좋은 글 잘 보고 갑니다.
    Practical Common Lisp 책을 보다가 관련된 내용이 언급이 됐는데, 전반적으로 Dynamic Typing과 Test Driven Development 가 잘 이해가 안갔는데, 어느정도 감을 잡고 갑니다.(책은 계속해서 볼 수 있을 정도로.. ^^;)
    나머지는 따로 공부해나가야겠지요. RSS 구독도 신고합니다~ㅎ

  6. Favicon of https://skyul.tistory.com BlogIcon 서광열 lambda

    | 2007.01.09 01:44 신고 | PERMALINK | EDIT |

    감사합니다^^ 공부 열심히 하셔서 좋은 가르침을 주세요.

Write your message and submit

ML 공부를 위한 참고 자료

Posted 2006.10.07 20:13
ML 참고 자료입니다. 이 글을 앞으로 계속 갱신할 예정입니다.

ML 참고 문서 Version 0.1 (2006-10-07)

[1] Programming in Standard ML,Robert Harper Carnegie Mellon University Spring Semester, 2005
http://www.cs.cmu.edu/~rwh/smlbook/

카네기 멜론 대학(CMU)에서 나온 SML 입문서입니다. CMU 학부 학생들이 SML을 처음 공부할 때 보는 책이라고 나와 있습니다. 총 293 페이지로 SML의 기본적인 내용을 아주 자세히 설명하고 있어서 함수형 언어를 처음 배우는 (혹은 프로그래밍을 처음 접하는) 입문자에게 좋은 책입니다.
  1. Favicon of http://legendre.tistory.com BlogIcon legendre

    | 2007.11.11 11:30 | PERMALINK | EDIT | REPLY |

    좋은 사이트 소개 감사드려요.
    (Standard) ML을 수업으로 처음 접하게 되었는데 재미있는 언어네요. :)
    한국 ML 사용자 모임, 이런 사이트는 찾아보니 안 나오더라고요.

Write your message and submit
동적 타이핑에 대한 여러 가지 오해가 있는데, 그 중 하나가 동적 타이핑을 써야만  Polymorphism이 간단해진다고 데 믿는 것이다. 일반적으로 많은 프로그래머들이 C++의 Template이나, Java/C#의 Geneircs을 쓰다가 파이썬/루비의 Parametric Polymorphism만  접해보고 나서 성급하게 내리는 결론이 아닐까 쉽다.

실제로 2 개의 인자를 받아서 큰 값을 리턴하는 max 함수를 예로 들어보자.

<파이썬의 예>
>>> def max(a, b):
...     if (a >= b): return a
...     else: return b
...
>>> max(3,4)
4
>>> max(3.5,4.2)
4.2000000000000002


<자바의 예>
public class Foo {
    public static <T extends Comparable > T max(T a, T b) {
        if (a.compareTo(b) >= 0) return a;
        else return b;
    }

    public static void main(String... args) {
        System.out.println(max(3,4));
        System.out.println(max(3.5,4.2));
    }
}


위의 예를 보면 똑같은 일을 하기 위해서 파이썬은 간단히 변수 a, b만 있으면 되는 반면에 자바는 복잡한 문법으로 타입 변수  T에 대한 정보를 줘야 한다. 여기까지만 살펴보면, 파이썬이 훨씬 좋아 보인다.

한 가지 차이가 있다면 파이썬은 동적 타입 검사를 수행하기 때문에 a, b의 인자가 실제로 비교할 수 있는 연산자(>)가 정의되어 있지 않더라도 실행이 된다(실제로 실행할 때 런타임 에러가 발생한다)는 것이고, 자바의 경우는 Comparable 인터페이스를 구현하지 않은 객체를 넘기면 바로 컴파일 에러가 뜬다는 점이다.

그럼에도 불구하고, 여기까지만 살펴보면, 파이썬과 같은 동적 타입 언어가 Parametric Polymorphism을 훨씬 손쉽게 사용할 수 있는 것처럼 보인다. 자바의 Generics는 한 번 보고 직관적으로 사용할 수 있는 수준은 아니기 때문이다. 파이썬 입문자가 엄청난 생산성 향상을 맞보고 동적 타입 언어야 말로 개발자를 위한 언어라고 생각하는 게 무리가 아니다.

그렇지만 정적 타입 언어는 반드시 C++의 Template, Java/C#의 Generics처럼 저렇게 복잡한 타입 정보를 일일이 적어줘야만 하는 운명에 놓인 것인가? 정답은 그렇지 않다. 강력한 정적 타입 언어로 유명한 ML의 예를 살펴보자.

- fun umax (x, y, gt) = if gt(x,y) then x else y;
- umax (2, 3, op >);
val it = 3 : int
- umax (2.3, 3.2, op >);
val it = 3.2 : real

코드만 놓고 보면 파이썬과 별로 차이가 없다. (op >를 직접 코드에 쓰지 않고 인자로 넘긴 이유는 ML에서 > 연산자가 오버로딩 되었다는 점과 ML의 기본 타인 추론 규칙 때문인데, 자세한 설명을 생략한다.) 어쨌거나 x, y 두 개의 인자를 넘기는데, 타입 정보는 따로 준 적이 없다.

혹시ML은 파이썬과 마찬가지로 동적 타입 언어인데, 우리가 잘못 알았던 것일까? 그렇진 않다. ML은 무척 강력한 정적 타입 체킹을 하는 언어이다. 다만 타입 추론(type inference)를 통해서 불필요한 타입 정보를 생략할 수 있게 만들었다. 위 정보를통해서 ML은 x, y 변수가 >= 연산자가 정의되어 있는 임의의 타입임을 안다.

그럼 이 ML 코드가 파이썬 코드랑 뭐가 다른 것일까? 하는 일이 똑같다면 정적 타입 언어와 동적 타입 언어의 구분이 무의미할 테니깐 말이다. 개발자가 실수로 서로 비교할 수 없는 타입인 정수와 문자열을 비교했다고 하자.

ML의 경우
- umax (2.3, "hello", op >);
stdIn:1.1-17.7 Error: operator and operand don't agree [tycon mismatch]
operator domain: real * real * (real * real -> bool)
operand: real * string * ('Z * 'Z -> bool)
in expression:
umax (2.3,"hello",>)

바로 타입 에러가 나는 반면의 파이썬의 경우 int를 문자열로 바꿔서 비교한 후에 아무렇게나 리턴해 버린다. 물론 파이썬의 경우도 정말 정의가 안 된 연산(예를 들어 정수 + 문자열)에 대해서는 런타임에 타입 오류를 내는데, ML은 컴파일 타임에 알 수 있는 버그를 파이썬은 해당 코드를 실행시켜 보아야만 알 수 있다.

요약하면, 동적 타이핑 언어를 사용하면 Polymorphism 구현이 간단해지는 것은 사실이지만, 이 역시 컴파일 타임에 오류를 알 수 없다는 동적 타이핑 특유의 단점을 그대로 가지고 있다. 반대로 정적 타입 언어는 약간의 타입 정보만을 주면 이런 오류를 컴파일 타임에 모두 잡아낼 수 있다. C++, Java/C#처럼 뒤 늦게 Polymorphism을 추가한 경우 사용의 불편함이 있지만, ML처럼 처음부터 언어에 Polymorphism을 녹여넣고 타입 추론을 적극 활용하면 불편함을 최소한으로 줄이는 것이 가능하다.

똑같은 버그를 두고 1) 실행도 해보기 전에 컴파일하면서 잡는 방식, 2) 일일이 테스트하고 QA를 거쳐서 잡는 방식이 있다면 어느 쪽을 선택해야 할지는 자명하다. 소프트웨어 결함의 제거 비용은 시간에 대한 x^2 이상의 함수임을 잊지 말자. 현대의 스크립트 언어는 여러 가지 훌륭한 점이 많지만, 그 훌륭함이 동적 타입 체킹을 하기 때문은 아닐 것이다.
Write your message and submit