레이블이 scala인 게시물을 표시합니다. 모든 게시물 표시
레이블이 scala인 게시물을 표시합니다. 모든 게시물 표시

2015-02-21

[Scala] 관련있는 데이터를 묶어서 사용하기 - alias와 case class

 Scala에서 여러 개의 값을 묶어서 새로운 타입을 정의하는 방법은 여러 가지가 있다.

Type alias

 가장 쉬운 방법은 튜플의 alias를 만드는 것이다.

 위의 코드의 4번째 줄에서 month와 date를 선언하면서 Date 타입의 변수를 쪼개 새로운 값에 할당하는 것을 decomposition이라고 한다. 이렇게 decomposition을 이용하면 구조체의 값들을 쉽게 다른 값에 할당할 수 있고, 혹은 아래와 같이 패턴매칭을 이용해서 사용할 수 있다.


 하지만 튜플의 alias를 만드는 방식은 큰 문제가 있다. 이런 방식은 타입 세이프 하지 않다. 예를 들어 위에서 정의한 Address 타입과 함께 아래와 같이 정의된 Date 타입이 같이 사용된다면 둘 다 실제로는 같은 Tuple2[String, Int] 타입이기 때문에 패턴매칭으로는 AddressDate를 구분할 방법이 없다.



case class

 튜플의 alias가  타입 세이프 하지 않다는 문제를 해결하기 때문에 보통은 data composition에 case class를 사용한다. case class를 사용하면, 실제로는 다른 같은 타입들의 묶음과 구분할 수 있을 뿐 아니라, 내부 값에 이름으로 접근해 꺼낼 수 있어서 내부 값을 더 쉽게 읽을 수도 있다.

2015-01-07

[Scala] 함수의 상속 가능성을 deprecated 시키기

 흠.... 블로거로 옮기고 나서 글을 쓰기 시작한 지 1년이 지나가는데, 이렇게 써도 되는지 고민되는 글은 처음이다. 사실 블로거로 옮기기 전에 폭파한 블로그가 2개인가 3개인가 있는데 당시에는 글을 써도 되는지 고민 없이 막 썼었다. 아는 게 없어 용감했었나 보다. 근데 어느 정도 아는 게 생기고 나니 혹시 나로 인해 잘못된 내용을 배워가는 사람이 있을까봐 최대한 정석에 가까운 내용만을 쓰려고 노력했다. 근데 이번에 쓸 글은 어떻게 봐도 편법에 해당하는 내용이다. 그래서 아래와 같은 경고로 글을 시작하겠다.

경고: 지금부터 설명할 방법은 권장되는 방법이 아닌 hacky한 방법입니다. 사용함으로 인해 생기는 문제는 책임지지 않습니다.

 지난번 글에서 deprecated annotation을 이용해서 API를 변경하는 방법을 설명했었다. 이번에 설명할 내용은 function prototype은 변경하지 않고 클래스의 상속 가능성을 deprecate 시키는 것에 관한 내용이다.

 Scala에는 sealed modifier나 final modifier를 이용해서 함수의 상속성을 제한하는 기능이 있다. 이 두 modifier에 대해서 간단하게 설명하면 sealed modifier는 다른 파일에서는 해당 클래스를 상속 못하게 막는 modifier이고, final modifier는 클래스를 상속 못하도록 하거나 함수의 override를 막아주는 modifier이다. 즉, API로 제공되던 클래스에 sealed나 final을 붙이거나 함수에 final을 붙이면 상속할 수 있던 클래스나 override할 수 있던 함수를 상속 불가능하게 만들게 된다. 이런 상황에서 쓰기 위해 설계되었던 것이 annotation이 deprecatedInheritance과 deprecatedOverriding이다.

 원래 deprecatedInheritance와 deprecatedOverriding는 Scala 2.10에 들어갈 예정이었다. 하지만 디자인 이슈를 결정하지 못하고 방황하는 사이에 2.10을 출시할 일정이 다가왔고 결국 구현체는 있지만, API로는 공개되지 않은 채 2.10이 출시되게 되었다. 따라서 정상적인 방법으로는 Scala 2.10이나 Scala 2.11에서 이 두 annotation을 사용할 방법은 없다.

 하지만 상속 가능성을 deprecate 시키는 기능은 라이브러리 제작자라면 포기하기 아까운 기능이다. 가장 좋은 것은 이 기능이 Scala 라이브러리에 정식으로 반영되기를 기다리는 것이지만, 만약 어떻게든 지금 당장 사용하길 원한다면 다음과 같은 비정상적인 방법을 써야 한다.

 현재 이 두 annotation은 scala package에 private으로 되어있다. 따라서 scala라는 이름의 package object를 만들고 그 안에서 DeprecatedInheritanceDeprecatedOverriding라는 public 한 새로운 type을 만드는 것으로 scala package 밖에서도 두 annotation을 사용할 수 있게 된다.

 하지만 시작부터 경고하며 시작했듯이 이는 hacky한 방식이다. 언제 이름이 바뀔지 모르고, 심지어 운이 없다면 언제 사라질지도 모르는 기능이다. 거의 편법에 한없이 가까운 방식이므로 사용할지 말지는 알아서 판단해서 선택해야 한다.

Scala annotation

2015-01-06

[Scala] deprecated annotation - 호환성을 보장하며 API 수정하기

 크게는 구조의 변경을 위해서부터, 작게는 오타 수정까지 API를 수정할 일은 많이 있다. 하지만 API를 변경하는 것은 복잡하고, 오래 걸리고, 어려운 일이다.

deprecated

 API를 변경한다고 해서, 전에 사용하던 API를 바로 지우면, 그전 API를 사용하는 사람들이 급격하게 코드를 바꿔야 한다. 반대로 지우지 않고 2개의 API를 둔다면, 전 API를 사용하던 사람이 코드를 고치지 않을 뿐 아니라, 새로 코드를 작성하는 코드에도 사용하지 않아야 하는 API를 사용하게 돼서 결국 2벌의 코드를 유지 보수해야 한다. 이런 상황에서 사용하는 것이 deprecated annotation이다.

 위의 예제와 같이 deprecated annotation을 붙이면 해당하는 함수를 호출하는 코드를 컴파일할 때, 컴파일은 되지만 경고 메시지를 출력해준다. 게다가 내부적으로 Java의 Deprecated annotation을 더해주기 때문에, 자바 코드에서 deprecated 된 스칼라 함수를 호출해도 경고 메시지가 출력된다.
 사실 Scala의 deprecated annotation은 Java의 Deprecated annotation과 에러 메시지와 version을 정해줄 수 있다는 것 외에는 큰 차이가 없다. 그렇기에 Scala는 다른 annotation을 더 지원한다.

deprecatedName

 함수의 type signature를 바꾸는 일만큼, 함수의 type signature는 그대로 두고, 함수 인자의 이름만 바꿔야 하는 일도 많이 생긴다. 그 이유는 다양한데 기존에 사용했던 이름이 부적절했기 때문일 수도 있고, 함수의 동작이 바뀌면서 넘겨야 하는 인자가 변했지만, 우연히 타입은 같은 경우였을 수도 있다.
 Java는 named parameter를 지원하지 않기 때문에 인자의 이름만 변경하는 경우가 문제 되지 않는다. 하지만 Scala처럼 named parameter를 지원하는 언어에서는 함수의 이름을 바꾸는 것도 문제가 될 수 있다. 이럴 때 사용하기 위한 것이 deprecatedName이라는 annotation이다.  deprecatedName을 이용하면 아래와 같이 새 이름과 deprecated 된 이름 모두 쓸 수 있다. deprecated 된 이름을 쓰면 컴파일 시 경고 메시지를 출력하지만, 성공적으로 컴파일 된다.


 처음 코드를 작성할 때 수정할 일이 없도록 코드를 작성하는 것이 가장 좋겠지만, 현실적으로 그런 것은 불가능하다. 그래서 Scala를 이용해 라이브러리를 작성할 때는 deprecated annotation을 이용해서 하위 호환성을 보장하며 코드를 수정하고 적당한 시기에 deprecated 된 코드를 지운다.
 deprecated 된 코드를 오래 들고 있을수록 라이브러리를 사용하는 사람에게는 변화를 반영할 여유를 주게 되지만, 라이브러리를 작성하는 입장에서는 쓸데없는 짐을 지고 가는 것이기도 하다. 그래서 일반적인 라이브러리 개발의 경우 major version up 혹은 minor version up에서 deprecated 된 부분을 지우는 것이 일반적이지만, in-house 개발일 경우 deprecated annotation을 사용하지 않고 바로 코드를 지우기도 한다. deprecated 된 코드를 언제 지우는가는 전적으로 개발자가 프로젝트의 성격을 보고, 라이브러리를 사용하는 목적을 보고 적절히 선택해야 한다.

Scala annotation

2014-10-31

[Scala] 함수 선언과 호출에서의 괄호 생략


 위에서 보듯이 Scala에서는 인자가 0개인 함수를 선언하거나 호출할 때 괄호를 생략할 수 있다.1) 컴파일 타임에 괄호 생략에 대해 아무런 제약도 하지 않고, 컴파일된 byte code에도 차이가 없다. 그렇다고 아무 괄호나 생략해도 되는 것은 아니다. 언어적으로는 괄호 생략에 관해 아무 제약을 하지 않지만, side-effect가 없을 때만 괄호를 생략하여야 한다.2)

 side-effect가 없는 함수에 대해서만 괄호를 생략하는 것은 함수가 선언된 모양만으로 그 함수가 하는 일을 추측할 수 있기 때문에 코드의 가독성을 높이는 데 도움이 된다. 그렇다면 어째서 컴파일 타임에 side-effect가 없는 함수에 대해서만 괄호를 생략할 수 있도록 강제하지 않을까?
 이유는 간단하다. 컴파일러가 함수가 side-effect가 있는지 없는지 구분할 수 없기 때문이다. Haskelmonad같은 개념을 도입하여 side-effect를 분리해 낼 수 있을지도 모르지만, Scala는 그런 개념을 도입하지 않고, 사용자에게 책임을 넘겼다.

 side-effect는 functional 프로그래밍에서 매우 중요한 부분이기 때문에 이것에 대한 책임을 프로그래머에게 넘겼다는 점에서, Scala의 안 좋은 부분이라고 평하는 사람도 있다. 하지만 나는 이 정도 자유는 프로그래머에게 넘기는 것이 적당하다고 생각한다.

 예를 들어, 무언가 side-effect가 없는 일을 하는 함수가 있을 때 그 함수가 호출되면 logging을 하도록 수정하였다고 생각해보자. 이 함수는 side-effect가 있는 함수라고 봐야 할까? side-effect가 없는 함수라고 봐야 할까? 엄밀한 의미에서 따져본다면 side-effect가 있는 함수라고 분류해야겠지만, 실제 돌아가는 logic을 생각해보면 이 함수를 side-effect가 있다고 구분하는 것은 뭔가 억울(?)하다.

1) http://docs.scala-lang.org/style/method-invocation.html#arity-0
2) http://docs.scala-lang.org/style/naming-conventions.html#parentheses

2014-09-30

[Scala] implicit keyword (4) - 마치며

 지난 3번의 posting으로 Scala에서 implicit keyword의 3가지 사용법인 implicit converter, implicit classimplicit parameter에 대해서 알아봤다. 이 3가지 모두 약간씩 다르지만, 명시적으로 적어야 하는 코드를 줄여 코드를 간편하게 만들어 주는 역할을 해준다.
 그 때문에 적절하게 사용하면 verbose 코드를 줄여주어 가독성을 높여줄 뿐 아니라, Scala를 이용하여 domain-specific language(a.k.a. DSL)를 만들기 쉽게 만들어 준다. 실제로 많이 쓰이는 Play framework의 Configuration file이나 route file 또는 Scala와 Java 양쪽에서 많이 쓰이는 SBT는 Scala로 구현한 일종의 DSL로 Scala를 이용하여 static하게 compile 가능하게 되어있다.

 하지만 이런 power는 잘못 사용하면 해가 된다. 뭐 어떤 기능이 안 그러겠는가만은 특히 DSL을 만들기 쉽게 해준다는 것은 결국 기존의 언어가 아닌 새로운 언어를 정의하기 쉽게 된다는 것이고, 너무 많이 사용된 라이브러리는 Scala가 아닌 새로운 언어를 다시 배워야 한다는 문제가 생기기도 한다.
 실제로 SBT가 Scala로 컴파일되는 DSL이지만 필자는 아직도 SBT의 문법을 완벽히 이해하지 못했을 정도로 완전히 새로운 언어가 되었다. 또한, 많은 사람이 Scala를 처음 사용하면서 어려워하는 부분이 implicit converter와 implicit class로 인해 현재 class에 정의되지 않은 함수가 호출되는 것이다.1)

 Implicit keyword는 Scala의 난이도를 높여주는 장애물인 것은 분명하지만, 제대로 이해하고 있으면 새로운 세상을 보여주는 받침대가 된다.

1) 이전 글에서도 말했지만, 이러한 특성 때문에 Scala를 처음 사용할 때는 다른 언어보다 더욱더 IDE가 필요하다. 다시 한 번 IntelliJ IDEA를 추천한다.

Scala implicit keyword

[Scala] implicit keyword (3) - Implicit parameter

 implicit keyword의 3번째 사용법은 implicit parameter와 implicit value를 선언하는 것이다. implicit parameter는 함수를 호출할 때 인자를 생략할 수 있도록 해준다. 정확히는 함수를 호출할 때 인자를 생략하면 자동으로 그 인자를 채워서 실행시켜준다.
 이때 무엇으로 채워주는지를 정하는 규칙은 2가지가 있다. 1)

 첫 번째 규칙은 함수가 호출된 scope에서 prefix 없이 접근할 수 있는 implicit parameter와 같은 타입의 변수 중에 implicit label이 붙은 변수를 사용하는 것이다. 이 규칙에는 2가지 타입의 변수가 해당한다. 하나는 implicit parameter이고, 다른 하나는 해당 스코프에 선언된 implicit modifier가 붙은 local 변수이다. 이때 함수가 호출된 스코프에서 해당하는 규칙에 적용되는 변수가 2개 이상 있으면 "ambiguous implicit values"라는 메시지와 함께 컴파일 에러를 발생시킨다. 여기서 주의해야 할 점은 반드시 implicit parameter와 같은 타입의 변수여야 한다는 것이다. 설령 implicit converter로 변환 가능한 변수가 있어도 이는 implicit parameter로 넘어가지 않는다.

 두 번째 규칙은 companion object에 정의된 변수 중 implicit parameter와 같은 타입으로 선언된 implicit label이 붙은 변수를 사용하는 것이다. 이는 첫 번째 규칙에 해당하는 변수가 없을 때 사용되고, 첫 번째 규칙과 마찬가지로 두 번째 규칙에 해당하는 변수가 2개 이상 있으면 "ambiguous implicit values"라는 메시지와 함께 컴파일 에러를 발생시킨다. implicit converter를 적용하지 않는 것도 첫 번째 규칙과 같다.

 Scala library에서 implicit parameter를 잘 활용하는 대표적인 예는 Future class다. Future class의 method들은 언제나 자신이 실행될 ExecutionContext를 필요로 한다. 원래대로라면 함수를 호출할 때마다 ExecutionContext를 넘겨야 한다. 하지만 이를 implicit parameter로 선언하여 함수를 호출할 때 명시적으로 ExecutionContext를 넘기지 않아도 되도록 하였다.
 이는 boilerplate 코드를 간단하게 만든다는 장점도 있다. 하지만 다른 무엇보다도 서로 다른 symbol을 가지는 Option이나 List의 list comprehension method들2)과 같은 look and feel을 가지게 해준다는 장점이 크다. 같은 look and feel을 가지는 것이 단순히 감성적인 문제 때문에 좋은 게 아니다. Scala는 list comprehension method들에 대해서 for comprehension3)을 사용할 수 있게 해주기 때문에 좋은 것이다.

1) http://www.scala-lang.org/old/node/114
2) map, flatMap, filter 등...
3) http://www.scala-lang.org/old/node/111 for comprehension도 Scala의 중요 feature 중 하나이기 때문에 후에 따로 설명하도록 하겠다.

Scala implicit keyword

2014-09-07

[Scala] implicit keyword (2) - Implicit class

 Scala에서 implicit keyword를 사용하는 또 다른 예제는 Implicit class이다.

 Implicit class는 이미 존재하는 class에 새로운 함수를 추가하여 확장하기 위해서 사용된다.
 정확히 말하면 원래 있던 클래스 자체를 바꾸지는 않고, class를 implicit conversion해서 호출할 수 있는 함수를 추가할 수 있게 해준다.
 Standard library에 있는 implicit class의 대표적인 예는 Duration class들이다.

 위의 예제에서 보듯이 DurationIntDurationDouble이라는 implicit class가 각각  Int와 Double을 확장시켜서 seconds/milliseconds/nanoseconds 같은 method를 추가해서 Duration 객체를 만들 수 있게 해준다.

 이 설명을 보면 지난 글에서 설명해줬던 implicit converter가 해줄 수 있는 일과 크게 다르지 않아 보인다.
 사실 Implicit class는 implicit converter의 syntactic sugar에 불과하다. 내부적으로 implicit class로 정의된 class는 class의 이름과 같은 implicit converter를 같은 scope에 추가한다.
 즉, 아래와 같이 정의된 implicit class는

 아래와 같이 변환된다.


 내부적으로 implicit converter에 해당하는 함수를 정의하는 것이기 때문에 몇 가지 제약사항이 있다.
 첫 번째 제약사항으로 implicit class는 trait이나 classobject 안에 정의되어야 한다.
 이는 함수의 정의가 trait이나 class나 object 안에 정의되어야 하기 때문이다. 그래서 보통 implicit class들은 package object 안에 정의된다.
 두 번째 제약사항은 non-implicit인 parameter가 반드시 1개인 constructor를 가지고 있어야 하는 것이다.
 implicit parameter1)는 여러 개 가질 수 있으니 그 외에 넘기고 싶은 인자가 있으면 implicit parameter로 넘겨야 한다.
 세 번째 제약사항은 implicit class가 선언되는 scope 안에 implicit class와 같은 이름의 무언가가 있을 수 없다는 것이다.
 이것도 내부적으로 implicit converter에 해당하는 함수가 만들어지기 때문이다. 이 때문에 내부적으로 class와 같은 이름의 object를 만드는 case class는 implicit class가 될 수 없다.

1) implicit parameter에 대해서는 다음 글에서 설명하도록 하겠다.

Scala implicit keyword

2014-09-06

[Scala] implicit keyword (1) - implicit converter

 Implicit converter가 하는 일은 이름 그대로 value를 적절한 type으로 implicit하게 convert하는 것이다.
 Scala는 기본적으로 strong typed language이기 때문에 implicit conversion을 지원하지 않는다. 함수를 호출할 때 함수의 symbol에 맞지 않으면 바로 컴파일에러가 발생한다.
 implicit conversion을 하기 위해서는 해당 scope 안에 implicit converter를 구현해두어야 한다.

 Implicit converter의 선언은 다음과 같다.

 Implicit converter는 unary function에 implicit keyword를 붙여주는 것으로 정의된다.
 이렇게 Implicit converter를 구현해두면, sizeToRectangle 함수가 정의되어 있는 scope에서는 Size 객체가 Rectangle 객체로 implicit conversion이 가능해진다.

 예를 들어 Scala에서 자주 쓰이는 Option이라는 class를 보자.
 Option은 c#의 nullable과 유사한 type으로 원소가 없을 수 있는 객체이다. 이는 다른 관점에서 보면 최대 원소가 1개인 collection이라고 볼 수 있고, Option을 사용할 때 collection처럼 list comprehensive method1)를 사용하는 것을 권장한다.
 하지만 Option코드를 보면 Option은 Iterable이나 Traversable을 상속받지 않았고, 일부 함수를 제외하고 collection처럼 이용는데 필요한 모든 함수를 가지고 있지도 않다.
 하지만 Option을 Iterable로 변환해주는 option2Iterable이라는 implicit converter가 있기 때문에 iterable이 필요한 경우 자동으로 convert해서 넘겨준다.

 Implicit converter에 대응되는 개념이 다른 언어에 없는 것은 아니다.
 C++에서는 unary constructor를 구현하거나 assignment operator나 type case operator를 구현하여 implicit conversion을 구현한다. C#은 conversion operator를 구현하여 implicit conversion을 구현할 수 있다.
 하지만 implicit converter는 이들보다 더 표현력 있고 확장성 있는 개념이다.

 implicit converter가 다른 언어의 implicit conversion보다 표현력 있고 확장성 있다는 것을 보여주는 좋은 예제가 RichInt다.
 위의 예제는 Scala가 기본으로 제공해주는 Int타입의 확장함수 들이다. 하지만 Int 구현체에는 위의 함수들에 대한 구현이 없다. 왜냐하면 Scala는 정책상으로 Int class내에는 primitive operator2)에 대해서만 구현하도록 하고 있기 때문이다.
 그렇다면 위의 함수들은 어디에 정의되어 있을까?
 primitive operator가 아닌 함수들은 RichInt에 구현되어 있다. Int에서 RichInt로 implicit convert해주는 함수가 정의되어 있기 때문에 Int로 선언된 변수들에 대해서도 RichInt에 정의된 함수를 쓸 수 있는 것이다.

 여기서 Scala의 implicit converter가 C++에서 제공하는 implicit conversion보다 가지는 장점을 알 수 있다. C++에서 type case operator나 unary constructor를 구현하거나 C#에서 conversion operator를 구현하더라도 implicit type casting을 해서 함수의 symbol을 찾지 않는다.
 다시 말해서  C++이었다면 RichInt와 implicit conversion을 위한 operator들이 구현되어 있다고 해도 symbol을 찾기 위해서는 아래와 같은 추가 작업이 필요하다.
 하지만 Scala는 instance의 현재 class에 해당하는 symbol의 함수가 없어도 현재 scope에 있는 implicit converter를 1번 적용해서 나오는 class에서 해당하는 symbol의 함수를 찾을 수 있으면, 해당하는 함수를 부른다.

 또 기존의 implicit conversion에는 큰 문제가 있는데, implicit conversion의 정의가 class 내에 정의되어야 한다는 것이다. 즉, A에서 B로 가는 implicit conversion의 구현을 하고 싶으면 A와 B 둘 중 최소한 하나는 내가 수정할 수 있는 class여야 한다는 것이다.
 library에서 제공해주는 class 간의 implicit conversion을 구현할 방법이 없고 이 때문에 쓸데없이 Wrapper class를 만들어야 하는 일이 종종 발생한다.
 게다가 class에 implicit conversion이 정의되기 때문에 특정한 함수에서만 implicit conversion을 허용하거나, 반대로 특정 함수에서는 implicit conversion을 허용하지 않는 것을 할 수 없다.

 물론 이런 강대한 표현력은 가끔 과다해서 단점이 되기도 한다. 어떤 객체의 함수가 그 객체의 class 정의 밖에 존재하는 것이기 때문에 코드를 읽는데 어려움을 준다. 가끔은 다른 부분에서 자연스럽게 썼던 함수를 implicit converter를 import하지 않아서 사용하지 못하는 일도 있다.
 하지만 이 정도 문제는 좋은 IDE(Integrated Development Environment)가 있으면 쉽게 해결되는 문제다. 3)
 너무 과하지 않을 정도로 적당히4) 사용하면 한층 더 확장된 세계를 볼 수 있게 된다.

1) filter, map, flatMap ...
2) unary -, unary ~, +, * ...
3) 다시 한 번 IntelliJ IDEA를 추천한다.
4) 프로그래밍 언어는 결국 도구다. 어떤 도구를 쓰더라도 도구를 어떻게 사용할지는 그 도구를 사용하는 사람, 다시 말해 프로그래머가 잘 결정해야 한다.

Scala implicit keyword

2014-09-05

[Scala] implicit keyword (0)

 Scala의 가장 인상적인 keyword를 말하라고 하면 많은 사람이 implicit을 꼽을 것이다.
 implicit은 scala의 확장성에 무한한 힘을 주는 keyword임과 동시에 코드를 읽을 때 헬 게이트를 여는 주범이기도 하다. 1)

 implicit keywod는 Implicit converter, Implicit class, Implicit parameter의 3가지 목적으로 이용된다.
 앞으로 3번에 걸쳐서 각각에 대해 설명하도록 하겠다.

1) 개인적으로 코딩할 때 대부분 언어를 vim으로 작업해왔지만, scala를 배우면서 IntelliJ라는 IDE를 사용하기 시작했다.

Scala implicit keyword

2014-07-28

[Scala] sealed modifier

 Scala에는 sealed라는 독특한 modifier가 있다.

 같은 방식으로 사용하는데, sealed라는 modifier를 붙인 class는 선언된 파일 안에서만 상속받을 수 있다. 선언된 파일이 아닌 다른 파일에서는 사용할 수는 있지만 상속받으려고 한다면 컴파일 에러가 발생한다.
 다만, sealed의 자식은 sealed가 아니어서 주로 final modifier와 함께 쓰인다.

 Scala library 중 sealed modifier를 사용하는 대표적인 예제는 OptionTry다.
 Option은 Some과 None 2개의 자식이 있고, Try는 Success와 Failure 2개의 자식을 가지고 있다.
 Option과 Try는 sealed로 선언되어 같은 파일에서 선언한 Some, None과 Success, Failure 이외에는 자식을 가지지 못하게 하고, Some, Success, Failure는 final class로 None은 상속할 수 없는 object로 선언하여 사용자가 추가로 상속받을 방법을 막아놓았다.

2014-06-10

[Design Pattern] Loan pattern - resource를 안전하게 사용하기

 언젠가 썼던 글에서도 설명했듯이 C++에서는 RAII를 이용하여 Resource의 안전한 해제를 보장하는 것을 넘어 control flow를 제어하는 역할까지 해준다.
 하지만 Garbage Collection을 사용하는 C#이나 Java 같은 언어에서는 언제 메모리가 해제될지 모르기 때문에 RAII pattern을 사용할 수 없다. 그래서 코드의 실행을 보장하기 위하여 finally 구문이 생기게 된 것이다.

 try finally를 사용하는 일반적인 방법은 아래와 같다.

 exception이 발생할 수 있으면 try 구문으로 감싸고 반드시 실행시켜야 하는 코드를 finally에 두는 것이다.

 하지만 위의 코드는 딱 보기에도 재사용성이 떨어진다.
 다른 동작을 하기 위해서는 언제나 try / catch를 써야 해서 boilerplate한 코드가 반복되기도 한다.
 이를 해결하는 방법은 없을까?

 Scala에서는 이를 해결하기 위하여 resource를 빌려주는 방식을 자주 이용한다.
 resource의 management를 하는 함수(lender)가 있고, resource를 사용하는 함수(lendee)에게 빌려주어 잠시 사용하게 해주는 것이다.
 이를 이용하여 API의 encapsulation과 reusability를 올릴 수 있다.
 우선은 다음 예제를 보자.

 위의 예시에서는 executeSql이라는 함수가 connection string과 Statement를 인자로 받는 Function을 인자로 받는다(말은 복잡한데 실제로 복잡한건 아닌데....... 말로 설명하려니 복잡해졌다.).
 첫 번째 인자로 받은 connection string을 이용하여 Statement라는 resource를 만들어 관리하게 된다. 즉, executeSql이 lender가 되는 것이다.
 그리고 두 번째 인자인 Statement를 인자로 받는 Function을 landee로 삼아 자신이 만든 Statement를 빌려주어 원하는 작업을 수행하게 한다.

 이런 pattern을 resource를 관리하는 lender와 빌려서 사용하는 lendee로 나뉘기 때문에 lender-lendee pattern으로 부르는 사람도 있지만, 보통은 loan pattern이라고 부른다.

 loan pattern은 resource의 관리/사용을 구분하였기 때문에 resource를 안전하게 사용하는 것을 보장해준다.
 또한, 변하는 부분인 사용에 해당하는 부분만을 재정의하면 되기 때문에 손쉽게 코드를 재사용 가능하게 만들어주고, 함수를 library로 제공하게 되면 user들에게 resource의 할당과 해제를 숨길 수 있어서 encapsulation이라는 측면에서도 매우 좋다.
 실제로 scala library의 많은 부분은 loan pattern을 사용하고 있다.

2014-04-12

Actor model and akka

다음 프로젝트로 scalable 한 게임 서버프레임워크 구현을 진행 중이다.
아직 구상 중이라 결정된 것은 없지만, scalability와 functional 한 특성을 동시에 살릴 수 있는 scalaakka프레임워크가 후보로 들어왔고 이에 대해 간단하게 정리하여 발표할 기회가 있었다.
일단 발표자료는 간단하게 키워드들만 적었기에 이에 대해 보충 설명을 해보고자 한다.

akka는 무엇인가

 akka는 scala로 구현된 concurrency 제어를 위해 actor model을 도입한 프레임워크로 java와 scala API를 제공한다.
우선 akka는 actor model을 기본으로 하고 있기 때문에 akka의 특성을 이해하려면 actor model을 이해해야 한다.

Actor의 특징

 actor model은 간단히 설명하면 behavior, state, mailbox로 구성된 actor를 기본 단위로 하는 message processing을 이용하여 behavior를 비동기적으로 실행하는 model이다.
 이때 기본단위가 되는 actor는 몇 가지 특징이 있다. 우선 각 actor는 서로 간에 공유하는 자원이 없고 서로간의 state를 건드릴 수 없고, 오로지 message를 이용해서만 간섭할 수 있다.
 message는 mailbox에 쌓였다가 들어온 순서대로 처리된다.
 실행되는 behavior는 message에 의해 결정되고, 할 수 있는 일은 자신의 state를 바꾸거나, child actor를 만들거나, child actor를 죽이거나 다른 actor에 message를 보낼 수 있다.

 actor model의 actor는 사실 OOP에서 말하는 object와 매우 비슷하다.
 object는 member variable(state)을 가지고 있고, 어떤 방식으로 동작할지 method(behavior)를 가지고 있다.
 method는 다른 object를 만들거나, 자기가 관리하는 object를 부수거나 다른 object의 method를 호출하는 일을 한다.
 현대의 OOP 언어들(Java, C#, c++ 등)만을 사용한 사람들은 message를 이용해 method를 호출한다는 개념이 익숙하지 않을 수 있다.
 하지만 과거의 pure한 OOP 언어들(Simula, Smalltalk)에서는 다른 object에 message를 보내면 받은 message에 해당하는 method를 부른다는 개념이 있고, 이것이 간략화되어 object의 method를 호출한다는 개념이 된 것이다.
 그렇다면 OOP의 object와 actor model의 actor는 어떤 차이가 있을까?

 그 차이는 단 한 가지이다. Object의 method는 message를 보낸 context에서 바로 실행되어 method가 끝날 때까지 기다리지만, actor model의 actor는 message를 보낸 context와 독립적인 context에서 비동기적으로 실행된다는 것이다.

Actor model을 사용하는 이유

사실 Actor model은 그리 새로운 개념이 아니다. 처음 그 개념이 나온 것은 1973년의 일로 프로그래밍 모델중에서는 나름 오래된 편에 속하는 개념이다. 이런 오래된 개념이 요새와서 다시 각광을 받는 이유는 multi processing에 적합한 개념이기 때문이다.
더 이상 moore's law가 적용되지 않기 때문에 CPU vendor들은 하나의 CPU에 여러개의 프로세스를 장착하여 연산속도를 증가시키기 시작했다. multi-core환경을 효율적으로 사용하려면 여러개의 thread를 이용하여 구현하는 것이 중요하다.
 하지만 shared resource를 가지는 멀티쓰레드 환경에서는 여러가지 문제들(race condition, deadlock, blocking call 등)이 발생하기 쉽기 때문에 이를 회피하기 위한 패턴 혹은 모델들이 여러가지 나오게 되었고, 그 과정에서 actor model이 다시 각광받기 시작하였다.

 Actor model의 장점과 단점

 Actor model의 가장 큰 장점은 이해하기 쉽다는 것이다.
 message를 받으면 그에 맞는 behavior를 실행한다는 매우 간단한 동작원리와 다른 것에 영향을 받지 않는다는 특징 때문에 실행 순서를 이해하고 결과를 예측하기 매우 쉽다.
 또한, 모든 간섭을 message를 통해서 한다는 것도 큰 장점이다.
 우선 shared resource가 존재하지 않기 때문에 shared resource들로 말미암아서 생기던 문제들(race condition, deadlock 등)이 발생하지 않는다.
 그리고 message가 serializable하기만 한다면 같은 서버에서 실행하던 Actor를 다른 서버에서 실행하여 message를 주고받는 것도 가능하므로 손쉽게 서버를 scale-out할 수 있다.

 물론 actor model이 장점만 가지는 것은 아니다.
 shared state를 가지지 않고 모든 통신을 message로 하는 것은 control flow를 제어하고 correctness를 보장하는 것에는 큰 장점이었지만 속도 면에서는 큰 단점이 된다.

Concurrency in akka

 그렇다면 scala의 구현체인 akka는 어떻게 actor를 사용할지 코드를 통해서 알아보도록 하자.
다음의 코드를 보자.
 Actor 를 상속받아 receive method만 구현하면 Actor로 사용할 수 있다.
 receive method Any타입을 받을 수 있기 때문에 보통 패턴매칭을 이용하여 구현한다.
 Actor에 메세지를 보내는 방법은 크게 3가지가 있다.

 첫번째 방법은  ActorRef의 tell method를 이용해서 메세지를 보내는 것이다.

 하지만 이런 방식은 Actor에서 응답을 보내주는 것을 처리할 방법이 없다.
 두번째 방법은 메세지의 응답을 synchronous하게 기다리는 방법이다.
 Inbox를 이용하여 message를 보내면 recevie method를 통해서 Actor가 message를 처리하고 응답을 보내기를 기다린다. timeout시간을 주기는 하지만 이렇게 하면 결국 서로 다른 2개의 Actor에서 서로를 기다리면서 deadlock이 생길 수 있다.
 그렇기 때문에 akka에서는 Actor안에서는 Inbox를 이용하여 synchronous하게 메세지 보내는 것을 추천하지 않는다.
 그래서 대부분의 통신은 3번째 방법을 사용한다.

 위의 방법은 ask 함수를 이용하여 Future객체를 만들고 callback을 등록하여 asynchronous하게 처리할 수 있게 해준다.


 이 외에도 akka의 중요한 특징으로 concurrency외에 scalability와 fault-tolerance를 들 수 있다.
 akka는 serializable 메시지들만을 이용하였다면, 물리적으로 다른 서버에 있는 actor와 메시지를 주고받을 수 있게 해준다. 이를 이용해서 scale-out을 쉽게 구현할 수 있게 해준다.
 또한, child actor를 만들어서 메시지를 주고 모니터링할 수 있는 시스템을 제공해주기 때문에 손쉽게 오류에서 복구할 수 있도록 해준다.
 쓰다 보니 의욕이 떨어지고 있는 관계로 이 두 가지에 관해서는 다음 기회에 더 자세히 글을 쓰도록 하겠다.

p.s. 위의 코드는 activator에 들어 있는 hello-akka tutorial을 기반으로 작성되었다.