왜 모나드가 필요합니까?


366

겸손한 의견으로 유명한 질문에 대한 답 은 "모나드 란 무엇입니까?" 특히 투표율이 가장 높은 사람들은 왜 모나드가 실제로 필요한지 명확하게 설명하지 않고 모나드가 무엇인지 설명하려고 노력 합니다 . 문제에 대한 해결책으로 설명 할 수 있습니까?




4
어떤 연구를 이미 했습니까? 어디 봤어? 어떤 자료를 찾았습니까? 문의하기 전에 상당한 양의 조사를 수행하고 어떤 조사를 수행했는지 질문에 표시해 주시기 바랍니다 . 자원에 대한 동기 부여를 설명하는 많은 자료가 있습니다. 전혀 찾지 못한 경우 조금 더 연구해야 할 수도 있습니다. 당신이 일부를 찾았지만 도움이되지 않았다면, 당신이 찾은 것과 왜 그들이 왜 당신을 위해 일하지 않았는지 설명한다면 이것은 더 좋은 질문이 될 것입니다.
DW

8
이것은 확실히 Programmers.StackExchange에 더 적합하며 StackOverflow에 적합하지 않습니다. 가능하다면 이주를하기로 결심했지만 저는 할 수 없습니다. = (
jpmc26

3
@ jpmc26 "주로 의견 기반"으로 폐쇄 될 가능성이 높다. 여기에 적어도 기회가 있습니다 (어제 엄청난 수의
공감 율

답변:


580

왜 모나드가 필요합니까?

  1. 함수 만 사용하여 프로그램하고 싶습니다 . ( "기능 프로그래밍 (FP)").
  2. 그런 다음 첫 번째 큰 문제가 있습니다. 이것은 프로그램입니다 :

    f(x) = 2 * x

    g(x,y) = x / y

    무엇을 먼저 실행해야하는지 어떻게 알 수 있습니까? 우리는 어떻게 함수를 사용하여 정렬 된 순서의 함수 (즉 , 프로그램 ) 형성 할 수 있습니까?

    솔루션 : 작성 기능 . 당신이 먼저 원하는 g다음 f, 그냥 작성하십시오 f(g(x,y)). 이런 식으로 "프로그램"도 기능입니다 : main = f(g(x,y)). 좋습니다만 ...

  3. 더 많은 문제 : 일부 기능 이 실패 할 수 있습니다 (예 : g(2,0)0으로 나누기). 우리는이 없습니다 에는 "예외" FP의를 (예외가 함수가 아닙니다). 우리는 그것을 어떻게 해결합니까?

    솔루션 : 함수가 두 가지 종류의 것을 반환하도록 하겠습니다 : g : Real,Real -> Real(두 개의 실수에서 실제로 기능하는) 대신 , g : Real,Real -> Real | Nothing(두 개의 실수에서 (실제로 또는 아무것도)로) 함수를 허용합시다 .

  4. 그러나 함수는 (단순하게) 한 가지만 반환 해야 합니다.

    해결책 : 반환 할 새로운 유형의 데이터, 즉 실제 또는 전혀 아무것도 포함하지 않는 " boxing type "을 만들어 봅시다 . 따라서 우리는 가질 수 있습니다 g : Real,Real -> Maybe Real. 좋습니다만 ...

  5. 이제 어떻게됩니까 f(g(x,y))? f을 (를) 사용할 준비가되지 않았습니다 Maybe Real. 그리고 우리 g는를 사용할 수있는 모든 함수를 변경하고 싶지 않습니다 Maybe Real.

    솔루션 : "connect"/ "compose"/ "link"기능을위한 특수 기능을 갖도록 합시다 . 이런 식으로, 우리는 뒤에서 한 함수의 출력을 다음 함수를 제공하도록 조정할 수 있습니다.

    우리의 경우 : g >>= f(connect / compose gto f). 우리는 출력 >>=을 얻고 g, 검사하고, Nothing단지 호출 f하고 반환 하지 않는 경우를 원합니다 Nothing. 또는 반대로, 박스를 추출하여 Real공급 f하십시오. (이 알고리즘은 유형 에 >>=대한 구현 일뿐입니다 Maybe). 또한 "복싱 유형"(다른 상자, 다른 적응 알고리즘) 당 한 번만>>= 작성해야합니다 .

  6. 동일한 패턴을 사용하여 해결할 수있는 다른 많은 문제가 발생합니다. 1. "상자"를 사용하여 다른 의미 / 값을 코드화 / 저장하고 g이러한 "상자 값"을 반환하는 것과 같은 기능을 갖습니다 . 2. 의 출력을 의 입력에 g >>= f연결하는 데 도움 이되는 작곡가 / 링커 가 있으므로 전혀 변경할 필요가 없습니다 .gff

  7. 이 기술을 사용하여 해결할 수있는 놀라운 문제는 다음과 같습니다.

    • 일련의 기능 ( "프로그램")에서 모든 기능이 공유 할 수있는 전역 상태를 갖는 경우 : solution StateMonad.

    • 우리는 "불순 함수"를 좋아하지 않습니다 : 동일한 입력에 대해 다른 출력을 생성하는 함수 . 따라서 해당 함수를 표시하여 태그 / 박스 값을 반환합니다 : monad.IO

총 행복!


64
@Carl 우리를 계몽시키기 위해 더 나은 답변을 작성해주세요
XrXr

15
@Carl 나는이 패턴으로부터 혜택을받는 많은 문제들이 있고 (6 점), IO모나드는 목록에서 한 가지 더 문제 라는 점이 분명하다고 생각 합니다 IO(7 점). 반면에 IO한 번만 나타나고 마지막에 "IO에 대해 이야기하는 대부분의 시간"을 이해하지 마십시오.
cibercitizen1

4
모나드에 대한 큰 오해 : 상태에 대한 모나드; 예외 처리에 관한 모나드; 모나드없이 순수한 FPL에서 IO를 구현할 수있는 방법은 없습니다; 모나드는 분명합니다 (모순은입니다 Either). 대부분의 대답은 "왜 펑터가 필요한가?"입니다.
vlastachu

4
"6. 2. 의 출력을 의 입력에 g >>= f연결하는 데 도움 이되는 작곡가 / 링커 가 있으므로 전혀 변경할 필요가 없습니다 ." gff 이것은 전혀 옳지 않습니다 . 에서 f(g(x,y)), 이전에 f무엇이든 생성 할 수있었습니다. 될 수 있습니다 f:: Real -> String. "모노 딕 컴포지션"을 사용하면 을 생성 하도록 변경해야합니다Maybe String . 게다가, >>=그 자체 는 맞지 않습니다 ! 그것은이다 >=>,이 구성을하지 않습니다 그 >>=. Carl의 답변에서 dfeuer와의 토론을 참조하십시오.
Will Ness

3
귀하의 답변은 모나드 IMO가 실제로 "기능"(Kleisli 화살표)의 구성 / 품질에 관한 것으로 가장 잘 설명되어 있다는 점에서 옳습니다. Functor와 같은 모든 종류의 방식으로 상자를 배선 할 수 있습니다. 이들을 서로 연결하는 이 특정한 방법은 "모나드"를 정의하는 것입니다.
Will Ness

219

대답은 물론 "우리는하지 않습니다" 입니다. 모든 추상화와 마찬가지로 필요하지 않습니다.

하스켈은 모나드 추상화가 필요하지 않습니다. 순수한 언어로 IO를 수행 할 필요는 없습니다. 그만큼IO유형 자체가 잘 처리한다. 기존 모나드 desugaring do블록에 desugaring로 대체 될 수있다 bindIO, returnIOfailIO에 정의로서 GHC.Base모듈. (해커에 관한 문서화 된 모듈이 아니므로 문서화를 위해 소스 를 가리켜 야 합니다.) 따라서 모나드 추상화가 필요하지 않습니다.

필요하지 않다면 왜 존재합니까? 많은 계산 패턴이 모나 딕 구조를 형성한다는 것이 밝혀졌습니다. 구조를 추상화하면 해당 구조의 모든 인스턴스에서 작동하는 코드를 작성할 수 있습니다. 더 간결하게 말하면 코드 재사용.

기능적 언어에서 코드 재사용을위한 가장 강력한 도구는 기능의 구성이었습니다. 좋은 오래된 (.) :: (b -> c) -> (a -> b) -> (a -> c)운영자는 매우 강력합니다. 최소한의 구문이나 의미상의 오버 헤드로 작은 함수를 작성하고 쉽게 붙일 수 있습니다.

그러나 유형이 제대로 작동하지 않는 경우가 있습니다. 가지고있을 때 무엇을 foo :: (b -> Maybe c)하고bar :: (a -> Maybe b) ? foo . bar때문에, 유형 체킹하지 않습니다 bMaybe b동일한 유형 없습니다.

하지만 ... 거의 옳습니다. 약간의 여유가 필요합니다. Maybe b마치 기본적으로 다룰 수 있기를 원합니다b . 그러나 동일한 유형으로 평평하게 처리하는 것은 좋지 않습니다. Tony Hoare가 유명한 것으로 10 억 달러의 실수 라고 불리는 널 포인터와 거의 같은 것 입니다. 따라서 동일한 유형으로 처리 할 수없는 경우 컴포지션 메커니즘이 (.)제공 하는 확장 방법을 찾을 수 있습니다 .

이 경우, 근본적인 이론을 실제로 조사하는 것이 중요합니다 (.). 다행히 누군가 누군가 이미 우리를 위해 이것을했습니다. 범주 라고 알려진 수학적 구성 의 조합 (.)id구성 이 밝혀졌습니다 . 그러나 카테고리를 형성하는 다른 방법이 있습니다. 예를 들어 클라이 슬리 (Kleisli) 범주를 사용하면 구성중인 객체를 약간 보강 할 수 있습니다. 의 Kleisli 범주 는 과 로 구성됩니다 . 즉, 범주의 개체는Maybe(.) :: (b -> Maybe c) -> (a -> Maybe b) -> (a -> Maybe c)id :: a -> Maybe a(->) 로 a를Maybe 시키므로로 (a -> b)됩니다 (a -> Maybe b).

그리고 갑자기 우리는 작곡의 힘을 (.) 작업이 작동하지 않는 것으로 확장했습니다. 이것이 새로운 추상화의 원천입니다. Kleisli 카테고리는 단순한 것보다 더 많은 유형으로 작동 Maybe합니다. 그들은 카테고리 법을 준수하면서 적절한 카테고리를 구성 할 수있는 모든 유형에서 작동합니다.

  1. 왼쪽 정체성 : id . f =f
  2. 올바른 정체성 : f . id =f
  3. 연관성 : f . (g . h) =(f . g) . h

자신의 유형이이 세 가지 법률을 준수한다는 것을 증명할 수있는 한 Kleisli 범주로 바꿀 수 있습니다. 그리고 그에 대한 큰 문제는 무엇입니까? 모나드는 Kleisli 범주와 정확히 같은 것으로 나타났습니다. Monad'들 returnKleisli 동일하다 id. Monad의는 (>>=)Kleisli 동일하지 않은 (.),하지만 다른 측면에서 매우 쉽게 쓸 수있는 각 것으로 밝혀졌습니다. 범주 법은 모나드 법과 동일합니다.(>>=) 하고 (.).

그렇다면 왜이 모든 귀찮은 일을 겪어야합니까? 왜Monad 언어에 추상화가 있습니까? 위에서 언급했듯이 코드 재사용이 가능합니다. 또한 두 가지 다른 차원에서 코드를 재사용 할 수 있습니다.

코드 재사용의 첫 번째 차원은 추상화의 존재에서 직접 발생합니다. 추상화의 모든 인스턴스에서 작동하는 코드를 작성할 수 있습니다. 의 모든 인스턴스와 작동하는 루프로 구성된 전체 모나드 루프 패키지가 Monad있습니다.

두 번째 차원은 간접적이지만 구성의 존재로 이어집니다. 구성이 쉬운 경우 재사용이 가능한 작은 덩어리로 코드를 작성하는 것이 당연합니다. 이것은 (.)함수 연산자를 사용하여 작고 재사용 가능한 함수를 작성 하는 것과 같은 방법 입니다.

그렇다면 왜 추상화가 존재합니까? 코드에서 더 많은 구성을 가능하게하는 도구 인 것으로 입증되었으므로 재사용 가능한 코드가 생성되고 재사용 가능한 코드가 생성됩니다. 코드 재사용은 프로그래밍의 성배 중 하나입니다. 모나드 추상화는 우리를 그 성배쪽으로 조금 움직이기 때문에 존재합니다.


2
카테고리와 Kleisli 카테고리의 관계를 설명 할 수 있습니까? 귀하가 설명하는 세 가지 법률은 모든 범주에 적용됩니다.
dfeuer

1
@dfeuer 오. 코드에 넣으려면 newtype Kleisli m a b = Kleisli (a -> m b). Kleisli 범주는 categorical 형 반환 형식 ( b이 경우)이 형식 생성자의 인수 인 함수 m입니다. Iff Kleisli m가 범주를 형성하면 mMonad입니다.
Carl

1
범주 형 반환 유형은 정확히 무엇입니까? Kleisli m그 객체에서 화살표 것을 하스켈 유형 및 분류 형성되는 것 a까지가 b기능 출신 am b으로 id = return(.) = (<=<). 맞습니까? 아니면 다른 수준의 물건이나 무언가를 섞고 있습니까?
dfeuer

1
@dfeuer 맞습니다. 객체는 모든 유형이고 형태는 유형 a과 사이 b에 있지만 간단한 함수는 아닙니다. m함수의 반환 값에 여분 으로 꾸며져 있습니다.
Carl

1
카테고리 이론 용어가 실제로 필요한가? 하스켈은 타입을 그림으로 바꾸면 그림이 그려지는 방식에 대한 DNA가 될 수 있습니다 (종종 의존적이지만 *), 그림을 사용하여 이름이 작은 루비 문자 인 프로그램을 작성합니다 아이콘 위에.
aoeu256

24

벤자민 피어스는 TAPL 에서 말했다

유형 시스템은 프로그램에서 용어의 런타임 동작에 대한 일종의 정적 근사값을 계산하는 것으로 간주 될 수 있습니다.

그렇기 때문에 강력한 형식 시스템을 갖춘 언어가 형편없는 언어보다 엄격하게 표현되는 이유입니다. 같은 방식으로 모나드에 대해 생각할 수 있습니다.

@Carl과 sigfpe point처럼, 모나드, 타입 클래스 또는 다른 추상적 인 것들에 의존하지 않고 원하는 모든 연산을 데이터 타입에 장비 할 수 있습니다. 그러나 모나드는 재사용 가능한 코드를 작성할뿐만 아니라 모든 중복 디테일을 추상화 할 수 있습니다.

예를 들어 목록을 필터링하고 싶다고 가정 해 봅시다. 가장 간단한 방법은 filter다음 filter (> 3) [1..10]과 같은 함수 를 사용 하는 것 [4,5,6,7,8,9,10]입니다.

filter왼쪽에서 오른쪽으로 누산기를 전달하는 약간 더 복잡한 버전은 입니다.

swap (x, y) = (y, x)
(.*) = (.) . (.)

filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]

모든 i것을 얻으려면 다음 과 같이 i <= 10, sum [1..i] > 4, sum [1..i] < 25쓸 수 있습니다.

filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]

어느 것이 [3,4,5,6] .

또는 nub목록에서 중복 요소를 제거 하는 함수 를 재정의 할 수 있습니다 filterAccum.

nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []

nub' [1,2,4,5,4,3,1,8,9,4]같습니다 [1,2,4,5,3,8,9]. 여기서리스트는 누산기로 전달됩니다. 목록 모나드를 떠날 수 있기 때문에 코드가 작동하므로 전체 계산이 순수하게 유지됩니다 ( 실제로 notElem는 사용하지 >>=않지만 가능합니다). 그러나 IO 모나드를 안전하게 두는 것은 불가능합니다 (즉, IO 동작을 실행할 수없고 순수한 값을 반환 할 수 없습니다. 값은 항상 IO 모나드에 래핑됩니다). 또 다른 예는 가변 배열입니다. 가변 배열이있는 ST 모나드를 떠난 후에는 더 이상 일정한 시간에 배열을 업데이트 할 수 없습니다. 따라서 Control.Monad모듈 에서 모나 딕 필터링이 필요 합니다.

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
   flg <- p x
   ys  <- filterM p xs
   return (if flg then x:ys else ys)

filterM리스트의 모든 요소에 대해 모나드 동작을 실행하여 모나드 동작이 반환하는 요소를 생성합니다 True.

배열이 포함 된 필터링 예 :

nub' xs = runST $ do
        arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
        let p i = readArray arr i <* writeArray arr i False
        filterM p xs

main = print $ nub' [1,2,4,5,4,3,1,8,9,4]

[1,2,4,5,3,8,9]예상대로 인쇄합니다 .

그리고 어떤 요소를 반환할지 묻는 IO 모나드 버전

main = filterM p [1,2,4,5] >>= print where
    p i = putStrLn ("return " ++ show i ++ "?") *> readLn

예 :

return 1? -- output
True      -- input
return 2?
False
return 4?
False
return 5?
True
[1,5]     -- output

그리고 마지막 예시 filterAccum로 다음과 같은 관점에서 정의 할 수 있습니다 filterM.

filterAccum f a xs = evalState (filterM (state . flip f) xs) a

StateT평범한 데이터 유형 인, 후드 사용 모나드.

이 예제에서는 모나드를 통해 계산 컨텍스트를 추상화하고 재사용 가능한 코드를 작성할 수있을뿐 아니라 (@Carl이 설명하는 것처럼 모나드의 구성 성으로 인해) 사용자 정의 데이터 유형과 내장 프리미티브를 균일하게 처리 할 수 ​​있습니다.


1
이 답변은 왜 Monad 타입 클래스가 필요한지 설명합니다. 모나드가 필요한 이유를 이해하는 가장 좋은 방법은 모나드와 적용되는 펑터 ( 1 , 2)의 차이점에 대해 읽는 것 입니다.
user3237465

20

나는 IO특히 뛰어난 모나드 라고 생각 해서는 안되지만 초보자에게는 더 놀라운 모나드 중 하나이므로 설명을 위해 사용할 것입니다.

Haskell을위한 IO 시스템 구축

순전히 기능적인 언어 (그리고 실제로 Haskell이 처음 시작한 언어)를위한 가장 간단한 IO 시스템은 다음과 같습니다.

main :: String -> String
main _ = "Hello World"

- lazyness으로, 간단한 서명은 실제로 대화 형 터미널 프로그램을 구축하기에 충분 매우 하지만, 제한을. 가장 실망스러운 것은 텍스트 만 출력 할 수 있다는 것입니다. 좀 더 흥미로운 출력 가능성을 추가하면 어떨까요?

data Output = TxtOutput String
            | Beep Frequency

main :: String -> [Output]
main _ = [ TxtOutput "Hello World"
          -- , Beep 440  -- for debugging
          ]

귀엽지 만 물론 훨씬 더 현실적인 "대체 출력"은 파일에 쓰는 것 입니다. 그러나 파일 에서 읽을 방법이 필요 합니다. 어떤 기회?

우리가 main₁프로그램 을 가져 와서 파일을 프로세스로 파이프 처리 할 때 (운영 체제 기능 사용), 본질적으로 파일 읽기를 구현했습니다. Haskell 언어 내에서 파일 읽기를 트리거 할 수 있다면 ...

readFile :: Filepath -> (String -> [Output]) -> [Output]

이것은 "대화식 프로그램"을 사용합니다 String->[Output] 을 사용하고 파일에서 얻은 문자열을 공급하며 주어진 대화식 프로그램을 단순히 실행하는 비 대화식 프로그램을 생성합니다.

여기에는 한 가지 문제가 있습니다. 파일을 읽는 시점에 대한 개념은 없습니다 . [Output]목록 확인에 좋은 순서 준다 출력을 , 그러나 우리는 때를 주문하지 않는 입력이 완료됩니다.

해결 방법 : 입력 이벤트도 수행 할 작업 목록에 항목을 만드십시오.

data IO = TxtOut String
         | TxtIn (String -> [Output])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [Output])
         | Beep Double

main :: String -> [IO₀]
main _ = [ FileRead "/dev/null" $ \_ ->
             [TxtOutput "Hello World"]
          ]

이제 불균형을 발견 할 수 있습니다. 파일을 읽고 출력에 의존 할 수 있지만 파일 내용을 사용하여 다른 파일을 읽도록 결정할 수는 없습니다. 명백한 해결책 : 입력 이벤트의 결과를 IO단지 유형 이 아닌 유형으로 만드십시오 Output. 그것은 간단한 텍스트 출력을 포함하지만 추가 파일 등을 읽을 수 있습니다.

data IO = TxtOut String
         | TxtIn (String -> [IO₁])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [IO₁])
         | Beep Double

main :: String -> [IO₁]
main _ = [ TxtIn $ \_ ->
             [TxtOut "Hello World"]
          ]

이제 실제로 프로그램에서 원하는 파일 작업을 표현할 수 있지만 (성능이 좋지는 않지만) 다소 복잡합니다.

  • main₃전체 작업 목록 을 생성합니다 . 우리는 왜 서명을 사용하지 :: IO₁않습니까? 이것은 특별한 경우입니다.

  • 이 목록은 더 이상 프로그램 흐름에 대한 신뢰할 수있는 개요를 제공하지 않습니다. 대부분의 후속 계산은 일부 입력 작업의 결과로만 "공지"됩니다. 따라서 우리는리스트 구조를 버리고, 각 출력 작업에 단순히 "만약"을 적용 할 수 있습니다.

data IO = TxtOut String IO
         | TxtIn (String -> IO₂)
         | Terminate

main :: IO
main = TxtIn $ \_ ->
         TxtOut "Hello World"
          Terminate

나쁘지 않아!

이 모든 것이 모나드와 어떤 관련이 있습니까?

실제로 모든 프로그램을 정의하기 위해 일반 생성자를 사용하고 싶지는 않습니다. 좋은 기본 생성자 몇 개가 필요하지만 대부분의 상위 수준의 항목에는 멋진 고급 서명이있는 함수를 작성하려고합니다. 의미있는 유형의 값을 수락하고 결과로 IO 동작을 생성합니다.

getTime :: (UTCTime -> IO₂) -> IO
randomRIO :: Random r => (r,r) -> (r -> IO₂) -> IO
findFile :: RegEx -> (Maybe FilePath -> IO₂) -> IO

여기에 분명히 패턴이 있으며, 다음과 같이 작성하는 것이 좋습니다.

type IO a = (a -> IO₂) -> IO    -- If this reminds you of continuation-passing
                                  -- style, you're right.

getTime :: IO UTCTime
randomRIO :: Random r => (r,r) -> IO r
findFile :: RegEx -> IO (Maybe FilePath)

이제는 친숙해 보이기 시작하지만 여전히 위장 아래에서 얇게 위장 된 일반 기능 만 처리하고 있으며 위험합니다. 각“값-행동”에는 실제로 포함 된 기능의 결과적 행동을 전달할 책임이 있습니다. 전체 프로그램의 제어 흐름은 중간에 잘못된 동작 하나에 의해 쉽게 중단됩니다). 우리는 그 요구 사항을 명시 적으로 만드는 것이 좋습니다. 글쎄, 그것은 모나드 법칙 으로 밝혀 졌지만, 표준 바인드 / 조인 연산자없이 실제로 공식화 할 수 있는지는 확실하지 않습니다.

여하튼, 우리는 이제 적절한 모나드 인스턴스를 가진 IO의 공식화에 도달했습니다 :

data IO a = TxtOut String (IO a)
           | TxtIn (String -> IO a)
           | TerminateWith a

txtOut :: String -> IO ()
txtOut s = TxtOut s $ TerminateWith ()

txtIn :: IO String
txtIn = TxtIn $ TerminateWith

instance Functor IO where
  fmap f (TerminateWith a) = TerminateWith $ f a
  fmap f (TxtIn g) = TxtIn $ fmap f . g
  fmap f (TxtOut s c) = TxtOut s $ fmap f c

instance Applicative IO where
  pure = TerminateWith
  (<*>) = ap

instance Monad IO where
  TerminateWith x >>= f = f x
  TxtOut s c >>= f = TxtOut s $ c >>= f
  TxtIn g >>= f = TxtIn $ (>>=f) . g

분명히 이것은 효율적인 IO 구현은 아니지만 원칙적으로 사용할 수 있습니다.


@jdlugosz : IO3 a ≡ Cont IO2 a. 그러나 나는 초보자에게 친숙하다는 명성을 얻지 못했기 때문에 계속 모나드를 이미 알고있는 사람들에게는 더 많은 의견을 언급했습니다.
leftaroundabout

4

모나드 는 반복되는 문제를 해결하기위한 편리한 프레임 워크 일뿐입니다. 첫째, 모나드는해야 (즉, 요소 (또는 유형)을 보지 않고 매핑을 지원해야합니다), 그들은 또한 가져와야한다 바인딩 (또는 체인) 운영과 (요소 유형에서 모나드 가치를 창출 할 수있는 방법을 return). 마지막으로, bind그리고 return또한 모나드 법이라고,이 방정식 (왼쪽 및 오른쪽 정체성)을 만족해야합니다. (또는 flattening operation바인딩 대신 모나드를 정의 할 수도 있습니다.)

리스트 모나드는 일반적으로 비 결정론을 처리하는 데 사용됩니다. 바인드 작업은 목록의 한 요소 (직관적으로 모든 세계 에서 병렬로 )를 선택하고 프로그래머가 일부 계산을 수행 한 다음 모든 세계의 결과를 단일 목록으로 결합합니다 (중첩 목록을 연결하거나 병합하여) ). Haskell의 모나 딕 프레임 워크에서 순열 함수를 정의하는 방법은 다음과 같습니다.

perm [e] = [[e]]
perm l = do (leader, index) <- zip l [0 :: Int ..]
            let shortened = take index l ++ drop (index + 1) l
            trailer <- perm shortened
            return (leader : trailer)

다음은 repl 세션 의 예입니다 .

*Main> perm "a"
["a"]
*Main> perm "ab"
["ab","ba"]
*Main> perm ""
[]
*Main> perm "abc"
["abc","acb","bac","bca","cab","cba"]

리스트 모나드는 결코 부작용 계산이 아님에 유의해야한다. 모나드 인 수학적 구조 (즉, 위에서 언급 한 인터페이스와 법칙을 따르는)는 부작용을 암시하지 않지만 부작용을 일으키는 현상은 종종 모나 딕 틀에 잘 맞습니다.


3

모나드는 기본적으로 체인에서 함수를 함께 구성하는 역할을합니다. 기간.

이제 구성 방식이 기존 모나드에 따라 다르므로 다른 동작 (예 : 상태 모나드에서 변경 가능한 상태 시뮬레이션)이 발생합니다.

모나드에 대한 혼동은 매우 일반적인 것, 즉 함수를 구성하는 메커니즘이기 때문에 많은 것들에 사용될 수 있기 때문에 모나드가 "함수 구성"에 대해서만 상태, IO 등에 관한 것이라고 믿게한다. ".

모나드에 대한 흥미로운 점 중 하나는 컴포지션의 결과가 항상 "M a"유형, 즉 "M"으로 태그가 지정된 봉투 내부의 값이라는 것입니다. 이 기능은 예를 들어 불순 코드와 순수 코드를 명확하게 구분하여 구현하기에 정말 좋습니다. 모든 불순 동작을 "IO a"유형의 함수로 선언하고 IO 모나드를 정의 할 때 아무런 기능도 제공하지 않습니다. "IO a"내부의 값 결과적으로 순수한 함수는 없으며 동시에 "IO a"에서 값을 가져옵니다. 순수한 상태를 유지하면서 값을 취할 방법이 없기 때문입니다 (이 기능은 "IO"모나드 내부에 있어야만 사용할 수 있습니다) 그러한 가치). (참고 : 아무 것도 완벽하지 않으므로 "unsafePerformIO : IO a-> a"를 사용하여 "IO 구속 복귀"를 분리 할 수 ​​있습니다.


2

타입 생성자그 타입 패밀리의 값을 반환하는 함수 가 있다면 모나드가 필요 합니다 . 결국, 이러한 종류의 기능을 함께 결합하고 싶습니다 . 이것들은 대답 해야하는 세 가지 핵심 요소 입니다.

자세히 설명하겠습니다. 당신은 Int, StringReal입력의 기능 Int -> String, String -> Real그리고에 이렇게. 로 끝나는 이러한 기능을 쉽게 결합 할 수 있습니다 Int -> Real. 인생은 좋다

그런 다음 언젠가 새로운 유형의 패밀리 를 작성해야합니다 . 값 없음 ( Maybe), 오류 반환 ( Either), 여러 결과 ( List) 등 의 가능성을 고려해야하기 때문일 수 있습니다 .

공지 Maybe타입 생성자이다. 같은 유형을 취하고 Int새로운 유형을 반환합니다 Maybe Int. 가장 먼저 기억해야 할 것은 타입 생성자도없고 모나드도 없다는 것입니다.

물론, 당신은 당신의 타입 생성자를 사용하려면 코드에서, 곧 당신은 같은 기능을 종료 Int -> Maybe String하고 String -> Maybe Float. 이제는 기능을 쉽게 결합 할 수 없습니다. 인생은 더 이상 좋지 않습니다.

그리고 모나드가 구조에 올 때입니다. 그들은 당신이 그런 종류의 기능들을 다시 결합 할 수있게합니다. 당신은 단지 구성을 변경해야합니다 . > == .


2
이것은 유형 패밀리와 관련이 없습니다. 당신은 실제로 무엇에 대해 이야기하고 있습니까?
dfeuer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.