사용자 도구

사이트 도구


비동기_프로그래밍과_scala

차이

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

차이 보기로 링크

양쪽 이전 판 이전 판
다음 판
이전 판
비동기_프로그래밍과_scala [2020/08/09 07:02]
cumul0529 잘못 옮긴 용어 수정
비동기_프로그래밍과_scala [2020/08/09 07:39] (현재)
cumul0529 [함수형 프로그래밍과 타입 클래스] 마크업 수정
줄 157: 줄 157:
  
 <code scala> <code scala>
-// 영 좋지 못한 예시 +// 나쁜 예시
 def timesFourInParallel(n: Int): Async[Int] = def timesFourInParallel(n: Int): Async[Int] =
   onFinish => {   onFinish => {
줄 178: 줄 177:
 </code> </code>
  
-바로 이것이 비결정론의 실례(實例)입니다. 두 개의 작업이 종료되는 순서가 보장되지 않기 때문에 우리는 동기화를 수행하는 소규모의 상태 기계(state machine)를 구현해야 합니다.+바로 이것이 비결정론의 실제 예시입니다. 두 개의 작업이 종료되는 순서가 보장되지 않기 때문에 우리는 동기화를 수행하는 소규모의 상태 기계(state machine)를 구현해야 합니다.
  
 먼저 상태 기계의 ADT를 정의합니다. 먼저 상태 기계의 ADT를 정의합니다.
줄 196: 줄 195:
  
 <code scala> <code scala>
-// JVM에서는 영 좋지 못한 예시 (JavaScript에서는 돌아감) +// JVM에서는 나쁜 예시 (JavaScript에서는 돌아감)
 def timesFourInParallel(n: Int): Async[Int] = { def timesFourInParallel(n: Int): Async[Int] = {
   onFinish => {   onFinish => {
줄 416: 줄 414:
 ===== Future와 Promise ===== ===== Future와 Promise =====
  
-''%%scala.concurrent.Future%%''는 엄격하게 계산되는 비동기 연산을 기술하는 방법입니다. 지금까지 위에서 사용한 ''%%Async%%''형과도 유사합니다.+''%%scala.concurrent.Future%%''는 earger evaluation이 적용되는 비동기 연산을 기술하는 방법입니다. 지금까지 위에서 사용한 ''%%Async%%''형과도 유사합니다.
  
 <blockquote> <blockquote>
줄 446: 줄 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] = ???
 +
   // ...   // ...
 } }
줄 454: 줄 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)는 최대 한 번까지만 호출됩니다.
  
줄 529: 줄 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 {
줄 550: 줄 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)
줄 568: 줄 568:
 </code> </code>
  
-이 방법도 초보자에게는 조금 충격적일 수도 있습니다. 여기서 ''%%Future%%''들은 그 컬렉션이 엄격(strict)하게 정의되었을 때에만 병렬적(parallel)으로 실행되기 때문입니다. 이때 '엄격하게'라는 말은 Scala의 ''%%Stream%%''이나 ''%%Iterator%%'' 등으로 정의되지 않았음을 의미합니다. 이러한 표현이 초보자에게는 불명확한 탓도 있을 듯합니다.+이 방법도 초보자에게는 조금 충격적일 수도 있습니다. 여기서 ''%%Future%%''들은 그 컬렉션이 '엄격하게정의되었을 때에만 병렬적(parallel)으로 실행되기 때문입니다. 이때 '엄격하게'라는 말은 Scala의 ''%%Stream%%''이나 ''%%Iterator%%'' 등으로 정의되지 않았음을 의미합니다. 이러한 표현이 초보자에게는 불명확한 탓도 있을 듯합니다.
  
 ==== 재귀 실행 ==== ==== 재귀 실행 ====
줄 633: 줄 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%%''는 느긋하고 비동기적으로 처리될 가능성이 있는 연산을 제어하기 위한 형입니다. ''%%Task%%''는 특히 사이드 이펙트를 제어하고 비결정론과 콜백 지옥을 방지하는 데 유용합니다.+''%%Task%%''는 lazy하고 비동기적으로 처리될 가능성이 있는 연산을 제어하기 위한 형입니다. ''%%Task%%''는 특히 사이드 이펙트를 제어하고 비결정론과 콜백 지옥을 방지하는 데 유용합니다.
  
 [[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]]로부터 많은 영향을 받았기 때문에 같은 개념으로 개발되었지만, 그 구현은 다릅니다. [[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]]로부터 많은 영향을 받았기 때문에 같은 개념으로 개발되었지만, 그 구현은 다릅니다.
줄 649: 줄 649:
 요약하자면 ''%%Task%%''형은 다음과 같은 특성이 있습니다. 요약하자면 ''%%Task%%''형은 다음과 같은 특성이 있습니다.
  
-  * 느긋하고 비동기적인 연산을 표현합니다.+  * Lazy하고 비동기적인 연산을 표현합니다.
   * 하나 혹은 여러 개의 소비자(consumer)에게 오직 하나의 값만을 전달하는 생산자(producer)를 표현합니다.   * 하나 혹은 여러 개의 소비자(consumer)에게 오직 하나의 값만을 전달하는 생산자(producer)를 표현합니다.
-  * 느긋게 계산되기 때문에, ''%%Future%%''와 달리 ''%%runAsync%%''가 호출되기 전까지 어떤 실행이나 사이드 이펙트도 발생시키지 않습니다.+  * Lazy evaluation을 수행하기 때문에, ''%%Future%%''와 달리 ''%%runAsync%%''가 호출되기 전까지 어떤 실행이나 사이드 이펙트도 발생시키지 않습니다.
   * 메모아이제이션이 가능합니다.   * 메모아이제이션이 가능합니다.
   * 반드시 다른 논리 스레드에서 실행되어야 하는 것은 아닙니다.   * 반드시 다른 논리 스레드에서 실행되어야 하는 것은 아닙니다.
줄 664: 줄 664:
 Monix의 구현에서 ''%%Task%%''형은 다음과 같은 표로 정리할 수도 있습니다. Monix의 구현에서 ''%%Task%%''형은 다음과 같은 표로 정리할 수도 있습니다.
  
-             조급한 계산        ^ Lazy                ^ +            Eager               ^ Lazy                ^ 
-| 동기 작업   | A             | () => A             |+| 동기 작업   | 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]]] | | :::         | :::                 | [[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 | | 비동기 작업 | (A => Unit) => Unit | (A => Unit) => Unit |
줄 672: 줄 672:
 ==== 연속 실행 ==== ==== 연속 실행 ====
  
-[[비동기_프로그래밍과_scala&do=#콜백_지옥|3장]]에서 만든 함수를 ''%%Task%%''를 사용해서 다시 작성해 봅시다.+[[비동기_프로그래밍과_scala#콜백_지옥|3장]]에서 만든 함수를 ''%%Task%%''를 사용해서 다시 작성해 봅시다.
  
 <code scala> <code scala>
줄 690: 줄 690:
 </code> </code>
  
-이 코드는 ''%%Future%%''를 사용한 [[비동기_프로그래밍과_scala#연속_실행|4.1절]]의 코드와 거의 똑같아 보입니다. 유일한 차이점은 ''%%timesTwo%%''가 더이상 ''%%ExecutionContext%%''를 인수로 받지 않는다는 점입니다. 이것은 ''%%Task%%'' 참조가 마치 함수처럼&nbsp;느긋하게 계산되기 때문입니다.&nbsp;실제 계산이 일어나도록하는 ''%%foreach%%''가 호출되면 그때 비로소 결과가 출력됩니다. 그리고 바로 그때 [[https://monix.io/docs/2x/execution/scheduler.html|Scheduler]]가 필요해집니다. ''%%Scheduler%%''는 Monix의 향상된 ''%%ExecutionContext%%''입니다.+이 코드는 ''%%Future%%''를 사용한 [[비동기_프로그래밍과_scala#연속_실행|4.1절]]의 코드와 거의 똑같아 보입니다. 유일한 차이점은 ''%%timesTwo%%''가 더이상 ''%%ExecutionContext%%''를 인수로 받지 않는다는 점입니다. 이것은 ''%%Task%%'' 참조가 마치 함수처럼 lazy하게 계산되기 때문입니다. 실제 계산이 일어나도록하는 ''%%foreach%%''가 호출되면 그때 비로소 결과가 출력됩니다. 그리고 바로 그때 [[https://monix.io/docs/2x/execution/scheduler.html|Scheduler]]가 필요해집니다. ''%%Scheduler%%''는 Monix의 향상된 ''%%ExecutionContext%%''입니다.
  
 이제 [[비동기_프로그래밍과_scala#연속_실행_side-effect의_연옥|3.1절]]에서 했던 것처럼 해 봅시다. 이제 [[비동기_프로그래밍과_scala#연속_실행_side-effect의_연옥|3.1절]]에서 했던 것처럼 해 봅시다.
줄 726: 줄 726:
 // 나쁜 예 (이 코드는 여전히 연속 실행됩니다) // 나쁜 예 (이 코드는 여전히 연속 실행됩니다)
 def timesFour(n: Int): Task[Int] = { def timesFour(n: Int): Task[Int] = {
-  // Task는 느긋하게 계산되므로 여기서는 아직 계산되지 않습니다.+  // Task는 lazy하게 계산되므로 여기서는 아직 계산되지 않습니다.
   val fa = timesTwo(n)   val fa = timesTwo(n)
   val fb = timesTwo(n)   val fb = timesTwo(n)
-  // 느긋한 계산으로 인해 값이 연속으로 계산됩니다.+  // Lazy evaluation으로 인해 값이 연속으로 계산됩니다.
   for (a <- fa; b <- fb) yield a + b   for (a <- fa; b <- fb) yield a + b
 } }
줄 747: 줄 747:
 ''%%flatMap%%''에 대해 ''%%Task%%''는 재귀적이면서 스택-안전하도록 설계되었습니다. 내부적으로는 트램폴린 패턴을 사용해서 매우 효율적이기도 합니다. ''%%Task%%''는 Rúnar Bjarnason의 논문 [[http://blog.higher-order.com/assets/trampolines.pdf|<Stackless Scala with Free Monads>]]을 바탕으로 구현되었습니다. ''%%flatMap%%''에 대해 ''%%Task%%''는 재귀적이면서 스택-안전하도록 설계되었습니다. 내부적으로는 트램폴린 패턴을 사용해서 매우 효율적이기도 합니다. ''%%Task%%''는 Rúnar Bjarnason의 논문 [[http://blog.higher-order.com/assets/trampolines.pdf|<Stackless Scala with Free Monads>]]을 바탕으로 구현되었습니다.
  
-''%%Task%%''를 사용한 ''%%sequence%%'' 구현은 [[비동기_프로그래밍과_scala#재귀_실행|4.3절]]의 ''%%Future%%''를 사용한 구현과 유사하지만, 아래의 ''%%sequence%%''는 시그니처가 느긋한 연산을 나타내고 있습니다.+''%%Task%%''를 사용한 ''%%sequence%%'' 구현은 [[비동기_프로그래밍과_scala#재귀_실행|4.3절]]의 ''%%Future%%''를 사용한 구현과 유사하지만, 아래의 ''%%sequence%%''는 시그니처가 lazy evaluation을 나타내고 있습니다.
  
 <code scala> <code scala>
줄 772: 줄 772:
 ''%%map%%'', ''%%flatMap%%'', ''%%mapBoth%%'' 등의 함수들을 사용할 때 우리는 더이상 "''%%(A => Unit) => Unit%%''"과 같은 내부 구조를 신경쓰지 않아도 됩니다. 그러한 함수들은 규칙적(lawful)이고, 순수함수적이고, 참조-투명하기 때문입니다. 이는 우리가 함수와 함수의 결과에 대해 생각할 때 그 함수가 사용되는 환경을 신경쓰지 않아도 됨을 의미합니다. ''%%map%%'', ''%%flatMap%%'', ''%%mapBoth%%'' 등의 함수들을 사용할 때 우리는 더이상 "''%%(A => Unit) => Unit%%''"과 같은 내부 구조를 신경쓰지 않아도 됩니다. 그러한 함수들은 규칙적(lawful)이고, 순수함수적이고, 참조-투명하기 때문입니다. 이는 우리가 함수와 함수의 결과에 대해 생각할 때 그 함수가 사용되는 환경을 신경쓰지 않아도 됨을 의미합니다.
  
-이것이 바로 Hakell의 ''%%IO%%''가 대단한 이유입니다. Haksell은 사이드 이펙트를 '흉내내지' 않습니다. ''%%IO%%'' 값을 반환하는 함수들은 순수함수적이고 사이드이펙트를 프로그램의 주변부로 밀어냅니다. 이것은 ''%%Task%%''에 대해서도 마찬가지입니다. ''%%Future%%''도 조급한 계산으로 인해 조금 복잡해지긴 하지만 어쨌든 나쁘지 않은 선택입니다.+이것이 바로 Hakell의 ''%%IO%%''가 대단한 이유입니다. Haksell은 사이드 이펙트를 '흉내내지' 않습니다. ''%%IO%%'' 값을 반환하는 함수들은 순수함수적이고 사이드이펙트를 프로그램의 주변부로 밀어냅니다. 이것은 ''%%Task%%''에 대해서도 마찬가지입니다. ''%%Future%%''도 eager evaluation으로 인해 조금 복잡해지긴 하지만 어쨌든 나쁘지 않은 선택입니다.
  
 그렇다면 ''%%Task%%'', ''%%Future%%'', ''%%Coeval%%'', ''%%Eval%%'', ''%%IO%%'', ''%%Id%%'', ''%%Observable%%'' 등의 형을 추상화하는 인터페이스를 만들 수는 없을까요? 그렇다면 ''%%Task%%'', ''%%Future%%'', ''%%Coeval%%'', ''%%Eval%%'', ''%%IO%%'', ''%%Id%%'', ''%%Observable%%'' 등의 형을 추상화하는 인터페이스를 만들 수는 없을까요?
  
-가능합니다. 우리는 이미 순차 실행을 추상화하는 ''%%flatMap%%''을 보았고, 병렬 실행을 추상화하는 ''%%mapBoth%%''를 보았습니다. 하지만 우리는 그러한 기능들을 고전적인 OOP 인터페이스로는 구현할 수 없습니다. 한 가지 이유는, ''%%Function1%%'' 인자에 대한 공변성과 반변성의 법칙(covariance and contravariance rules)에 따라 ''%%flatMap%%''에서 형에 대한 정보가 소실되기 때문입니다. (F-바운드 다형성 타입을 사용하면 소실을 막을 수 있지만, 이 기능은 구현 재사용에 더 적합할 뿐만 아니라 다른 OOP 언어는 지원지도 않습니다.) 그리고 그보다 더 근본적으로, OOP에서는 데이터 생성자를 메서드로 표현할 수 없기 때문입니다. (즉, OOP의 서브타입은 클래스 전체가 아니라 각각의 인스턴스에 적용되기 때문입니다.)+가능합니다. 우리는 이미 순차 실행을 추상화하는 ''%%flatMap%%''을 보았고, 병렬 실행을 추상화하는 ''%%mapBoth%%''를 보았습니다. 하지만 우리는 그러한 기능들을 고전적인 OOP 인터페이스로는 구현할 수 없습니다. 한 가지 이유는, ''%%Function1%%'' 인자에 대한 공변성과 반변성의 법칙(covariance and contravariance rules)에 따라 ''%%flatMap%%''에서 형에 대한 정보가 소실되기 때문입니다. (F-바운드 다형성 타입을 사용하면 소실을 막을 수 있지만, 이 기능은 구현 재사용에 더 적합할 뿐만 아니라 다른 OOP 언어는 지원지도 않습니다.) 그리고 그보다 더 근본적으로, OOP에서는 데이터 생성자를 메서드로 표현할 수 없기 때문입니다. (즉, OOP의 서브타입은 클래스 전체가 아니라 각각의 인스턴스에 적용되기 때문입니다.)
  
 마침 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의 번역을 참고했습니다.)) 마침 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의 번역을 참고했습니다.))
줄 784: 줄 784:
 > 하지만 그러한 설명 방식은 Scala와 사용자 모두에게 민폐입니다. 다른 언어에서 저 개념들은 단순히 설명하기 어려운 디자인 패턴에 불과합니다. 대부분의 다른 언어들은 형에 대한 표현성이 부족하기 때문입니다. 저 개념들을 표현할 수 있는 언어는 손에 꼽습니다. 언어 사용자의 입장에서도 문제가 생겼을 때 저 개념들을 모른 채 관련된 자료를 검색하는 것은 매우 고통스러운 일입니다. > 하지만 그러한 설명 방식은 Scala와 사용자 모두에게 민폐입니다. 다른 언어에서 저 개념들은 단순히 설명하기 어려운 디자인 패턴에 불과합니다. 대부분의 다른 언어들은 형에 대한 표현성이 부족하기 때문입니다. 저 개념들을 표현할 수 있는 언어는 손에 꼽습니다. 언어 사용자의 입장에서도 문제가 생겼을 때 저 개념들을 모른 채 관련된 자료를 검색하는 것은 매우 고통스러운 일입니다.
  
-> 또 저는 이것이 모르는 것에 대한 본능적인 공포에서 나오는 일종의 [[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]%%''를 정의하는 것이 불가능했을 것입니다. 이 예시들을 어떻게 반지성주의자들에게 설명할 수 있겠습니까.+> 또 저는 이것이 모르는 것에 대한 본능적인 공포에서 나오는 일종의 [[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 (연속 실행과 재귀 실행) ==== ==== Monad (연속 실행과 재귀 실행) ====
비동기_프로그래밍과_scala.1596956573.txt.gz · 마지막으로 수정됨: 2020/08/09 07:02 저자 cumul0529