사용자 도구

사이트 도구


비동기_프로그래밍과_scala

차이

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

차이 보기로 링크

양쪽 이전 판 이전 판
다음 판
이전 판
비동기_프로그래밍과_scala [2020/08/09 07:25]
cumul0529 [Future와 Promise] 줄바꿈 추가
비동기_프로그래밍과_scala [2020/08/09 07:39] (현재)
cumul0529 [함수형 프로그래밍과 타입 클래스] 마크업 수정
줄 177: 줄 177:
 </code> </code>
  
-바로 이것이 비결정론의 실례(實例)입니다. 두 개의 작업이 종료되는 순서가 보장되지 않기 때문에 우리는 동기화를 수행하는 소규모의 상태 기계(state machine)를 구현해야 합니다.+바로 이것이 비결정론의 실제 예시입니다. 두 개의 작업이 종료되는 순서가 보장되지 않기 때문에 우리는 동기화를 수행하는 소규모의 상태 기계(state machine)를 구현해야 합니다.
  
 먼저 상태 기계의 ADT를 정의합니다. 먼저 상태 기계의 ADT를 정의합니다.
줄 637: 줄 637:
 ===== 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%%''가 호출되기 전까지 어떤 실행이나 사이드 이펙트도 발생시키지 않습니다.
   * 메모아이제이션이 가능합니다.   * 메모아이제이션이 가능합니다.
   * 반드시 다른 논리 스레드에서 실행되어야 하는 것은 아닙니다.   * 반드시 다른 논리 스레드에서 실행되어야 하는 것은 아닙니다.
줄 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>
줄 776: 줄 776:
 그렇다면 ''%%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.1596957950.txt.gz · 마지막으로 수정됨: 2020/08/09 07:25 저자 cumul0529