팬 레 터 프로 그래 밍 (30) - 팬 레 터 IO: Free Monad - Monad 생산 라인
19932 단어 free
우 리 는 IO 와 관련 된 모든 연산 이 스 택 넘 치 는 문제 에 직면 할 것 이라는 것 을 안다.IO 가 예상 치 못 한 데이터 양 과 반복 순환 작업 을 하기 때문이다.그래서 IO 알고리즘 디자인 도 Trampoline 과 같은 데이터 구 조 를 사용 합 니 다.또는 우 리 는 Trampoline 데이터 구조 와 알고리즘 을 이용 하여 IO 구성 요소 라 이브 러 리 를 설계 해 야 한다.이렇게 생각하면 우 리 는 반드시 Trampoline 에 대해 깊이 있 고 추상 적 이 어야 한다.Free Monad 는 Trampline 의 연장 입 니 다.Free Monad 를 소개 하기 전에 우 리 는 먼저 현실 적 인 예 에서 토론 을 전개 합 니 다.
만약 에 우리 가 은행 이체 함 수 를 작성 하려 고 한다 면 우 리 는 먼저 이 함수 의 디자인 (function signature) 을 유도 할 수 있 습 니 다.
1 def transfer(amount: Double, from: Account, to: Account, user: User, 2 context: Authorization with Logger with ErrorHandler with Storage): Unit
우선, 우 리 는 여기에서 매개 변수 주입 (parameter injection) 방식 을 사용 합 니 다. transfer 함수 입력 매개 변수 에 context object 를 주입 합 니 다.이 context object 에는 인증, 조작 추적, 오류 처리, 데이터 액세스 등 이 포함 되 어 있 습 니 다.이 건 전통 적 인 OOP 프로 그래 밍 모드 죠?팬 레 터 프로그래머 에 게 이 context object 를 통 해 일련의 조작 을 할 수 있 습 니 다.IO 조작 을 포함 하여 부작용 (side effect) 이 있 는 조작 을 할 수 있다 는 것 이다.그러면 이 함 수 는 함수 조합 (function composition) 을 실현 할 수 없습니다.transfer 함 수 는 팬 레 터 프로그래머 가 사용 해 야 할 함수 가 아 닙 니 다.
아마도 우 리 는 팬 레 터 프로 그래 밍 의 측면 에서 이 함 수 를 디자인 하려 고 시도 해 야 할 것 이다. 팬 레 터 프로 그래 밍 이 제창 하 는 탈바꿈 불 능 (immutability) 방식 으로 디자인 해 야 한다. 즉, 함수 호출 자 에 게 뭔 가 를 되 돌려 주 는 것 이다.
예 를 들 어 우 리 는 함수 호출 자 에 게 동작 을 설명 하 는 프로그램 을 되 돌려 줄 수 있 습 니 다. 명령 (instruction):
1 def transfer(amount: Double, from: Account, to: Account, user: User): List[Instruction]
이 판본 은 틀림없이 범 함 판본 일 것 이다.하지만 Instruction 유형 에 상호작용 이 포함 된다 면 충분 하지 않 습 니 다.우 리 는 먼저 간단 한 상호작용 의 데이터 형식 을 살 펴 보 자.
1 trait Interact[A] // 2 // , String
3 case class Ask(prompt: String) extends Interact[String] 4 // ,
5 case class Tell(msg: String) extends Interact[Unit]
만약 우리 가 위의 생각 에 따라 명령 을 되 돌려 준다 면:
1 val prg = List(Ask("What's your first name?"), 2 Ask("What's your last name?"), 3 Tell("Hello ??? ???"))
이 프로그램 prg 는 결함 이 있 습 니 다. 상호작용 을 실현 할 수 없습니다.Ask 명령 을 임시 변수 에 저장 하면 목적 을 달성 할 수 있 을 것 같 습 니 다.그러면 우리 가 이 prg 를 다음 과 같이 바 꾸 면:
1 for { 2 x <- Ask("What's your first name?") 3 y <- Ask("What's your last name?") 4 _ <- Tell(s"Hello $y $x!") 5 } yield ()
이게 모 나 드 스타일 아니에요?원래 해결 방법 은 상호작용 유형 인 trait Interact [A] 를 Monad 로 바 꾸 면 된다.
그러나 인 터 랙 트 를 Monad 로 바 꾸 려 면 유닛 과 flatpap 두 함 수 를 실현 해 야 하 며 인 터 랙 트 trait 를 검사 하 는 것 은 불가능 하 다.
그럼 우 리 는 아래 의 노력 을 모 나 드 로 어떻게 바 꾸 느 냐 에 두 어야 한다.우리 가 이 명제 에서 프 리 모 나 드 를 언급 한 이상 모 나 드 생산 라인 이다.그러면 Free Monad 로 Interact 를 Monad 로 바 꿀 수 있 을까요?
우선 이 Free Monad 타 입 구 조 를 살 펴 보 겠 습 니 다.
1 trait Free[F[_],A] 2 case class Return[F[_],A](a: A) extends Free[F,A] 3 case class Bind[F[_],I,A](a: F[I], f: I => Free[F,A]) extends Free[F,A]
이 Free 결 과 는 Trampoline 과 너무 비슷 합 니 다.만약 Free 가 Monad 라면, 우 리 는 반드시 그것 의 flatMap 함 수 를 실현 해 야 한다.
1 trait Free[F[_],A] { 2 def unit(a: A) = Return(a) 3 def flatMap[B](f: A => Free[F,B]): Free[F,B] = this match { 4 case Return(a) => f(a) 5 // Trampoline FlatMap(FlatMap(b,g),f) == FlatMap(b, (x: Any) => FlatMap(g(x),f))
6 case Bind(fa,g) => Bind(fa, (x: Any) => g(x) flatMap f) 7 // 。 8 // case Bind(fa,g) => Bind(fa, g andThen (_ flatMap f))
9 } 10 def map[B](f: A => B): Free[F,B] = flatMap(a => Return(f(a))) 11
12 } 13 case class Return[F[_],A](a: A) extends Free[F,A] 14 case class Bind[F[_],I,A](a: F[I], f: I => Free[F,A]) extends Free[F,A]
우 리 는 아래 의 lift 함수 로 Interact [A] 를 free [F, A] 로 승격 할 수 있다. :
1 implicit def lift[F[_],A](fa: F[A]): Free[F,A] = Bind(fa, (a: A) => Return(a)) 2 //> lift: [F[_], A](fa: F[A])ch13.ex6.Free[F,A]
lift 가 있 으 면 우 리 는 할 수 있 습 니 다. prg 는 Monad 로 승격 되 었 습 니 다.
1 trait Interact[A] // 2 // , String
3 case class Ask(prompt: String) extends Interact[String] 4 // ,
5 case class Tell(msg: String) extends Interact[Unit] 6
7 implicit def lift[F[_],A](fa: F[A]): Free[F,A] = Bind(fa, (a: A) => Return(a)) 8 //> lift: [F[_], A](fa: F[A])ch13.ex6.Free[F,A]
9 for { 10 x <- Ask("What's your first name?") 11 y <- Ask("What's your last name?") 12 _ <- Tell(s"Hello $y $x!") 13 } yield () //> res0: ch13.ex6.Free[ch13.ex6.Interact,Unit] = Bind(Ask(What's your first nam 14 //| e?),<function1>)
이것 은 implicit scope 의 유형 전환 으로 인해 Interact 를 Free 로 승격 시 켰 고 Free 는 Monad 이기 때문에 우 리 는 for - comprehension 을 사용 할 수 있 습 니 다.
자, 이 프로그램 설명 이 끝 난 후에 어떻게 연산 해 야 합 니까?Free Monad 는 두 가지 기능 을 포함 하여 서로 관련 이 없 으 며 따로 고려 할 수 있 습 니 다.이른바 관심 분리 (separation of concern) 다.Free Monad 의 두 가지 기능 은 각각 Monad 와 Interpreter (번역 기) 이다.저 희 는 Monad 설명 프로그램 알고리즘 을 사용 하여 Interpreter 번역 프로그램 으로 특정한 운영 환경 에 대한 실행 가능 한 코드 를 만 듭 니 다.
Free Monad 의 Interpreter 는 알고리즘 과 연산 의 분리 고려 를 실현 했다.Interpreter 프로그램 연산 은 변환 함 수 를 통 해 이 루어 집 니 다.이 함 수 는 F [] 라 는 알고리즘 을 G [] 라 는 실행 가능 한 환경 을 위 한 Monad 실행 코드 로 해석 합 니 다.이러한 전환 은 바로 자연 변환 (Natural Transformation) 입 니 다. 함수 디자인 은 다음 과 같 습 니 다.
1 trait ~>[F[_],G[_]] { 2 def apply[A](fa: F[A]): G[A] 3 }
이 구축 함수 (constructor) 는 들 어 오 는 F [A] 를 G [A] 로 번역 한 것 이 분명 합 니 다.
현재 Interpreter 에서 알고리즘 을 실행 하 는 것 은 알고리즘 F [] 의 표현 식 을 일대일 로 G [] 변환 하 는 것 입 니 다.List 구조 에서 요 소 를 처리 하 는 방식 처럼 우 리 는 접 는 알고리즘 으로 F [] 구조 에서 표현 식 의 전환 을 실현 할 수 있 습 니 다.
1 def foldMap[G[_]: Monad](f: F ~> G): G[A] = this match { 2 case Return(a) => Monad[G].unit(a) 3 case Bind(b,g) => Monad[G].flatMap(f(b))(a => g(a).foldMap(f)) 4 }
우 리 는 foldpap 이 Free Monad F [] 의 표현 식 을 Monad G 상태 와 대응 하 는 것 을 보 았 다.빈 드 상 태 는 순환 적 으로 돌아 가 는 것 을 주의 하 세 요.
지금 우 리 는 가장 간단 한 번역 을 시도 해 볼 수 있다. F, Id 변환:
1 ype Id[A] = A 2 implicit val idMonad: Monad[Id] = new Monad[Id] { 3 def unit[A](a: A) = a 4 def flatMap[A,B](fa: A)(f: A => B): B = f(fa) 5 } //> idMonad : ch13.ex6.Monad[ch13.ex6.Id] = ch13.ex6$$anonfun$main$1$$anon$1@2 6 //| 530c12
7 object Console extends (Interact ~> Id) { 8 def apply[A](i: Interact[A]): A = i match { 9 case Ask(prompt) => { 10 println(prompt) 11 readLine 12 } 13 case Tell(msg) => println(msg) 14 } 15 }
연산 위의 인 터 랙 트 프로그램: Id 가 아무런 효과 가 없 기 때문에 인 터 랙 트 에서 Id 로 전환 하 는 것 은 인 터 랙 트 표현 식 을 직접 연산 하 는 것 입 니 다.
1 val prg = for { 2 x <- Ask("What's your first name?") 3 y <- Ask("What's your last name?") 4 _ <- Tell(s"Hello $y $x!") 5 } yield () //> prg : ch13.ex6.Free[ch13.ex6.Interact,Unit] = Bind(Ask(What's your first n 6 //| ame?),<function1>)
7
8 prg.foldMap(Console)
아니면 우 리 는 좀 더 복잡 한 번역 을 시도 해 볼 수 있다.
1 type Tester[A] = Map[String, String] => (List[String], A) 2 implicit val testerMonad = new Monad[Tester] { 3 def unit[A](a: A) = (_ => (List(),a)) 4 def flatMap[A,B](ta: Tester[A])(f: A => Tester[B]): Tester[B] = { 5 m => { 6 val (l1,a) = ta(m) 7 val (l2,b) = f(a)(m) 8 (l1 ++ l2,b) 9 } 10 } 11 } //> testerMonad : ch13.ex6.Monad[ch13.ex6.Tester]{def unit[A](a: A): Map[Strin 12 //| g,String] => (List[Nothing], A)} = ch13.ex6$$anonfun$main$1$$anon$2@5b464ce 13 //| 8
14 object TestConsole extends (Interact ~> Tester) { 15 def apply[A](i: Interact[A]): Tester[A] = i match { 16 case Ask(prompt) => m => (List(), m(prompt)) 17 case Tell(msg) => _ => (List(msg),()) 18 } 19 }
이상 저 희 는 Interact 를 실행 하 는 상호작용 정 보 를 맵 [String, String] 구조 에 저장 합 니 다.여기 서 인 터 랙 트 에서 함수 맵 = > (List, A) 으로 전환 되 었 습 니 다.
1 val prg = for { 2 x <- Ask("What's your first name?") 3 y <- Ask("What's your last name?") 4 _ <- Tell(s"Hello $y $x!") 5 } yield () //> prg : ch13.ex6.Free[ch13.ex6.Interact,Unit] = Bind(Ask(What's your first n 6 //| ame?),<function1>)
7
8 prg.foldMap(TestConsole)
지난 절 에 우 리 는 Trampoline 에 대해 논 의 했 습 니 다. 팬 레 터 알고리즘 에서 불가피 한 스 택 넘 침 문 제 를 해결 하 는 것 이 주 된 목적 입 니 다. 만약 우리 가 Free Monad 로 IO 문 제 를 해결한다 면 스 택 넘 침 문 제 는 피 할 수 없습니다. 우 리 는 Free Monad 에서 Trampoline 유형 을 사용 하 는 것 을 고려 해 야 합 니 다. 그래 야 우 리 는 안심 하고 Free Monad 로 어떠한 종류의 Monad 를 생 성하 고 연산 할 수 있 습 니 다.스 택 넘 치 는 문 제 를 힙 으로 해결 합 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
C#은 텍스트와 이미지가 포함된 PowerPoint 독을 생성합니다.PowerPoint 문서(슬라이드)는 일반적인 프레젠테이션의 문서이며, 스피치, 교육, 제품의 프레젠테이션 등의 면에서 널리 응용되고 있다. 이 문은 간단한 PowerPoint 파일을 만드는 방법을 보여줍니다. 다음...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.