2015-07-28

[Monad] 사용 예제 - Try : 예외 처리하기

 현대의 대부분의 언어는 예외 처리를 위해서 try-catch 시스템을 사용한다. 예외가 발생할 수 있는 코드를 try 블록에 집어넣고, 예외를 throw하면 catch 블록에서 예외를 잡아서 처리하는 방식으로, 사실상 현대의 언어들이 예외를 처리하는 방식의 de facto라고 할 수 있다. 하지만 try-catch 시스템에는 여러 가지 문제가 있다.

 우선 다른 함수를 호출할 때, 어떤 예외가 발생할지 모른다. 그래서 Java 같은 언어는 함수의 시그니쳐에 발생 가능한 예외를 적는 checked exception이라는 개념을 만들었지만, RuntimeException은 어떤 예외가 발생할지 모른다거나, 모든 예외를 하나하나 등록하기 귀찮아서 그냥 Exception이 발생한다고 적거나 하는 이유로 그다지 쓸모없다는 인식이 강하고 C#을 비롯한 다른 언어들에서는 사용되지 않는다. 그저 API 문서에 함수가 어떤 예외를 발생시킬 수 있는지 적을 뿐이다.

 게다가 try-catch 시스템은 예외를 던지는 것이기 때문에 컨트롤 플로우가 뛰게 된다. 물론 현대 언어에서는 클로져나 람다 함수가 자주 사용되기 때문에 컨트롤 플로우가 직선적으로 흐르지 않는다. 하지만 try-catch 시스템은 도가 지나치다. 예외를 던지면, 예외를 잡을 때까지 컨트롤 플로우가 거슬러 올라간다. 그래서 try-catch에 의한 예외 시스템을 가독성이라는 측면에서 gotosetjmp/longjmp와 다를 게 없다고 비판하는 사람들도 있다.
 반면에 Try 모나드를 사용한 예외처리는 좀 더 예측할 수 있고 가독성 있는 코드를 작성할 수 있게 해준다.

 Try 모나드는 Option과 마찬가지로 두 모나드의 sum type이다. 하지만 하나의 타입 파라미터를 받는 Option과 다르게 타입 파라미터를 두 개 받는다. 이 두 타입은 각각 성공했을 때 결과 타입인 T와 에러가 발생했을 에러 타입인 E다. 그래서 Try 모나드는 Try[T, E]로 표현한다. Try 모나드가 가지는 타입은 각각 실행이 정상적으로 되어 T 타입의 값을 가지는 경우인 Ok[T]와 에러가 발생하여 E 타입의 에러가 난 경우인 Error[E]이다.

 Try는 두 개의 타입 파라미터를 가지고 있는 만큼 타입을 진행시키는 bind 함수도 두 개 있다.

 첫 번째 bind 함수는 Try[T, E] 타입을 Try[U, E]로 진행시킨다. 이 함수는 현재의 TryOk일 경우에만 실행된다. 현재의 값을 콜백 함수에 넣어 실행시키며, 콜백 함수의 결과가 이 bind 함수의 결과가 된다. 에러가 발생한 Try인 Error일 경우 콜백 함수를 실행시키지 않으며, 결괏값은 현재와 같은 Error 타입이 된다.

 두 번째 bind 함수 Try[T, E] 타입을 Try[T, F]로 진행시킨다. 이 함수는 위의 함수와 반대로 현재 값이 Ok일 경우 아무 일도 하지 않고, Ok인 값을 그대로 반환한다. 반면에 현재 TryError일 경우, 에러값을 콜백 함수에 넣어 그 결괏값을 반환한다.

 이처럼 Try 모나드는 정상적인 실행 결과를 가지거나 에러값을 가진다. 이를 서로 다른 bind 함수를 이용하여 진행시킨다. 따라서 컨트롤 플로우가 뛰지 않고 언제나 bind 함수에 넘긴 콜백을 실행시키는 것으로 진행된다. 또한, 에러가 타입으로 나오기 때문에 어떤 함수가 어떤 에러를 발생시킬지 쉽게 알 수 있고, 발생한 에러가 어떻게 처리되는지를 컴파일 타임에 알 수 있다.

2015-07-24

Phantom type - 구체화 되지 않는 타입 추가하기

 Phantom type은 받은 타입 파라미터 중에서 구조체의 선언에 기여하지 않는 타입 파라미터가 존재하는 타입을 말한다. 무언가 존재하는 것 같지만, 만져지는 실체가 없는 것이 유령 같다고 하여 phantom type이라고 불린다.

 내가 알기로는 Haskell에서 가장 먼저 도입된 것으로 알고 있다. Haskell이 phantom type을 사용하기 시작한 뒤로 파라메트릭 폴리몰피즘을 중시하며 강타입 타입 시스템을 가진 Scala나 Rust 같은 다른 언어에서도 사용된다.

 보통 phantom type을 사용하는 이유는 런타임 오버헤드 없이 컴파일 타임에 제약 조건을 추가하기 위해서다. phantom type에 사용된 타입 파라미터는 구조체의 값으로 사용되지 않기 때문에 구조체의 크기를 증가시키거나, 실행 시에 별도의 정보를 더 들고 다니지 않는다. 그저 컴파일 타임에 타입 체크하는 조건을 강화할 뿐이다.

이에 대한 좋은 예제가 rust by example에 있어서 가지고 왔다.1)

 위의 예제는 Length라는 구조체를 단위를 타입 파라미터로 받는 클래스로 선언하였다. 하지만 단위에 해당하는 UnitLength 구조체의 어떤 값으로도 사용되지 않는다. 따라서 Length 구조체의 크기는 f64의 크기와 같다.2) 하지만 Length<Mm>Length<Inch>와 다른 값이기 때문에 Length<Mm> 타입인 값과 Length<Inch> 타입인 두 값을 더할 수 없다.


1) 예제 코드는 Apache 2.0 라이센스로 배포되는 rust by example에 나오는 예제를 가지고 왔다.
2) rust는 구조체에 기여하지 않는 타입 파라미터를 만들 수 없어 PhantomData를 이용했다. PhantomData는 size가 0인 구조체이다.

2015-07-19

[ECMAScript 6] const - 상수 선언하기

 constlet과 같이 ECMAScript 6에 도입된 block 단위 상수 선언문이다.
 중복으로 선언할 수 없고, 선언 전에 사용할 수 없다는 것은 let과 같다. 거기에 const는 추가적인 제약이 더 붙는다.

 우선 const로 선언된 이름에는 값을 재할당할 수 없다. 이는 문법적으로 에러로 처리한다. 따라서 const에 값을 할당하는 구문을 실행할 때 발생하는 것이 아니라 const에 값을 할당하는 구문이 있는 함수가 선언될 때 에러가 발생한다.

 또한, const를 이용해서 상수를 선언할 때는 언제나 값을 초기화해주어야 한다. const로 선언된 상수에 값을 할당하지 못한다는 것을 생각하면, 당연한 일이다. 이 또한 문법 에러로, 초기화하지 않는 const를 선언할 때가 아니라, 선언하는 구문이 있는 함수를 선언할 때 에러가 발생한다.

 하지만 const도 상수 선언을 위한 완벽한 해결책은 아니다. const로 선언한 상수에는 값을 재할당할 수 없지만, 상수임에도 불구하고 값을 변경시킬 수 있기 때문이다. const로 선언한 상수는 어디까지나 값의 재할당을 막을 뿐, 그 값을 보호해주지 않는다.

 const가 완벽한 해결책인 것은 아니지만, 한계를 알고 적절하게 사용하면 좀 더 안정적이고 가독성 있는 코드를 작성할 수 있다.

2015-07-18

[ECMAScript 6] let - block 단위 스코프

 ECMAScript 6에서는 기존의 function scope였던 var 이외에 let이라는 block scope 변수 선언을 지원한다. let을 통한 변수 선언은, var를 통한 변수 선언과 다르게 hoisting 하지 않는다. 즉, 변수가 선언된 이후부터 변수가 유효하고 그전에는 해당 변수를 사용할 수 없다.


 hoisting을 없앤 것뿐 아니라 그 외의 실수하기 쉬운 부분을 에러로 처리하여 좀 더 안전한 코드를 작성할 수 있도록 하였다.

 예를 들면, ECMAScript 5에서는 hoisting 된 변수를 실수로 선언 전에 사용할 경우 그 변수는 undefined가 된다. 하지만 let을 사용하면, 변수를 hositing 하지 않을 뿐 아니라, 변수를 선언한 블록 안에서 선언 전에 해당하는 이름을 사용하는 것을 에러로 처리한다.

 또한, 이전에는 같은 스코프, 다시 말해서 같은 함수 안에서 변수의 선언문이 여러 개 있는 것이 정상적인 구문으로 처리되었다. 하지만 let을 사용한다면 같은 스코프에서 중복으로 선언하는 것이 에러로 처리된다.

 아쉬운 점은 위와 같은 에러가 함수의 선언에서 발생하는 것이 아니라, 실제로 그 구문을 실행할 때 발생한다는 것이다. 따라서 여전히 높은 커버리지의 테스트를 작성해야 안전한 코드라고 보장할 수 있다. 하지만 예전처럼 예상하지 못한 undefined가 나와서 문제가 발생한 부분이 아닌 다른 곳부터 추적해가야 할 일은 없어졌다.

 또 다른 문제는 babel에서 다르게 동작한다는 것이다. 파이어폭스나 크롬 등 모던 브라우저나 io.js는 ECMAScript 6를 지원하지만, 오래된 버전의 IE나 node.js 등에서는 아직 let을 지원하지 않는다. 그런 환경에서는 babel을 사용해야 하는데, babel에서는 중복된 선언이나, 선언 전에 사용하는 것을 에러로 처리하지 않는다.
 하지만 조만간 node.js에서도 let을 지원할 것이고, 오래된 버전의 IE도 없어지고 있으니 이런 문제는 곧 해결될 것이다.

2015-07-15

[Monad] 사용 예제 - Option : 존재하지 않음을 표현하기

 모나드는 많은 방식으로 사용되지만, 그중에서 Option 타입부터 설명하도록 하겠다. 그 이유는 Option 타입이 가장 기본적인 모나드이고, 가장 많이 사용되는 모나드이기 때문이다.
 Option 타입은 Haskell 및 몇몇 언어에서는 Maybe 모나드로 불리고, 언어 대부분에서는 Option 타입이라고 불린다. 이 글에서는 그냥 많이 사용되는 Option 타입이라는 이름을 쓰도록 하겠다.

 Option 타입이 해결하고자 하는 문제는 값이 존재하지 않음을 런타임 에러가 발생할 가능성 없이 표현하는 것이다. C++, C#, Java 등 기존의 많은 언어는 값이 존재하지 않음을 표현하기 위해서, null point를 사용하였다. 그리고 이 null point 문제는 컴파일 타임에 잡을 수 없는 NullPointerException을 발생시키기 때문에 조심해서 사용해야 했다.
 이런 문제를 해결하기 위해 아무런 동작을 하지 않는 객체를 만드는 Null Object pattern 같은 디자인 패턴을 이용하거나, null check를 한 겹 감싼 클래스를 만들거나 해서 문제를 최소화하고 있으나, 여전히 문제를 완벽하게 해결할 수는 없었다. Option 타입은 이에 대한 완벽한 해결책을 제공한다.

 Option 타입은 하나의 타입 파라미터를 받아, 그 타입의 값을 가지고 있을 수도 있고, 없을 수도 있다. Int 타입을 타입 파라미터로 받았다면, 타입은 Option[Int]가 되며, String 타입을 타입 파라미터로 받았다면, Option[String]이 된다. 즉, T 타입을 타입 파라미터로 받은 Option은 Option[T]가 된다. 이를 간단히 표현하기 위해서 T?같은 방식으로 표현하기도 한다.

 Option[T] 타입의 값은 T 타입의 값을 가지고 있을 수도 있고, 아무런 값이 없을 수도 있다. 이렇게 말하면 단순한 nullable과 다를 게 없어 보인다. 하지만 Option은 두 상태를 다른 타입으로 분리함으로써 nullable보다 안전한 방법을 제공한다.

 Option 타입은 두 타입의 sum type이다. 하나는 값이 존재하지 않음을 나타내는 None이라는 타입이고, 다른 하나는 무언가 값이 있음을 나타내는 Some이라는 타입이다. sum type을 지원하는 F#, rust, Haskell 같은 언어에서는 이를 sum type으로 표현하고, Scala나 전통적인 객체지향 언어에서는 Option이라는 interface의 구현체로 Some과 None이 있는 것으로 표현한다.
sum type을 표현할 방법이 없어 Scala처럼 상속관계로 표현하였다.

 None은 아무런 값도 가지고 있지 않음을 나타내는 타입이다. None 타입의 값에 bind operator를 호출해도 아무 일도 일어나지 않는다. 인자로 넘겨진 함수는 실행되지 않고, bind operator의 결과는 언제나 None이다.

 Some 타입은 무언가 값을 가지고 있음을 나타내는 타입이다. 어떤 타입의 값을 가졌는지 나타내기 위해 타입 파라미터를 받는다. T 타입의 값을 가지고 있는 Some 타입은 Some[T]라고 표현한다. Some[T] 타입은 반드시 T 타입의 값을 들고 있어야 한다. Some 타입이면서 내부적으로 값을 들고 있지 않는 상황은 올 수 없다.1) Some타입은 반드시 값을 가지고 있기 때문에 bind operator는 언제나 NullPointerException 없이 원하는대로 실행된다.

1) Scala는 언어적으로 이것을 금지하지 않는다. 하지만 Some이 null을 가지고 있도록 작성한 코드는 잘못된 코드이다. 이런 경우는 반드시 None을 써야 한다.

2015-07-14

[JavaScript] undefined 이해하기

 JavaScript에는 3가지 undefined가 존재한다.

타입으로서의 undefined

 우선 undefined는 타입이다.

 ECMA Script 5까지는 다음과 같은 6가지1) Built-in type이 있었다.
  1. number
  2. boolean
  3. string
  4. object
  5. null
  6. undefined
 undefined는 그중 하나다.

값으로서의 undefined

 undefined는 undefined 타입의 유일한 값이다.

 우선 값이 할당되지 않은 변수 혹은 값이 할당되지 않은 프로퍼티는 undefined가 된다.

 또한, 리턴문이 없는 함수나 리턴하는 값이 없는 리턴문으로 끝나는 함수의 실행 결과도 undefined가 된다.

 혹은 전역 프로퍼티인 undefined를 통해서 undefined 값을 얻을 수 있다.

전역 프로퍼티 undefined

 global context에는 undefined라는 이름의 프로퍼티가 설정되어 있다. 이 undefined는 undefined인 값을 가진다.
 ECMA Script 5 이후로 이 프로퍼티는 non-configurable이고, non-writable로 설정되어 있다. 따라서 변경할 수 없다.



1) ECMA Script 6에는 Symbol type이 추가되었다.

2015-07-11

Monad는 무엇인가

 모나드는 하스켈의 성공(?)과 함께 다른 언어에도 유행처럼 퍼져나갔다. 하지만 그 배경이 되는 이론이 너무 복잡하고 수학적이라 많은 사람이 하스켈을 맛만 보다 떠나게 된 이유가 되었고, 다른 언어들에서도 모나드라는 것은 금지어가 되다시피 하였다.

 하지만 어렵다고 모른 채로 살기에는 모나드는 너무 유용하다. 아니 유용한 정도가 아니라 이미 많은 곳에 사용되고 있다.

 그리고 사실 모나드는 쉽다. 아니 어렵긴 어렵다. 그 배경 지식인 카테고리 이론은 어려운 것 맞다. 하지만 그걸 알 필요 없다. 언제는 프로그래머들이 함수가 무엇인지 수학적으로 이해하고 사용하였나? 카테고리론을 이해하지 못해도 모나드는 얼마든지 사용할 수 있다.

 이제 슬슬 모나드가 무엇인지 궁금해졌을 것이다. 이쯤에서 한 문장으로 정리해서 설명했으면 좋겠지만, 아쉽게도 그건 좀 어렵다. 아마 이런 모습도 사람이 모나드를 이해하기 어려워하는 이유일 것이다. 그래도 최대한 풀어서 설명하면 모나드는 다음과 같다.

모나드는 다른 타입을 인자로 받는 타입이다.

 모나드는 타입이다. 기본적으로 모나드는 int나 string 같은 타입이다. 다른 점이 있다면, 모나드는 타입을 인자로 받는다. C++에 익숙한 사람이라면, template class를 다른 generic class를 지원하는 언어에 익숙한 사람이라면 generic class를 생각하면 된다.
 편의에 따라 앞으로 T라는 타입을 받은 모나드 M을 M[T]라고 표현하겠다.

    모나드 타입의 값을 생성하는 함수가 있어야 한다.

     모나드는 임의 타입의 값을 받아서 그 타입을 인자로 받은 모나드 타입의 값을 반환하는 함수가 있어야 한다. 다시 말하면, T 타입의 값을 받아서 M[T] 타입의 값을 반환하는 함수가 있어야 한다. 이는 하스켈에서 return operator라고 불리는 함수이다. 혹은 unit operator라고 부른다.

    다른 모나드 타입으로 진행하는 함수가 있어야 한다.

     이는 하스켈에서는 >>=라고 쓰이는 함수다. 이 함수는 M[T] 타입의 모나드가 있을 때, T 타입의 변수를 받아 M[U] 타입의 모나드를 반환하는 함수를 받아서, M[U] 타입의 값을 반환하는 함수다. 이 함수를 통해서 모나드에서 다른 모나드로 진행할 수 있다.
     보통 bind operator라고 부른다.


     정리하면, 모나드가 되기 위해서는 다음의 3가지 조건을 만족해야 한다.
    1. 타입을 인자로 받는 타입이다.
    2. unit operator가 있어야 한다.
    3. bind operator가 있어야 한다.
     위의 3가지 조건을 만족해야 모나드라고 부른다. 사실 수학적으로 따지려면 몇가지 성질을 더 만족해야 한다. 하지만 그건 어디까지나 수학적으로 그렇다는 것이다. 프로그래머가 사용하기 위해서는 위의 수준으로 이해해도 사용하는데 큰 문제 없다.

     모나드가 무엇인지 설명하였지만, 추상적인 내용이라서 모나드가 무엇이고, 어떻게 사용되며, 어떤 장점이 있는지 잘 감이 안 올 것 같다. 그래서 다음 글에서 모나드가 어떻게 사용되는지를 통하여 좀 더 구체적으로 알아보도록 하겠다.

    사용 예제

    2015-07-07

    [npm] publish 하기 전에 테스트하기


     npm publish라는 명령어를 통해 내가 만든 라이브러리를 npm을 통해 배포할 수 있다.

     보통의 경우라면 문제없다. 하지만 TypeScriptCoffeeScript를 이용하여 컴파일된 라이브러리를 배포하거나, webpack같은 것을 이용해서 라이브러리들을 패킹해서 배포할 경우 npm publish를 하는 것은 마음 놓고 할 수 있는 작업이 아니다.
     배포 전에 명령어를 수행하기 위해 prepublish에 스크립트를 저장하거나, npmignore에 배포하지 않을 파일들을 추가하거나 하는데, 이런 것들이 제대로 되어 있는지 실제 npm에 올리기 전에는 알 수 없기 때문이다.

     그럴 때 사용하기 좋은 커맨드가 npm pack이다. npm pack을 이용하면, 제대로 된 파일들을 배포할지 확인할 수 있다. npm pack을 실행하면 prepublish를 실행시키고, npmignore에 들어있는 파일들이 빠진 파일들이 {라이브러리 이름}-{버젼}.tgz 라는 이름의 압축파일이 만들어진다.

     그러면 해당 라이브러리를 사용하는 프로젝트를 만들고, npm install {압축파일 경로}를 실행하면, 실제로 publish된 라이브러리를 설치한 것처럼 라이브러리를 설치하여 테스트할 수 있다.

    2015-07-06

    [Python] Gil과 Python

     지난번에 언급했듯이 CPython이나 PyPy는 Global interpreter lock(a.k.a. GIL)을 이용해서 동시에 2개 이상의 스레드가 실행되지 못하게 함으로써 스레드 간 동기화를 보장한다.

     하지만 이는 CPython과 PyPy가 thread를 구현하는 방법일 뿐, Python 스펙에는 동시에 2개 이상의 스레드를 실행시키지 말라거나, GIL을 사용하라거나 하는 말은 없다. 그저 CPython과 PyPy가 효율성을 떨어뜨리더라도 GIL을 사용하는 것이 이득이 되는 것이 많다고 생각해서 GIL을 사용하도록 구현한 것뿐이다.

     그래서 Python 구현체 중에서 .net framework 위에서 돌아가는 Iron Python이나 JVM 위에서 올라가는 Jython의 경우 GIL을 사용하지 않는다.

    2015-07-05

    sfuture - JavaScript에서 concurrent한 프로그램 작성하기

     sfuture는 JavaScript에서 사용할 수 있는 컨커런트한 프로그램을 쉽게 작성할 수 있도록 도와주는 라이브러리다. 이름에서 알 수 있듯이, Scala의 Future를 JavaScript로 포팅한 것으로, 내부적으로 ECMA Script 6 promise를 사용하고 있어서, promise가 구현된 환경(node.js 0.12.0 이상, 대부분의 모던 브라우저)에서는 아무런 디펜던시 없이 바로 사용할 수 있다.

     만들게 된 이유는, 전에 rhino engine을 이용해서 Scala로 작성한 beyond라는 게임 서버 엔진이 있었는데, 여러 가지 문제가 있어서 이것을 node.js로 포팅하게 되면서 필요하게 되었다.
     Rhino를 사용할 때는 Java class를 그대로 재사용할 수 있었기에 인터페이스만 수정하는 수준이면 가능했는데, node.js로 포팅해오면서 그럴 수 없게 되었다. 처음에는 Future를 포팅해올지, async등 기존의 라이브러리들을 이용하도록 할지 고민했다.
     하지만 async를 실제로 사용을 해보니, 이것도 결국 콜백 헬을 없앨 수 있는 건 아니고, 오히려 코드가 길어질수록, 가독성만 떨어뜨리는 느낌이라서 결국 새로 구현하였다.

     구현은 타입스크립트로 되어 있지만, publish 전에 컴파일하여 자바스크립트 파일만 배포한다. 원본 소스를 보고 싶으면, 깃헙 리파지토리를 보길 바란다.