사용자 도구

사이트 도구


비동기_프로그래밍과_scala

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

양쪽 이전 판 이전 판
다음 판
이전 판
비동기_프로그래밍과_scala [2017/02/27 08:53]
cumul0529 인용 문법 수정
비동기_프로그래밍과_scala [2020/08/09 07:39] (현재)
cumul0529 [함수형 프로그래밍과 타입 클래스] 마크업 수정
줄 3: 줄 3:
 ====== 비동기 프로그래밍과 Scala ====== ====== 비동기 프로그래밍과 Scala ======
  
-비동기(asynchrony)는 어떤 곳에서도 사용될 수 있는, 병행성(concurrency)을 포괄하는 개념입니다. 이 글은 비동기 처리가 무엇인지, 어떤 문제를 해결해야 하는지에 관해 다룹니다. ((역주이 문서는 [[https://monix.io/|Monix]]의 개발자 Alexandru Nedelcu님이 작성한 <[[https://alexn.org/blog/2017/01/30/asynchronous-programming-scala.html|Asynchronous Programming and Scala]]>를 번역한 문서입니다.))+비동기(asynchrony)는 어떤 곳에서도 사용될 수 있는, 병행성(concurrency)을 포괄하는 개념입니다. 이 글은 비동기 처리가 무엇인지, 어떤 문제를 해결해야 하는지에 관해 다룹니다. ((**역주** 이 문서는 [[https://monix.io/|Monix]]의 개발자 Alexandru Nedelcu님이 작성한 [[https://alexn.org/blog/2017/01/30/asynchronous-programming-scala.html|<Asynchronous Programming and Scala>]]를 번역한 문서입니다.))
  
 ===== 소개 ===== ===== 소개 =====
줄 12: 줄 12:
 Multithreading <: Asynchrony Multithreading <: Asynchrony
 </code> </code>
 +
 비동기 연산은 다음과 같이 형(type)을 통해서 표현할 수도 있습니다. 비동기 연산은 다음과 같이 형(type)을 통해서 표현할 수도 있습니다.
  
줄 17: 줄 18:
 type Async[A] = (Try[A] => Unit) => Unit type Async[A] = (Try[A] => Unit) => Unit
 </code> </code>
 +
 여러 개의 ''%%Unit%%'' 반환형(return type)이 더럽다고 느껴지는 이유는, 바로 비동기가 더럽기 때문입니다. 다음과 같은 특성을 갖는 모든 작업, 프로세스, 네트워크 상의 한 노드를 비동기 연산이라고 할 수 있습니다. 여러 개의 ''%%Unit%%'' 반환형(return type)이 더럽다고 느껴지는 이유는, 바로 비동기가 더럽기 때문입니다. 다음과 같은 특성을 갖는 모든 작업, 프로세스, 네트워크 상의 한 노드를 비동기 연산이라고 할 수 있습니다.
  
-  - 프로그램의 메인 플로우 밖에서 실행되거나, (호출자의 입장에서) 현재의 호출 스택에서 실행되지 않음+  - (호출자의 입장에서) 프로그램의 메인 플로우 밖에서 실행되거나, 현재의 호출 스택에서 실행되지 않음
   - 작업이 끝나고 나면 실행되는 콜백을 사용함   - 작업이 끝나고 나면 실행되는 콜백을 사용함
-  - 결과가 언제 나올 것이라는 보장도결과가 결국 오긴 나오는도 보장할 수 없+  - 결과가 나올 것인지혹은 언제 올 것인지 보장되지 않
  
-비동기가 병행성(concurrency)을 포괄하긴 하지만, 그것이 필연적으로 멀티스레딩을 포괄하는 것이 아님은 매우 중요합니다. 예를 들어, JavaScript에서는 대부분의 I/O 동작이 비동기적일 뿐만 아니라, 아주 무거운 비지니스 로직도 (''%%setTimeout%%''에 기반한 스케쥴링을 통해) 비동기적으로 처리될 수 있기 때문에, 인터페이스가 계속해서 응답할 수 있습니다. ((역주대부분의 JavaScript 런타임에서 비지니스 로직은 싱글스레드로 작동합니다. 비동기 코드가 반드시 멀티스레드 환경에서 작동하는 것은 아니라는 뜻이죠.))+비동기가 병행성(concurrency)을 포괄하긴 하지만, 그것이 필연적으로 멀티스레딩을 포괄하는 것이 아님은 매우 중요합니다. 예를 들어, JavaScript에서는 대부분의 I/O 동작이 비동기적일 뿐만 아니라, 아주 무거운 비지니스 로직도 (''%%setTimeout%%''에 기반한 스케쥴링을 통해) 비동기적으로 처리될 수 있기 때문에, 인터페이스가 계속해서 응답할 수 있습니다. ((**역주** 대부분의 JavaScript 런타임에서 비지니스 로직은 싱글스레드로 작동합니다. 비동기 코드가 반드시 멀티스레드 환경에서 작동하는 것은 아니라는 뜻이죠.))
  
-프로그램에 비동기를 도입하는 일은, 병행성 문제(concurrency problems)를 일으킵니다. 우리는 비동기 연산이 언제 끝날지 절대로 알 수 없어서, 동시에 작동한 여러 개의 비동기 연산의 결과를 모으는 일은 동기화(synchronization)를 필요로 하게 되기 때문입니다. 동기화 작업은 프로그램이 더 이상 동작의 순서에 의존하지 않도 하는 작업이기도 한데, 순서로부터의 독립은 바로 비결정론적 알고리즘의 핵심 요소이기도 합니다.+프로그램에 비동기를 도입하는 일은, 병행성 문제(concurrency problems)를 일으킵니다. 우리는 비동기 연산이 언제 끝날지 절대로 알 수 없어서, 동시에 작동한 여러 개의 비동기 연산의 결과를 모으는 일은 동기화(synchronization)를 필요로 하게 되기 때문입니다. 동기화 작업은 프로그램이 더 이상 동작의 순서에 의존하지 않도록 하는 작업이기도 한데, 순서로부터의 독립은 바로 비결정론적 알고리즘의 핵심 요소이기도 합니다.
  
 <blockquote> <blockquote>
줄 32: 줄 34:
 </blockquote> </blockquote>
  
-{{ 비동기_프로그래밍과_scala:비결정론적_알고리즘.png?600 |}}+{{비동기_프로그래밍과_scala:비결정론적_알고리즘.png?600|비결정론적 알고리즘}}
  
 실력 있는 독자라면, 사용되는 방식과 환경에 따라서 조금씩의 차이는 있지만 이러한 종류의 문제가 어디에서나 발견된다는 사실을 눈치 챌 수 있습니다. 실력 있는 독자라면, 사용되는 방식과 환경에 따라서 조금씩의 차이는 있지만 이러한 종류의 문제가 어디에서나 발견된다는 사실을 눈치 챌 수 있습니다.
줄 54: 줄 56:
 def await[A](fa: Async[A]): A def await[A](fa: Async[A]): A
 </code> </code>
 +
 그러나 우리는 비동기 처리가 다른 일반적인 함수들과 같다고 가정해서는 안됩니다. 그 이유는 CORBA의 실패로부터도 배울 수 있습니다. 그러나 우리는 비동기 처리가 다른 일반적인 함수들과 같다고 가정해서는 안됩니다. 그 이유는 CORBA의 실패로부터도 배울 수 있습니다.
  
줄 75: 줄 78:
   * Scheme의 [[https://en.wikipedia.org/wiki/Call-with-current-continuation|call/cc]]에서 구현된 [[https://en.wikipedia.org/wiki/Continuation|1급 객체 지속]]. 한 지점에서의 실행 상태를 저장해 두었다가 프로그램의 다른 지점에서 저장해 둔 지점으로 돌아갈 수 있습니다.   * Scheme의 [[https://en.wikipedia.org/wiki/Call-with-current-continuation|call/cc]]에서 구현된 [[https://en.wikipedia.org/wiki/Continuation|1급 객체 지속]]. 한 지점에서의 실행 상태를 저장해 두었다가 프로그램의 다른 지점에서 저장해 둔 지점으로 돌아갈 수 있습니다.
   * C#, [[https://github.com/scala/async|scala-async]], [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function|최신 ECMAScript]]의 ''%%async%%''/''%%await%%''.   * C#, [[https://github.com/scala/async|scala-async]], [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function|최신 ECMAScript]]의 ''%%async%%''/''%%await%%''.
-  * 런타임에 관리되는 [[https://en.wikipedia.org/wiki/Green_threads|그린스레드(green thread)]]. 이 방법은 [[https://en.wikipedia.org/wiki/Thread_(computing)#M:N_.28hybrid_threading.29|M:N 멀티스레딩]]와 함께 사용될 수도 있습니다. Golang과 Haskell에서 사용되는 방법입니다.+  * 런타임에 관리되는 [[https://en.wikipedia.org/wiki/Green_threads|그린스레드(green thread)]]. 이 방법은 [[https://en.wikipedia.org/wiki/Thread_(computing)#M:N_.28hybrid_threading.29|M:N 멀티스레딩]]과 함께 사용될 수도 있습니다. Golang과 Haskell에서 사용되는 방법입니다.
   * Erlang과 Akka에 구현된 [[https://en.wikipedia.org/wiki/Actor_model|행위자 모델]]이나 Clojure의 core.async와 Golang에 구현된 [[https://en.wikipedia.org/wiki/Communicating_sequential_processes|CSP]]   * Erlang과 Akka에 구현된 [[https://en.wikipedia.org/wiki/Actor_model|행위자 모델]]이나 Clojure의 core.async와 Golang에 구현된 [[https://en.wikipedia.org/wiki/Communicating_sequential_processes|CSP]]
   * 순서를 지정하고 결과를 통합하는 모나드. Haskell에서 [[https://wiki.haskell.org/IO_inside|IO]]형과 함께 사용된 [[https://hackage.haskell.org/package/async-2.1.1/docs/Control-Concurrent-Async.html|Async]]형, [[https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/asynchronous-workflows|F#의 비동기 워크플로우]], [[http://docs.scala-lang.org/overviews/core/futures.html|Scala의 Future와 Promise]], [[https://monix.io/docs/2x/eval/task.html|Monix의 Task]], [[https://github.com/scalaz/scalaz/blob/scalaz-seven/concurrent/src/main/scala/scalaz/concurrent/Task.scala|Scalaz의 Task]] 등등이 모두 이 방법에 해당합니다.   * 순서를 지정하고 결과를 통합하는 모나드. Haskell에서 [[https://wiki.haskell.org/IO_inside|IO]]형과 함께 사용된 [[https://hackage.haskell.org/package/async-2.1.1/docs/Control-Concurrent-Async.html|Async]]형, [[https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/asynchronous-workflows|F#의 비동기 워크플로우]], [[http://docs.scala-lang.org/overviews/core/futures.html|Scala의 Future와 Promise]], [[https://monix.io/docs/2x/eval/task.html|Monix의 Task]], [[https://github.com/scalaz/scalaz/blob/scalaz-seven/concurrent/src/main/scala/scalaz/concurrent/Task.scala|Scalaz의 Task]] 등등이 모두 이 방법에 해당합니다.
  
-이렇게 해결법이 많다는 것은, 그 중에 무엇도 일반적인 목적으로 비동기를 처리하기에 적합하지 않다는 것을 의미합니다. 메모리 관리와 병렬성(concurrency) 처리는 우리 소프트웨어 개발자들이 마주하고 있는 가장 큰 문제라는 점에서, [[https://en.wikipedia.org/wiki/No_Silver_Bullet|'은 총알은 없다']] 딜레마와도 관련이 있습니다. +이렇게 해결법이 많다는 것은, 그 중에 무엇도 일반적인 목적으로 비동기를 처리하기에 적합하지 않다는 것을 의미합니다. 메모리 관리와 비동기(asynchrony) 처리는 우리 소프트웨어 개발자들이 마주하고 있는 가장 큰 문제라는 점에서, [[https://en.wikipedia.org/wiki/No_Silver_Bullet|'은 총알은 없다']] 딜레마와도 관련이 있습니다. 
  
-> 원주경고 개인적인 의견과 불평입니다.+**원주** 경고개인적인 의견과 불평입니다.
  
 > 어떤 사람들은 Golang과 같은 M:N 플랫폼을 자랑합니다만, 저는 JVM이나 .Net과 같은 1:1 멀티스레딩 플랫폼을 좋아합니다. > 어떤 사람들은 Golang과 같은 M:N 플랫폼을 자랑합니다만, 저는 JVM이나 .Net과 같은 1:1 멀티스레딩 플랫폼을 좋아합니다.
줄 149: 줄 152:
 ==== 병렬 실행 (비결정론의 림보) ==== ==== 병렬 실행 (비결정론의 림보) ====
  
-위의 '연속 실행'에서의 두 번째 실행은 첫 번째 실행에 의존하지 않습니다. 즉, 두 실행은 병렬적(parallel)으로 처리될 수 있습니다. JVM에서는 CPU-bound 작업((역주CPU-bound 작업이란, 처리 속도가 주로 CPU의 속도에 따라 결정되는 작업을 의미합니다. IO-bound, Memory-bound 등의 용어도 있습니다.))들을 병렬적으로 실행할 수 있고, JavaScript에서도 Ajax 요청을 보내거나 Web Worker API를 사용할 때 병렬 처리가 가능합니다.+위의 '연속 실행'에서의 두 번째 실행은 첫 번째 실행에 의존하지 않습니다. 즉, 두 실행은 병렬적(parallel)으로 처리될 수 있습니다. JVM에서는 CPU-bound 작업((**역주** CPU-bound 작업이란, 처리 속도가 주로 CPU의 속도에 따라 결정되는 작업을 의미합니다. IO-bound, Memory-bound 등의 용어도 있습니다.))들을 병렬적으로 실행할 수 있고, JavaScript에서도 Ajax 요청을 보내거나 Web Worker API를 사용할 때 병렬 처리가 가능합니다.
  
 안타깝게도 병렬 실행에서는 일이 조금 복잡해집니다. 아래와 같은 순진한 접근 방법은 완전히 틀렸습니다. 안타깝게도 병렬 실행에서는 일이 조금 복잡해집니다. 아래와 같은 순진한 접근 방법은 완전히 틀렸습니다.
  
 <code scala> <code scala>
-// 영 좋지 못한 예시 +// 나쁜 예시
 def timesFourInParallel(n: Int): Async[Int] = def timesFourInParallel(n: Int): Async[Int] =
   onFinish => {   onFinish => {
줄 175: 줄 177:
 </code> </code>
  
-바로 이것이 비결정론의 실례(實例)입니다. 두 개의 작업이 종료되는 순서가 보장되지 않기 때문에 우리는 동기화를 수행하는 소규모의 상태 기계(state machine)를 구현해야 합니다.+바로 이것이 비결정론의 실제 예시입니다. 두 개의 작업이 종료되는 순서가 보장되지 않기 때문에 우리는 동기화를 수행하는 소규모의 상태 기계(state machine)를 구현해야 합니다.
  
 먼저 상태 기계의 ADT를 정의합니다. 먼저 상태 기계의 ADT를 정의합니다.
줄 193: 줄 195:
  
 <code scala> <code scala>
-// JVM에서는 영 좋지 못한 예시 (JavaScript에서는 돌아감) +// JVM에서는 나쁜 예시 (JavaScript에서는 돌아감)
 def timesFourInParallel(n: Int): Async[Int] = { def timesFourInParallel(n: Int): Async[Int] = {
   onFinish => {   onFinish => {
줄 301: 줄 302:
 </code> </code>
  
-> 원주[[https://monix.io/|Monix]]의 [[https://monix.io/docs/2x/execution/atomic.html|Atomic]]형을 사용하면 코드를 JavaScript / Scala.js에서 크로스컴파일 거나, 성능 향상을 위한 도구와 ''%%AtomicReference%%''와 관련된 훌륭한 유틸리티들을 사용할 수 있습니다.+**원주** [[https://monix.io/|Monix]]의 [[https://monix.io/docs/2x/execution/atomic.html|Atomic]]형을 사용하면 코드를 JavaScript / Scala.js로 크로스컴파일 거나, 성능 향상을 위한 도구와 ''%%AtomicReference%%''와 관련된 훌륭한 유틸리티들을 사용할 수 있습니다.
  
 이제 조금 어려워졌나요? 조금 더 힘내봅시다. 이제 조금 어려워졌나요? 조금 더 힘내봅시다.
줄 307: 줄 308:
 ==== 재귀 실행 (분노의 StackOverflow) ==== ==== 재귀 실행 (분노의 StackOverflow) ====
  
-위의 ''%%onFinish%%''의 호출이 스택-안전하지 않고(stack-unsafe), 호출 시점에서 비동기 범위(asynchronous boundary)를 강제하지 않으면 프로그램이 ''%%StackOverflowError%%''로 터질 수 있다고 한다면, 어떤 생각이 드시나요? ((역주'비동기 범위'라는 용어가 모호하게 느껴진다면 [[https://github.com/reactive-streams/reactive-streams-jvm/issues/46|Reactive Streams에 등록된 관련 이슈]]를 읽어보면 좋습니다.))+위의 ''%%onFinish%%''의 호출이 스택-안전하지 않고(stack-unsafe), 호출 시점에서 비동기 범위(asynchronous boundary)를 강제하지 않으면 프로그램이 ''%%StackOverflowError%%''로 터질 수 있다고 한다면, 어떤 생각이 드시나요? ((**역주** '비동기 범위'라는 용어가 모호하게 느껴진다면 [[https://github.com/reactive-streams/reactive-streams-jvm/issues/46|Reactive Streams에 등록된 관련 이슈]]를 읽어보면 좋습니다.))
  
 말 그대로의 의미입니다. 앞서 작성한 코드를 제네릭한 방법(generic way)으로 다시 작성해 봅시다. 말 그대로의 의미입니다. 앞서 작성한 코드를 제네릭한 방법(generic way)으로 다시 작성해 봅시다.
줄 409: 줄 410:
 </code> </code>
  
-앞서 말씀드린 것과 같이, 강제된 비동기 범위 없이 실행된 ''%%onFinish%%''는 스택 오버플로우를 유발할 수 있습니다. JavaScript에서는 ''%%setTimeout%%''을 사용한 스케쥴링으로 문제를 해결할 수 있으며, JVM에서는 스레드 풀을 사용하거나 Scala의 ''%%ExecutionContext%%''를 사용할 수 있습니다. ((역주JavaScript에서의 ''%%setTimeout%%''을 사용한 스케줄링에 관해서는 역자가 제6회 D2 CAMPUS SEMINAR에서 자세하게 발표했었습니다. 관련 자료는 [[http://d2.naver.com/news/9930078|D2 공식 사이트]]에 등록되어 있습니다.))+앞서 말씀드린 것과 같이, 강제된 비동기 범위 없이 실행된 ''%%onFinish%%''는 스택 오버플로우를 유발할 수 있습니다. JavaScript에서는 ''%%setTimeout%%''을 사용한 스케쥴링으로 문제를 해결할 수 있으며, JVM에서는 스레드 풀을 사용하거나 Scala의 ''%%ExecutionContext%%''를 사용할 수 있습니다. ((**역주** JavaScript에서의 ''%%setTimeout%%''을 사용한 스케줄링에 관해서는 역자가 제6회 D2 CAMPUS SEMINAR에서 자세하게 발표했었습니다. 관련 자료는 [[http://d2.naver.com/news/9930078|D2 공식 사이트]]에 등록되어 있습니다.))
  
 ===== Future와 Promise ===== ===== Future와 Promise =====
  
-''%%scala.concurrent.Future%%''는 엄격하게 계산되는 비동기 연산을 기술하는 방법입니다. 지금까지 위에서 사용한 ''%%Async%%''형과도 유사합니다.+''%%scala.concurrent.Future%%''는 earger evaluation이 적용되는 비동기 연산을 기술하는 방법입니다. 지금까지 위에서 사용한 ''%%Async%%''형과도 유사합니다.
  
 <blockquote> <blockquote>
줄 420: 줄 421:
 </blockquote> </blockquote>
  
-> 원주필자의 불평입니다.+**원주** 필자의 불평:
  
-> Future와 Promise에 관한 ''%%docs.scala-lang.org%%''의 [[http://docs.scala-lang.org/overviews/core/futures.html|문서]]의 다음 설명은 오해를 불러일으키기 쉽고 오개념의 원인이 됩니다.+> Future와 Promise에 관한 [[http://docs.scala-lang.org/overviews/core/futures.html|docs.scala-lang.org 문서]]의 다음 설명은 오해를 불러일으키기 쉽고 오개념의 원인이 됩니다.
  
 > "Future는 여러 동작을 병렬적(paraell)으로 수행하는 방법을 제공합니다. 이러한 방법은 효율적이며 비봉쇄적(non-blocking)입니다." > "Future는 여러 동작을 병렬적(paraell)으로 수행하는 방법을 제공합니다. 이러한 방법은 효율적이며 비봉쇄적(non-blocking)입니다."
줄 443: 줄 444:
   // 값 변형   // 값 변형
   def map[U](f: T => U)(implicit ec: ExecutionContext): Future[U] = ???   def map[U](f: T => U)(implicit ec: ExecutionContext): Future[U] = ???
 +
   // 연속 실행 ;-)   // 연속 실행 ;-)
   def flatMap[U](f: T => Future[U])(implicit ec: ExecutionContext): Future[U] = ???   def flatMap[U](f: T => Future[U])(implicit ec: ExecutionContext): Future[U] = ???
 +
   // ...   // ...
 } }
줄 451: 줄 454:
 ''%%Future%%''의 특성은 다음과 같습니다. ''%%Future%%''의 특성은 다음과 같습니다.
  
-  * [[https://en.wikipedia.org/wiki/Eager_evaluation|조급한 계산 (엄격한 계산)]]((역주'조급한 계산'은 'eager evaluation'의 번역이며, '엄격한 계산(strict evaluation)'이라고도 합니다. 반의어로는 '느긋한 계산(lazy evaluation)'이 있습니다.))함수의 호출자가 ''%%Future%%''의 참조를 전달 받으면, 작업 내용과 무관하게 비동기 작업은 이미 시작된 상태입니다. +  * [[https://en.wikipedia.org/wiki/Eager_evaluation|Eager evaluation.]]((**역주** Eager evaluation은 한국어로 '조급한 계산''즉시 평가등으로 번역되곤 합니다. 아쉽지만 어떤 단어도 원어의 의미를 충분히 살리면서 널리 쓰고 있지 않기 때문에 부득이하게 원어를 그대로 사용했습니다. 동의어로는 'strict evaluation'이 있으며, 반의어로는 'lazy evaluation'이 있습니다.)) 함수의 호출자가 ''%%Future%%''의 참조를 전달 받으면, 작업 내용과 무관하게 비동기 작업은 이미 시작된 상태입니다. 
-  * [[https://en.wikipedia.org/wiki/Memoization|메모아이제이션 (캐싱)]]. 조급한 계산이 이루어진다는 것은, 함수가 아닌 일반적인 변수와 같이 동작하며, 결괏값은 모든 소비자(listener)에서 사용할 수 있어야 함을 의미합니다. ''%%value%%'' 속성은 결괏값을 메모아이즈하기 위해 존재합니다. 아직 연산이 완료되지 않은 경우에는 ''%%None%%''이 할당됩니다. 당연한 이야기지만, ''%%value%%'' 속성의 ''%%def value%%''를 호출하면 비결정론적인 결과를 얻습니다.+  * [[https://en.wikipedia.org/wiki/Memoization|메모아이제이션 (캐싱)]]. Eager evaluation이 이루어진다는 것은, 함수가 아닌 일반적인 변수와 같이 동작하며, 결괏값은 모든 소비자(listener)에서 사용할 수 있어야 함을 의미합니다. ''%%value%%'' 속성은 결괏값을 메모아이즈하기 위해 존재합니다. 아직 연산이 완료되지 않은 경우에는 ''%%None%%''이 할당됩니다. 당연한 이야기지만, ''%%value%%'' 속성의 ''%%def value%%''를 호출하면 비결정론적인 결과를 얻습니다.
   * 단일한 값을 흘려보내고(stream) 나타냅니다(show). 메모아이제이션이 적용되었기 때문입니다. 따라서 작업 완료에 대한 소비자(listener)는 최대 한 번까지만 호출됩니다.   * 단일한 값을 흘려보내고(stream) 나타냅니다(show). 메모아이제이션이 적용되었기 때문입니다. 따라서 작업 완료에 대한 소비자(listener)는 최대 한 번까지만 호출됩니다.
  
줄 498: 줄 501:
 } }
 </code> </code>
-역시 아주 쉬워졌습니다. 위의 for-컴프리헨션은 아래와 같이 ''%%flatMap%%''과 ''%%map%%''으로 풀어서 쓸 수도 있습니다.+역시 아주 쉬워졌습니다. 위의 for-comprehension은 아래와 같이 ''%%flatMap%%''과 ''%%map%%''으로 풀어서 쓸 수도 있습니다.
  
 <code scala> <code scala>
줄 526: 줄 529:
  
 <code scala> <code scala>
-// 영 좋지 못한 예시+// 나쁜 예시
 def sum(list: List[Future[Int]])(implicit ec; ExecutionContext): Future[Int] = def sum(list: List[Future[Int]])(implicit ec; ExecutionContext): Future[Int] =
   async {   async {
줄 547: 줄 550:
 <code scala> <code scala>
 def timesFourInParallel(n: Int)(implicit ec: ExecutionContext): Future[Int] = { def timesFourInParallel(n: Int)(implicit ec: ExecutionContext): Future[Int] = {
-  // Future는 조급게 계산되므로, 둘 모두 값을 합치기 전에 실행됩니다+  // Future는 eager evaluation을 수행하므로, 둘 모두 값을 합치기 전에 실행됩니다
   val fa = timesTwo(n)   val fa = timesTwo(n)
   val fb = timesTwo(n)   val fb = timesTwo(n)
줄 565: 줄 568:
 </code> </code>
  
-이 방법도 초보자에게는 조금 충격적일 수도 있습니다. 여기서 ''%%Future%%''들은 그 컬렉션이 엄격(strict)하게 정의되었을 때에만 병렬적(parallel)으로 실행되기 때문입니다. 이때 '엄격하게'라는 말은 Scala의 ''%%Stream%%''이나 ''%%Iterator%%'' 등으로 정의되지 않았음을 의미합니다. 이러한 표현이 초보자에게는 불명확한 탓도 있을 듯합니다.+이 방법도 초보자에게는 조금 충격적일 수도 있습니다. 여기서 ''%%Future%%''들은 그 컬렉션이 '엄격하게정의되었을 때에만 병렬적(parallel)으로 실행되기 때문입니다. 이때 '엄격하게'라는 말은 Scala의 ''%%Stream%%''이나 ''%%Iterator%%'' 등으로 정의되지 않았음을 의미합니다. 이러한 표현이 초보자에게는 불명확한 탓도 있을 듯합니다.
  
 ==== 재귀 실행 ==== ==== 재귀 실행 ====
줄 630: 줄 633:
 CPU-bound 작업은 [[https://monix.io/docs/2x/eval/task.html|Monix Task]]가 Scala의 ''%%Future%%''에 비해 압도적인 성능을 보여주고 있습니다. CPU-bound 작업은 [[https://monix.io/docs/2x/eval/task.html|Monix Task]]가 Scala의 ''%%Future%%''에 비해 압도적인 성능을 보여주고 있습니다.
  
-> 원주이 벤치마크 결과는 한정적입니다. 여전히 ''%%Future%%''가 더 빠른 경우도 있고 (Monix의 [[https://monix.io/docs/2x/reactive/observers.html|Observer]]는 몇 가지 합당한 이유로 인해 ''%%Future%%''를 사용하고 있습니다), 애초에 CPU-bound 작업의 성능이 크게 중요하지 않은 경우도 자주 있습니다. 예를 들어서 I/O 작업을 할 때는 처리량과 CPU 속도가 별 상관이 없습니다.+**원주** 이 벤치마크 결과는 한계가 있습니다. 여전히 ''%%Future%%''가 더 빠른 경우도 있고 (Monix의 [[https://monix.io/docs/2x/reactive/observers.html|Observer]]는 몇 가지 합당한 이유로 인해 ''%%Future%%''를 사용하고 있습니다), 애초에 CPU-bound 작업의 성능이 크게 중요하지 않은 경우도 자주 있습니다. 예를 들어서 I/O 작업을 할 때는 처리량과 CPU 속도가 별 상관이 없습니다.
  
 ===== Task와 Scala의 IO 모나드 ===== ===== Task와 Scala의 IO 모나드 =====
  
-''%%Task%%'' is a data type for controlling possibly lazy & asynchronous computations, useful for controlling side-effects, avoiding nondeterminism and callback-hell.+''%%Task%%''는 lazy하고 비동기적으로 처리될 가능성이 있는 연산을 제어하기 위한 형입니다. ''%%Task%%''는 특히 사이드 이펙트를 제어하고 비결정론과 콜백 지옥을 방지하는 데 유용합니다.
  
-The [[https://monix.io/|Monix]] library provides a very sophisticated [[https://monix.io/docs/2x/eval/task.html|Task]] implementation, inspired by the [[https://github.com/scalaz/scalaz/blob/scalaz-seven/concurrent/src/main/scala/scalaz/concurrent/Task.scala|Task in Scalaz]]. Same conceptdifferent implementation.+[[https://monix.io/|Monix]]의 [[https://monix.io/docs/2x/eval/task.html|Task]] 구현은 매우 정교합니다. [[https://github.com/scalaz/scalaz/blob/scalaz-seven/concurrent/src/main/scala/scalaz/concurrent/Task.scala|Scalaz의 Task]]로부터 많은 영향을 받았기 때문에 같은 개념으로 개발되었지만그 구현은 다릅니다.
  
-The ''%%Task%%'' type is also inspired by [[https://wiki.haskell.org/IO_inside|Haskell'IO monad]], being in this author's opinion the true IO type for Scala. +또 ''%%Task%%''형은 [[https://wiki.haskell.org/IO_inside|Haskell의 IO 모나드]]로부터도 영향을 받았습니다. 필자는 Task가 Scala에서의 IO형이라고 생각합니다. 다만 이 방식이 좋은 것인지에 대해서는 논란이 있습니다.
  
-This is a matter of debate, as Scalaz also exposes a separate IO type that only deals with synchronous executionThe Scalaz IO is not async, which means that it doesn't tell the whole story, because on top of the JVM you need to represent async computations somehowIn Haskell on the other hand you have the Async type which is converted to IO, possibly managed by the runtime (green-threads and all)+> Scalaz은 동기 작업만을 위한 별도의 IO형을 갖고 있습니다즉 Scalaz의 IO형은 비동기가 아니기 때문에 JVM에서 어떻게든 별도로 비동기 연산을 처리할 필요가 생깁니다반면 Haskell에서는 Async형이 있고, 이 Async형은 IO형으로 변환되기 때문에 런타임에 관리될 수 있습니다. (이때 그린스레드 등이 사용됩니다.)
  
-On the JVM, with the Scalaz implementationwe can't represent async computations with IO and without blocking threads on evaluation, which is something to avoid, because [[https://monix.io/docs/2x/best-practices/blocking.html|blocking threads is error prone]].+> JVM를 위한 Scalaz의 구현을 따르면비동기 연산을 IO형으로 나타낼 방법이 없습니다. 또 Scalaz의 구현에서는 스레드를 봉쇄(block)하지 않고도 비동기 연산을 처리할 방법이 없습니다. 그러나 [[https://monix.io/docs/2x/best-practices/blocking.html|스레드 봉쇄(block)는 오류를 일으키기 쉬우므로]] 가급적 피하는 것이 좋습니다.
  
-In summary the ''%%Task%%'' type:+요약하자면 ''%%Task%%''형은 다음과 같은 특성이 있습니다.
  
-  * models lazy & asynchronous evaluation +  * Lazy하고 비동기적인 연산을 표현합니다. 
-  * models a producer pushing only one value to one or multiple consumers +  * 하나 혹은 여러 개의 소비자(consumer)에게 오직 하나의 값만을 전달하는 생산자(producer)를 표현합니다. 
-  * it is lazily evaluatedso compared with ''%%Future%%'' it doesn’t trigger the execution, or any effects until ''%%runAsync%%'' +  * Lazy evaluation을 수행하기 때문에, ''%%Future%%''와 달리 ''%%runAsync%%''가 호출되기 전까지 어떤 실행이나 사이드 이펙트도 발생시키지 않습니다. 
-  * it is not memoized by default on evaluation, but the Monix Task can be +  * 메모아이제이션이 가능합니다. 
-  * doesn’t necessarily execute on another logical thread+  * 반드시 다른 논리 스레드에서 실행되어야 하는 것은 아닙니다.
  
-Specific to the Monix implementation:+특히 Monix의 구현에서 ''%%Task%%''형은
  
-  * allows for cancelling of a running computation +  * 연산을 도중에 취소할 수 있습니다. 
-  * never blocks any threads in its implementation +  * 그 어떤 스레드도 절대 봉쇄(block)하지 않습니다. 
-  * does not expose any API calls that can block threads +  * 스레드를 봉쇄(block)할 가능성이 있는 그 어떤 API도 호출하지 않습니다. 
-  * all async operations are stack safe +  * 모든 비동기 작업은 스택-안전(stack-safe)합니다.
-  * A visual representation of where ''%%Task%%'' sits in the design space:+
  
-^              ^ Eager               ^ Lazy                ^ +Monix의 구현에서 ''%%Task%%''형은 다음과 같은 표로 정리할 수도 있습니다.
-| Synchronous  | A                  | () => A             | +
-| :::          | :::                 | [[https://monix.io/docs/2x/eval/coeval.html|Coeval[A]]], [[https://github.com/scalaz/scalaz/blob/scalaz-seven/effect/src/main/scala/scalaz/effect/IO.scala|IO[A]]] | +
-| Asynchronous | (A => Unit) => Unit | (A => Unit) => Unit | +
-| :::          | Future[A]           | [[https://monix.io/docs/2x/eval/task.html|Task[A]]] |+
  
-==== Sequencing ====+^             ^ Eager               ^ Lazy                ^ 
 +| 동기 작업   | A                   | () => A             | 
 +| :::         | :::                 | [[https://monix.io/docs/2x/eval/coeval.html|Coeval[A]]], [[https://github.com/scalaz/scalaz/blob/scalaz-seven/effect/src/main/scala/scalaz/effect/IO.scala|IO[A]]] | 
 +| 비동기 작업 | (A => Unit) => Unit | (A => Unit) => Unit | 
 +| :::         | Future[A]           | [[https://monix.io/docs/2x/eval/task.html|Task[A]]] |
  
-Redefining our function from [[비동기_프로그래밍과_scala&do=#콜백_지옥|section 3]] in terms of ''%%Task%%'':+==== 연속 실행 ==== 
 + 
 +[[비동기_프로그래밍과_scala#콜백_지옥|3]]에서 만든 함수를 ''%%Task%%''를 사용해서 다시 작성해 봅시다.
  
 <code scala> <code scala>
 import monix.eval.Task import monix.eval.Task
 + 
 def timesTwo(n: Int): Task[Int] = def timesTwo(n: Int): Task[Int] =
   Task(n * 2)   Task(n * 2)
- +  
-// Usage+// 사용법
 { {
-  // Our ExecutionContext needed on evaluation+  // 계산되는 시점에 ExecutionContext가 필요합니다
   import monix.execution.Scheduler.Implicits.global   import monix.execution.Scheduler.Implicits.global
- +  
-  timesTwo(20).foreach { result => println(s"Result: $result") } +  timesTwo(20).foreach { result => println(s"결과: $result") } 
-  //=> Result: 40+  //=> 결과: 40
 } }
 </code> </code>
  
-The code seems to be almost the same as the ''%%Future%%'' version in [[비동기_프로그래밍과_scala#연속_실행|section 4.1]], the only difference is that our ''%%timesTwo%%'' function no longer takes an ''%%ExecutionContext%%'' as a parameterThis is because ''%%Task%%'' references are lazy, being like functions, so nothing gets printed until the call to ''%%foreach%%'' which forces the evaluation to happenIt is there that we need a [[https://monix.io/docs/2x/execution/scheduler.html|Scheduler]], which is Monix's enhanced ''%%ExecutionContext%%''.+이 코드는 ''%%Future%%''를 사용한 [[비동기_프로그래밍과_scala#연속_실행|4.1]]의 코드와 거의 똑같아 보입니다. 유일한 차이점은 ''%%timesTwo%%''가 더이상 ''%%ExecutionContext%%''를 인수로 받지 않는다는 점입니다이것은 ''%%Task%%'' 참조가 마치 함수처럼 lazy하게 계산되기 때문입니다. 실제 계산이 일어나도록하는 ''%%foreach%%''가 호출되면 그때 비로소 결과가 출력됩니다그리고 바로 그때 [[https://monix.io/docs/2x/execution/scheduler.html|Scheduler]]가 필요해집니다. ''%%Scheduler%%''는 Monix의 향상된 ''%%ExecutionContext%%''입니다.
  
-Now to do sequencing like in [[비동기_프로그래밍과_scala#연속_실행_side-effect의_연옥|section 3.1]]:+이제 [[비동기_프로그래밍과_scala#연속_실행_side-effect의_연옥|3.1]]에서 했던 것처럼 해 봅시다.
  
 <code scala> <code scala>
 def timesFour(n: Int): Task[Int] = def timesFour(n: Int): Task[Int] =
   for (a <- timesTwo(n); b <- timesTwo(n)) yield a + b   for (a <- timesTwo(n); b <- timesTwo(n)) yield a + b
 + 
 // Usage // Usage
 { {
-  // Our ExecutionContext needed on evaluation+  // 계산되는 시점에 ExecutionContext가 필요합니다
   import monix.execution.Scheduler.Implicits.global   import monix.execution.Scheduler.Implicits.global
- +  
-  timesFour(20).foreach { result => println(s"Result: $result") } +  timesFour(20).foreach { result => println(s"결과: $result") } 
-  //=> Result: 80+  //=> 결과: 80
 } }
 </code> </code>
  
-And just like with the ''%%Future%%'' type, that "//for comprehension//" magic is translated by the Scala compiler to nothing more than calls to ''%%flatMap%%'' and ''%%map%%'', literally equivalent with:+''%%Future%%''형과 마찬가지로 for-comprehension은 Scala 컴파일러에 의해서 아래와 같은 ''%%flatMap%%''과 ''%%map%%''에 대한 호출로 변환됩니다.
  
 <code scala> <code scala>
줄 713: 줄 717:
 </code> </code>
  
-==== Parallelism ====+==== 병렬 실행 ====
  
-The story for ''%%Task%%'' and parallelism is better than with ''%%Future%%'', because ''%%Task%%'' allows fine-grained control when forking taskswhile trying to execute transformations (e.g. ''%%map%%''''%%flatMap%%'') on the current thread and call-stack, thus preserving cache locality and avoiding context switches for what is in essence sequential work.+''%%Task%%''는 ''%%Future%%''보다 더 수월하게 병렬성을 구현할 수 있습니다. ''%%Task%%''는 작업을 포크할 때 더 세밀한 제어를 가능하게 할 뿐만 아니라, ''%%flatMap%%''과 ''%%map%%'' 등의 변형을 현재 스레드의 현재 호출 스택에서 수행하기 때문입니다. 이를 통해 캐시 국소성(cache locality)을 유지하고 문맥 교환(context switch)을 피할 수 있습니다.
  
-But first, translating the sample using ''%%Future%%'' does not work:+그러나 다음과 같이 단순히 ''%%Future%%''를 ''%%Task%%''로 바꾸는 것만으로는 부족합니다.
  
 <code scala> <code scala>
-// BAD SAMPLE (for achieving parallelism, as this will be sequential)+// 나쁜 예 (이 코드는 여전히 연속 실행됩니다)
 def timesFour(n: Int): Task[Int] = { def timesFour(n: Int): Task[Int] = {
-  // Will not trigger execution b/c Task is lazy+  // Task는 lazy하게 계산되므로 여기서는 아직 계산되지 않습니다.
   val fa = timesTwo(n)   val fa = timesTwo(n)
   val fb = timesTwo(n)   val fb = timesTwo(n)
-  // Evaluation will be sequential b/c of laziness+  // Lazy evaluation으로 인해 값이 연속으로 계산됩니다.
   for (a <- fa; b <- fb) yield a + b   for (a <- fa; b <- fb) yield a + b
 } }
 </code> </code>
  
-In order to achieve parallelism %%''Task''%% requires you to be explicit about it:+''%%Task%%''를 사용한 병렬 실행은 다음과 같이 명시적으로 나타내야 합니다.
  
 <code scala> <code scala>
줄 737: 줄 741:
 </code> </code>
  
-Oh, does ''%%mapBoth%%'' seem familiarIf those two tasks fork threads on execution, then they will get executed in parallel as ''%%mapBoth%%'' starts them both at the same time.+''%%mapBoth%%''가 왠지 익숙하지 않나요이렇게 하면  ''%%mapBoth%%''가 두 작업을 동시에 시작하기 때문에 두 개의 스레드가 포크되면서 병렬적으로 값이 계산됩니다.
  
-==== Recursivity ====+==== 재귀 실행 ====
  
-Task is recursive and stack-safe (in ''%%flatMap%%'') and incredibly efficient, being powered by an internal trampolineYou can checkout this cool paper by Rúnar Bjarnason on [[http://blog.higher-order.com/assets/trampolines.pdf|Stackless Scala with Free Monads]] for getting a hint on how Task got implemented so efficiently.+''%%flatMap%%''에 대해 ''%%Task%%''는 재귀적이면서 스택-안전하도록 설계되었습니다내부적으로는 트램폴린 패턴을 사용해서 매우 효율적이기도 합니다. ''%%Task%%''는 Rúnar Bjarnason의 논문 [[http://blog.higher-order.com/assets/trampolines.pdf|<Stackless Scala with Free Monads>]]을 바탕으로 구현되었습니다.
  
-The ''%%sequence%%'' implementation looks similar with the one for ''%%Future%%'' in [[비동기_프로그래밍과_scala#재귀_실행|section 4.3]], except that you can see the laziness in the signature of ''%%sequence%%'':+''%%Task%%''를 사용한 ''%%sequence%%'' 구현은 [[비동기_프로그래밍과_scala#재귀_실행|4.3]]의 ''%%Future%%''를 사용한 구현과 유사하지만아래의 ''%%sequence%%''는 시그니처가 lazy evaluation을 나타내고 있습니다.
  
 <code scala> <code scala>
줄 751: 줄 755:
     .map(_.reverse)     .map(_.reverse)
 } }
- +  
-// Invocation+// 사용법
 { {
-  // Our ExecutionContext needed on evaluation+  // 계산되는 시점에 ExecutionContext가 필요합니다
   import monix.execution.Scheduler.Implicits.global   import monix.execution.Scheduler.Implicits.global
 + 
   sequence(List(timesTwo(10), timesTwo(20), timesTwo(30))).foreach(println)   sequence(List(timesTwo(10), timesTwo(20), timesTwo(30))).foreach(println)
   // => List(20, 40, 60)   // => List(20, 40, 60)
줄 762: 줄 766:
 </code> </code>
  
-===== Functional Programming and Type-classes =====+((**역주** 이 아래부터 6장에서는, 상당히 높은 수준의 함수형 프로그래밍 이론 지식이 요구되는 사고 실험이 진행됩니다. 특히 Haskell에 익숙하지 않다면 바로 7장으로 건너 뛰는 것도 나쁘지 않을 듯합니다.))
  
-When working with well grown functions such as ''%%map%%'', ''%%flatMap%%'' and ''%%mapBoth%%'', we no longer care that underlying it all is an ''%%"(A => Unit) => Unit"%%'', because these functions are, assuming lawfulness, pure and referentially transparent. This means we can reason about them and their result, divorced from their surrounding context.+===== 함수형 프로그래밍과 타입 클래스 =====
  
-This is the great achievement of Haskell'''%%IO%%''. Haskell does not "fake" side-effectsas functions returning ''%%IO%%'' values are literally purethe side-effects being pushed at the edges of the program where they belong. And we can say the same thing about ''%%Task%%''. Well, for ''%%Future%%'' it's more complicated given its eager naturebut working with ''%%Future%%'' is not bad either.+''%%map%%'', ''%%flatMap%%'', ''%%mapBoth%%'' 등의 함수들을 사용할 때 우리는 더이상 "''%%(A => Unit) => Unit%%''"과 같은 내부 구조를 신경쓰지 않아도 됩니다. 그러한 함수들은 규칙적(lawful)이고순수함수적이고, 참조-투명하기 때문입니다. 이는 우리가 함수와 함수의 결과에 대해 생각할 때 그 함수가 사용되는 환경을 신경쓰지 않아도 됨을 의미합니다.
  
-And can we build interfaces that abstract over such types as ''%%Task%%''''%%Future%%'', ''%%Coeval%%'', ''%%Eval%%'', ''%%IO%%''''%%Id%%''''%%Observable%%'' and others?+이것이 바로 Hakell의 ''%%IO%%''가 대단한 이유입니다. Haksell은 사이드 이펙트를 '흉내내지않습니다. ''%%IO%%'' 값을 반환하는 함수들은 순수함수적이고 사이드이펙트를 프로그램의 주변부로 밀어냅니다. 이것은 ''%%Task%%''에 대해서도 마찬가지입니다. ''%%Future%%''도 eager evaluation으로 인해 조금 복잡해지긴 하지만 어쨌든 나쁘지 않은 선택입니다.
  
-Yes we can, we've already seen that ''%%flatMap%%'' describes sequencingwhile ''%%mapBoth%%'' describes parallelism. But we can't describe them with classic OOP interfacesfor one because due to the covariance and contravariance rules of ''%%Function1%%'' parameters we'd lose type info in ''%%flatMap%%'' (unless you use F-bounded polymorphic typeswhich are more suitable for implementation reuse and aren't available in other OOP languages)but also because we need to describe a data constructor that can't be a method (i.e. OOP subtyping applies to instances and not whole classes).+그렇다면 ''%%Task%%'', ''%%Future%%'', ''%%Coeval%%''''%%Eval%%'', ''%%IO%%'', ''%%Id%%'', ''%%Observable%%'' 등의 형을 추상화하는 인터페이스를 만들 수는 없을까요?
  
-Fortunately Scala is one of the very few languages capable of higher kinded types and with the ability to encode [[https://en.wikipedia.org/wiki/Type_class|type-classes]]which means we've got everything needed to port concepts from Haskell 😄+가능합니다우리는 이미 순차 실행을 추상화하는 ''%%flatMap%%''을 보았고, 병렬 실행을 추상화하는 ''%%mapBoth%%''를 보았습니다하지만 우리는 그러한 기능들을 고전적인 OOP 인터페이스로는 구현할 수 없습니다. 한 가지 이유는, ''%%Function1%%'' 인자에 대한 공변성과 반변성의 법칙(covariance and contravariance rules)에 따라 ''%%flatMap%%''에서 형에 대한 정보가 소실되기 때문입니다. (F-바운드 다형성 타입을 사용하면 소실을 막을 수 있지만, 이 기능은 구현 재사용에 더 적합할 뿐만 아니라 다른 OOP 언어는 지원하지도 않습니다.) 그리고 그보다 더 근본적으로, OOP에서는 데이터 생성자를 메서드로 표현할 수 없기 때문입니다. (즉, OOP의 서브타입은 클래스 전체가 아니라 각각의 인스턴스에 적용되기 때문입니다.)
  
-> Author's RantThe dreaded ''%%Monad%%'', ''%%Applicative%%'' and ''%%Functor%%'' words strike fear in the hearts of the unfaithful, having given rise to the belief that they are "academicnotions disconnected from real-world concerns, with book authors going to great length to avoid using these words, which includes Scala'API documentation and official tutorials+마침 Scala는 상류 타입(higher kinded types)을 지원하는 몇 안되는 언어에 속하고 [[https://en.wikipedia.org/wiki/Type_class|타입 클래스]]를 지원하기 때문에 Haskell의 기능을 그대로 옮겨오는 것이 가능합니다. 😄 ((**역주** 'higher kinded types'를 어떻게 번역해야 좋을지 난처하던 차에, '상류 타입'이라는 번역어를 [[https://twitter.github.io/scala_school/ko/advanced-types.html|한국어판 Scala School]]에서 빌려왔습니다. 해당 문서에도 '상류 타입'이라는 번역어에 대한 고민이 자세하게 역주로 붙어 있습니다. 이외에도 다른 OOP 언어에서 잘 쓰이지 않는 Scala 특유의 개념들은 대부분 한국어판 Scala School의 번역을 참고했습니다.)) 
 + 
 +> **원주** 저자의 분노: ''%%Monad%%'', ''%%Applicative%%''''%%Functor%%'' 등의 단어를 공포스럽게 생각하거나 현실과 동떨어진 "학문적인개념으로 치부하는 사람이 많은 탓에 많은 저자들이 저 단어들을 사용하지 않기 위해 노력합니다. 이것은 Scala의 API 문서와 공식 튜토리얼도 마찬가지입니다.
  
-But this is a disservice to both the Scala language and its usersIn other languages they are only design patterns that are hard to explain primarily because they can't be expressed as typesYou can count the languages having this expressive capability with one handAnd users suffer because in case of trouble they don't know how to search for existing literature on the subject, having been deprived of learning the correct jargon+하지만 그러한 설명 방식은 Scala와 사용자 모두에게 민폐입니다다른 언어에서 저 개념들은 단순히 설명하기 어려운 디자인 패턴에 불과합니다대부분의 다른 언어들은 형에 대한 표현성이 부족하기 때문입니다저 개념들을 표현할 수 있는 언어는 손에 꼽습니다. 언어 사용자의 입장에서도 문제가 생겼을 때 저 개념들을 모른 채 관련된 자료를 검색하는 것은 매우 고통스러운 일입니다.
  
-I also feel this is a flavor of [[https://en.wikipedia.org/wiki/Anti-intellectualism|anti-intellectualism]], as usual born out of fear of the unknownYou can see it coming from people that really know what they are doing, as none of us is immune. For example Java'[[https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html|Optional]] type violates the functor laws (e.g. ''%%opt.map(f).map(g) != opt.map(f andThen g))%%'', in ''%%Swift 5 == Some(5)%%'' which is preposterous and good luck explaining to people that ''%%Some(null)%%'' actually makes sense for as long as null is a valid value of AnyRef and because otherwise you can't define ''%%Applicative[Option]%%''.+또 저는 이것이 모르는 것에 대한 본능적인 공포에서 나오는 일종의 [[https://en.wikipedia.org/wiki/반지성주의|반지성주의]]라고 생각합니다예를 들어서 Java의 [[https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html|Optional]]형은 함자 법칙(functor law)에 위배됩니다(즉, ''%%opt.map(f).map(g) != opt.map(f andThen g)%%''입니다.) Swift에서는 어이없게도 ''%%5 == Some(5)%%''입니다. ''%%Some(null)%%''도 이상하지 않습니다. ''%%null%%''이 ''%%AnyRef%%''의 유효한 값이기 때문입니다. 그렇지 않았더라면 ''%%Applicative[Option]%%''를 정의하는 것이 불가능했을 것입니다. 이 예시들을 어떻게 반지성주의자들에게 설명할 수 있겠습니까.
  
-==== Monad (Sequencing and Recursivity) ====+==== Monad (연속 실행과 재귀 실행) ====
  
-This article is not about explaining MonadsThere are other great articles for thatBut if you're looking to build an intuition, here's another one: in the context of data types such as ''%%Future%%'' or ''%%Task%%'', Monads describe sequencing of operations and is the only reliable way to ensure ordering.+이 글의 목적은 모나드에 대해 설명하는 것이 아닙니다모나드에 관해서는 다른 좋은 글들이 있습니다. 모나드는 잘 모르지만 비동기 프로그래밍에 대한 감을 쌓기 위해서 이 글을 읽고 있다면 적어도 알아두어야 할 것이 있습니다. ''%%Future%%''나 ''%%Task%%'' 등의 자료형을 사용함에 있어서 모나드는 실행의 순서를 올바르게 표현하는 유일한 방법이라는 사실입니다.
  
-> [[https://twitter.com/shipilev/status/822004316605206529|Aleksey Shipilëv]]: "Observation: programmers doing concurrency with imperative languages are tripped by the unchallenged belief that ";" defines sequencing."+<blockquote> 
 +관찰 결과: 명령형 언어(imperative language)로 병행성(concurrency)을 다루는 프로그래머들은 ";"이 순서를 정의한다는 맹목적인 믿음으로 인해 함정에 빠지고 만다. 
 +<cite>[[https://twitter.com/shipilev/status/822004316605206529|Aleksey Shipilëv]]</cite> 
 +</blockquote>
  
-A simple encoding of the ''%%Monad%%'' type in Scala:+''%%Monad%%''형을 Scala로 표현해 봅시다.
  
 <code scala> <code scala>
-// We shouldn't need to do this :-(+// 이 줄을 적지 않아도 된다면 좋을텐데 말이죠 :-(
 import scala.language.higherKinds import scala.language.higherKinds
 + 
 trait Monad[F[_]] { trait Monad[F[_]] {
-  /** Constructor (said to lift a value `A` in the `F[A]` +  /** 생성자 (`A`를 `F[A]` Monad로 리프팅합니다.) 
-    * monadic context). Also part of `Applicative`, see below.+    * `Applicative`의 일부이기도 합니다. 아래를 보세요.
     */     */
   def pure[A](a: A): F[A]   def pure[A](a: A): F[A]
- +  
-  /** FTW */+  /** 빰 */
   def flatMap[A,B](fa: F[A])(f: A => F[B]): F[B]   def flatMap[A,B](fa: F[A])(f: A => F[B]): F[B]
 } }
 </code> </code>
  
-And providing an implementation for ''%%Future%%'':+이어서 ''%%Future%%''를 구현합시다.
  
 <code scala> <code scala>
줄 825: 줄 834:
 </code> </code>
  
-This is really powerful stuffWe can now describe a generic function that works with ''%%Task%%'', ''%%Future%%'', ''%%IO%%'', whatever, although it would be great if the ''%%flatMap%%'' operation is stack-safe:+''%%FutureMonad%%''형은 무척 강력한 도구입니다. ''%%Task%%'', ''%%Future%%'', ''%%IO%%'' 뿐만 아니라 무엇이든지 ''%%FutureMonad%%''와 함께 사용할 수 있습니다. 다음과 같이 ''%%flatMap%%''을 스택-안전하는 것도 가능합니다.
  
 <code scala> <code scala>
-/** Calculates the N-th number in a Fibonacci series. */+/** 피보나치 수열의 N번째 숫자를 구합니다. */
 def fib[F[_]](n: Int)(implicit F: Monad[F]): F[BigInt] = { def fib[F[_]](n: Int)(implicit F: Monad[F]): F[BigInt] = {
   def loop(n: Int, a: BigInt, b: BigInt): F[BigInt] =   def loop(n: Int, a: BigInt, b: BigInt): F[BigInt] =
줄 835: 줄 844:
       else loop(n - 1, b, a + b)       else loop(n - 1, b, a + b)
     }     }
 + 
   loop(n, BigInt(0), BigInt(1))   loop(n, BigInt(0), BigInt(1))
 } }
- +  
-// Usage:+// 사용법
 { {
-  // Needed in scope+  // 유효범위(scope)를 만듭니다.
   import FutureMonad.instance   import FutureMonad.instance
   import scala.concurrent.ExecutionContext.Implicits.global   import scala.concurrent.ExecutionContext.Implicits.global
- +  
-  // Invocation +  // 실행 
-  fib[Future](40).foreach(r => println(s"Result: $r")) +  fib[Future](40).foreach(r => println(s"결과: $r")) 
-  //=> Result: 102334155+  //=> 결과: 102334155
 } }
 </code> </code>
  
-PRO-TIP: this is just a toy exampleFor getting serious, see [[http://typelevel.org/cats/|Typelevel's Cats]]+**원주** 이 코드는 그저 장난 수준입니다본격적인 프로젝트를 보고 싶다면 Typelevel의 [[http://typelevel.org/cats/|Typelevel's Cats]]를 참고하세요.
  
-==== Applicative (Parallelism) ====+==== Applicative (병렬 실행) ====
  
-Monads define sequencing of operations, but sometimes we want to compose the results of computations that are independent of each other, that can be evaluated at the same time, possibly in parallelThere's also a case to be made that applicatives are more composable than monads 😏+모나드는 실행의 순서를 정의합니다하지만 동시에 실행 가능한 서로 독립적인 연산들의 결과를 합쳐야 할 때도 있습니다. 그런 경우에는 ''%%Monad%%''보다 ''%%Applicative%%''가 적절할 수 있습니다.
  
-Let's expand our mini Typeclassopedia to put on your wall:+먼저 간단한 타입백과사전(Typeclassopedia)을 만들어 봅시다. ((**역주** '타입백과사전'은 'Typeclassopedia'의 번역입니다. 첫 글자가 대문자인 것으로 보아 Haskell의 타입백과사전을 의식한 것으로 보입니다. 다양한 형들의 구현과 관계를 나타낸 코드나 문서를 타입백과사전이라고 부릅니다.))
  
 <code scala> <code scala>
 trait Functor[F[_]] { trait Functor[F[_]] {
-  /** I hope we are all familiar with this one. */+  /** 이 코드는 독자 여러분 모두 이해하리라고 믿습니다. */
   def map[A,B](fa: F[A])(f: A => B): F[B]   def map[A,B](fa: F[A])(f: A => B): F[B]
 } }
 + 
 trait Applicative[F[_]] extends Functor[F] { trait Applicative[F[_]] extends Functor[F] {
-  /** Constructor (lifts a value `A` in the `F[A]` applicative context). */+  /** 생성자 (`A`를 `F[A]` Applicative로 리프팅합니다.*/
   def pure[A](a: A): F[A]   def pure[A](a: A): F[A]
- +  
-  /** Maps over two references at the same time.+  /** 두 참조에 대해서 동시에 map을 실행합니다.
     *     *
-    * In other implementations the applicative operation is `ap`, +    * 다른 구현체에서는 Applicative 연산자가 `ap`일 수 있습니다. 
-    * but `map2` is easier to understand.+    * 하지만 `map2` 구현이 훨씬 이해하기 쉽습니다.
     */     */
   def map2[A,B,R](fa: F[A], fb: F[B])(f: (A,B) => R): F[R]   def map2[A,B,R](fa: F[A], fb: F[B])(f: (A,B) => R): F[R]
 } }
 + 
 trait Monad[F[_]] extends Applicative[F] { trait Monad[F[_]] extends Applicative[F] {
   def flatMap[A,B](fa: F[A])(f: A => F[B]): F[B]   def flatMap[A,B](fa: F[A])(f: A => F[B]): F[B]
줄 882: 줄 891:
 </code> </code>
  
-And to expand our ''%%Future%%'' implementation:+이어서 ''%%Future%%''를 구현합니다.
  
 <code scala> <code scala>
-// Supplying an instance for Future isn't clean, ExecutionContext needed 
 class FutureMonad(implicit ec: ExecutionContext) class FutureMonad(implicit ec: ExecutionContext)
   extends Monad[Future] {   extends Monad[Future] {
 + 
   def pure[A](a: A): Future[A] =   def pure[A](a: A): Future[A] =
     Future.successful(a)     Future.successful(a)
 + 
   def flatMap[A,B](fa: Future[A])(f: A => Future[B]): Future[B] =   def flatMap[A,B](fa: Future[A])(f: A => Future[B]): Future[B] =
     fa.flatMap(f)     fa.flatMap(f)
 + 
   def map2[A,B,R](fa: Future[A], fb: Future[B])(f: (A,B) => R): Future[R] =   def map2[A,B,R](fa: Future[A], fb: Future[B])(f: (A,B) => R): Future[R] =
-    // For Future there's no point in supplying an implementation that's +    // flatMap에 기반하지 않은 구현체를 위한 함수이지만, 
-    // not based on flatMap, but that's not the case for Task ;-)+    // Task는 해당되지 않습니다 ;-)
     for (a <- fa; b <- fb) yield f(a,b)     for (a <- fa; b <- fb) yield f(a,b)
 } }
 + 
 object FutureMonad { object FutureMonad {
   implicit def instance(implicit ec: ExecutionContext): FutureMonad =   implicit def instance(implicit ec: ExecutionContext): FutureMonad =
줄 907: 줄 915:
 </code> </code>
  
-So we can now define generic functions based on ''%%Applicative%%'' which is going to work for ''%%Future%%''''%%Task%%'', etc:+이제 ''%%Future%%''''%%Task%%'' 등과 사용할 수 있는 제네릭 함수를 ''%%Application%%''에 기반해서 정의할 수 있습니다.
  
 <code scala> <code scala>
줄 919: 줄 927:
 </code> </code>
  
-PRO-TIP: worth repeatingthis is just a toy exampleFor getting serious, see [[http://typelevel.org/cats/|Typelevel'Cats]]+**원주** 다시 한 번 강조하지만이 코드는 단지 장난 수준일 뿐입니다본격적인 프로젝트를 보고 싶다면 [[http://typelevel.org/cats/|Typelevel의 Cats]]를 참고하세요.
  
-==== Can We Define a Type-class for Async Evaluation? ====+==== 비동기 계산을 위한 타입 클래스를 정의할 수 있을까요? ====
  
-Missing from above is a way to actually trigger an evaluation and get a value outThinking of Scala'''%%Future%%'', we want a way to abstract over ''%%onComplete%%''Thinking of Monix'Task we want to abstract over ''%%runAsync%%''Thinking of Haskell's and Scalaz'IO, we want a way to abstract over ''%%unsafePerformIO%%''.+지금까지 살펴 본 내용에서 빠진 부분은 실제로 계산을 시작하고 결괏값을 받아오는 방법입니다. Scala의 ''%%Future%%''를 생각해 보면우리는 ''%%onComplete%%''를 추상화해야 하는 것입니다. Monix의 ''%%Task%%''로 치자면 ''%%runAsync%%''를 추상화하는 것이기도 합니다. Haskell이나 Scalaz의 ''%%IO%%''에서는 ''%%unsafePerformIO%%''입니다.
  
-The [[https://github.com/functional-streams-for-scala/fs2/|FS2]] library has defined a type-class called [[https://github.com/functional-streams-for-scala/fs2/blob/series/1.0/core/shared/src/main/scala/fs2/util/Effect.scala|Effect]] that goes like this (simplified):+[[https://github.com/functional-streams-for-scala/fs2/|FS2]]에서는 Effect라는 타입 클래스를 정의했습니다다음은 그 구현을 간략화한 코드입니다.
  
 <code scala> <code scala>
줄 933: 줄 941:
 </code> </code>
  
-This looks like our initial ''%%Async%%'' type, very much similar with ''%%Future.onComplete%%'', with ''%%Task.runAsync%%'' and could be applied to ''%%IO.unsafePerformIO%%''.+우리가 처음에 만들었던 ''%%Async%%''형과 비슷한 것 같기도 합니다. ''%%Future.onComplete%%'', ''%%Task.runAsync%%''와는 더욱 비슷합니다. ''%%IO.unsafePerformIO%%''에 적용할 수 있을 것 같기도 합니다. 
 + 
 +하지만 이것은 다음의 이유로 진짜 타입 클래스라고 하기는 어렵습니다.
  
-However, this is not a real type-class because:+  * 이 구현은 불규칙적(lawless)입니다. 그러나 이것만으로 타입 클래스가 아니라고 하기는 곤란합니다. (''%%Show%%''와 같이 유용하지만 불규칙적인 타입 클래스도 분명 존재합니다.) 그보다는 더 중요한 이유가 있습니다. 
 +  * [[비동기_프로그래밍과_scala#재귀_실행_분노의_stackoverflow|3.3절]]에서 본 것과 같은 '분노의 StackOverflowError'를 피하기 위해서는 실행 문맥(execution context)이나 스레드풀을 지정해야 하기 때문입니다.
  
-  - it is lawless and while that's not enough to disqualify it (after alluseful lawless type-classes like ''%%Show%%'' exist), the bigger problem is … +실행 문맥(execution context)을 지정하는 방법은 구현 방식에 따라 다릅니다. Java는 ''%%Executor%%''를 사용합니다. Scala의 ''%%Future%%''는 ''%%ExecutionContext%%''를 쓰고Monix는 ''%%ExecutionContext%%''를 향상시킨 ''%%Scheduler%%''를 사용합니다. FS2와 Scalaz는 스레드를 포크(fork)할 때 ''%%Executor%%''의 래퍼(wrapper)인 ''%%Strategy%%''를 사용하지만 ''%%unsafePerformIO%%''나 ''%%runAsync%%''가 호출될 때 실행 문맥(execution context)을 변경하지는 않습니다. (그래서 사실 Scalaz의 많은 콤비네이터들은 안전하지 않습니다.)
-  - as shown in [[비동기_프로그래밍과_scala#재귀_실행_분노_stackoverflow|section 3.3]], in order to avoid the Wrath of ''%%StackOverflowError%%'', we need some sort of execution context or thread-pool that can execute tasks asynchronously without blowing up the stack+
  
-And such an execution context is different from implementation to implementation. Java will use ''%%Executor%%'', the Scala Future uses ''%%ExecutionContext%%'', Monix uses ''%%Scheduler%%'' which is an enhanced ''%%ExecutionContext%%'', FS2 and Scalaz use ''%%Strategy%%'' which wraps an Executor for forking threads and don't inject a context when their ''%%unsafePerformIO%%'' or ''%%runAsync%%'' gets called (which is why many of the Scalaz combinators are in fact unsafe), etc.+우리는 이와 같은 전략을 ''%%Future%%''에 대해서도 취할 수 있습니다. 유효범위(scope)로부터 ''%%implicit whatever: Context%%''를 받아서 타입 클래스의 인스턴스를 생성하는 방식입니다. 하지만 이러한 방식은 약간 이상하고 비효율적입니다. 게다가 이 방식을 따르면 ''%%Effect.unsafePerformIO%%''만으로 ''%%flatMap%%''을 정의할 수 없고그렇다면 그 인스턴스가 반드시 ''%%Monad%%''를 상속한다고 보기울 것입니다.
  
-We could apply the same strategy as with ''%%Future%%'', to build the type-class instance by taking a ''%%implicit whateverContext%%'' from the scope. But that's a little awkward and inefficient. It's also telling that we can't define ''%%flatMap%%'' only in terms of ''%%Effect.unsafePerformIO%%'', not without that execution contextAnd if we can't do it, then the type should probably not inherit from ''%%Monad%%'' because it's not necessarily a ''%%Monad%%''.+그래서 처음 주어진 질문("비동기 계산을 위한 타입 클래스를 정의할 수 있을까요?")에 대해 저는 확신이 없습니다. [[http://typelevel.org/cats|Cats]]에 도입할 수 있는 아이디어가 있다면 저도 꼭 듣고 싶습니다.
  
-So I'm personally not sure - if you have suggestions for what should be introduced in [[http://typelevel.org/cats|Cats]], I'd love to hear them.+여러분이 이 사고 실험을 재밌게 즐기셨다면 좋겠습니다설계는 늘 즐겁죠😎
  
-I do hope you enjoyed this thought experiment, designing things is fun 😎+===== 올바른 도구를 고르자 =====
  
-===== Picking the Right Tool =====+어떤 추상화는 다른 추상화보다 더 일반적인 목적을 가지고 있습니다. 저는 개인적으로 "특정한 작업에는 특정한 최적의 도구가 있다"는 사고방식이 잘못된 선택을 정당화하기 위해 남용되고 있다고 생각합니다.
  
-Some abstractions are more general purpose than others and personally I think the mantra of "picking the right tool for the job" is overused to defend poor choices.+이 시점에서 우리는 Rúnar Bjarnason의 [[https://www.youtube.com/watch?v=GqmsQeSzMdw|<제약은 자유이며, 자유는 제약이다>]]를 보아야 합니다. 병행성(concurrency)을 추상화하는 작업의 핵심을 정확하게 짚는 멋진 발표입니다.
  
-That saidthere's this wonderful presentation by Rúnar Bjarnason called [[https://www.youtube.com/watch?v=GqmsQeSzMdw|Constraints LiberateLiberties Constrain]] that really drives the point home with concurrency abstractions at least.+이미 말했듯이병행성(concurrency)을 처리함에 있어 모든 상황에 일반적으로 적용될 수 있는 '은 총알'은 없습니다더 고수준의 추상화일수록, 문제를 해결하는 데 필요한 유효범위(scope)는 좁아집니다그리고 모델의 유효범위가 좁아지고 기능이 줄어들수록그 모델은 간단해지고 결과를 합치기도 쉬워집니다. 예를 들어서 Scala 커뮤니티의 개발자들은 Akka의 행위자 모델(actor model)을 과도하게 사용하는 경향이 있습니다. (물론 Akka는 잘못 쓰지만 않는다면 훌륭한 라이브러리입니다.) 그러나 ''%%Future%%''나 ''%%Task%%''로도 같은 기능을 구현할 수 있다면 Akka의 ''%%Actor%%''는 굳이 사용하지 않는 편이 낫습니다. Monix와 ReactiveX의 ''%%Observable%%'' 등과 같은 다른 추상화도 마찬가지입니다.
  
-As said, there is no silver bullet that can be generally applied for dealing with concurrency. The more high-level the abstraction, the less scope it has in solving issues. But the less scope and power it has, the simpler and more composable the model is. For example many developers in the Scala community are overusing Akka Actors - which is a great library, but not when misapplied. Like don't use an Akka ''%%Actor%%'' when a ''%%Future%%'' or a ''%%Task%%'' would do. Ditto for other abstractions, like the ''%%Observable%%'' pattern in Monix and ReactiveX.+그리고 이 두 가지 규칙을 마음에 새기도록 합시다.
  
-Also learn by heart these 2 very simple rules:+  * 콜백, 스레드와 스레드 봉쇄(block)를 지양하자. 오류를 일으키기 쉽고 결과를 합치는 것도 불가능하다. 
 +  * 병행성(concurrency)을 지양하자. 그것은 전염병과 같다.
  
-avoid dealing with callbacks, threads and locks, because they are very error prone and not composable at all +마지막으로 이것만 말하겠습니다. 병행성(concurrency) 전문가들은 애초에 병행성이 생기지 않도록 하는 데 그 누구보다 전문적입니다. 💀
-avoid concurrency like the plague it is +
-And let me tell you, concurrency experts are first of all experts in avoiding concurrency 💀+
비동기_프로그래밍과_scala.1488185597.txt.gz · 마지막으로 수정됨: 2017/02/26 23:53 (바깥 편집)