겸손한 의견으로 유명한 질문에 대한 답 은 “모나드 란 무엇입니까?” 특히 투표율이 가장 높은 사람들은 왜 모나드가 실제로 필요한지 명확하게 설명하지 않고 모나드가 무엇인지 설명하려고 노력 합니다 . 문제에 대한 해결책으로 설명 할 수 있습니까?
답변
왜 모나드가 필요합니까?
- 함수 만 사용하여 프로그램하고 싶습니다 . ( “기능 프로그래밍 (FP)”).
-
그런 다음 첫 번째 큰 문제가 있습니다. 이것은 프로그램입니다 :
f(x) = 2 * x
g(x,y) = x / y
무엇을 먼저 실행해야하는지 어떻게 알 수 있습니까? 우리는 어떻게 함수를 사용하여 정렬 된 순서의 함수 (즉 , 프로그램 ) 를 형성 할 수 있습니까?
솔루션 : 작성 기능 . 당신이 먼저 원하는
g
다음f
, 그냥 작성하십시오f(g(x,y))
. 이런 식으로 “프로그램”도 기능입니다 :main = f(g(x,y))
. 좋습니다만 … -
더 많은 문제 : 일부 기능 이 실패 할 수 있습니다 (예 :
g(2,0)
0으로 나누기). 우리는이 없습니다 에는 “예외” FP의를 (예외가 함수가 아닙니다). 우리는 그것을 어떻게 해결합니까?솔루션 : 함수가 두 가지 종류의 것을 반환하도록 하겠습니다 :
g : Real,Real -> Real
(두 개의 실수에서 실제로 기능하는) 대신 ,g : Real,Real -> Real | Nothing
(두 개의 실수에서 (실제로 또는 아무것도)로) 함수를 허용합시다 . -
그러나 함수는 (단순하게) 한 가지만 반환 해야 합니다.
해결책 : 반환 할 새로운 유형의 데이터, 즉 실제 또는 전혀 아무것도 포함하지 않는 ” boxing type “을 만들어 봅시다 . 따라서 우리는 가질 수 있습니다
g : Real,Real -> Maybe Real
. 좋습니다만 … -
이제 어떻게됩니까
f(g(x,y))
?f
을 (를) 사용할 준비가되지 않았습니다Maybe Real
. 그리고 우리g
는를 사용할 수있는 모든 함수를 변경하고 싶지 않습니다Maybe Real
.솔루션 : “connect”/ “compose”/ “link”기능을위한 특수 기능을 갖도록 합시다 . 이런 식으로, 우리는 뒤에서 한 함수의 출력을 다음 함수를 제공하도록 조정할 수 있습니다.
우리의 경우 :
g >>= f
(connect / composeg
tof
). 우리는 출력>>=
을 얻고g
, 검사하고,Nothing
단지 호출f
하고 반환 하지 않는 경우를 원합니다Nothing
. 또는 반대로, 박스를 추출하여Real
공급f
하십시오. (이 알고리즘은 유형 에>>=
대한 구현 일뿐입니다Maybe
). 또한 “복싱 유형”(다른 상자, 다른 적응 알고리즘) 당 한 번만>>=
작성해야합니다 . -
동일한 패턴을 사용하여 해결할 수있는 다른 많은 문제가 발생합니다. 1. “상자”를 사용하여 다른 의미 / 값을 코드화 / 저장하고
g
이러한 “상자 값”을 반환하는 것과 같은 기능을 갖습니다 . 2. 의 출력을 의 입력에g >>= f
연결하는 데 도움 이되는 작곡가 / 링커 가 있으므로 전혀 변경할 필요가 없습니다 .g
f
f
-
이 기술을 사용하여 해결할 수있는 놀라운 문제는 다음과 같습니다.
-
일련의 기능 ( “프로그램”)에서 모든 기능이 공유 할 수있는 전역 상태를 갖는 경우 : solution
StateMonad
. -
우리는 “불순 함수”를 좋아하지 않습니다 : 동일한 입력에 대해 다른 출력을 생성하는 함수 . 따라서 해당 함수를 표시하여 태그 / 박스 값을 반환합니다 : monad.
IO
-
총 행복!
답변
대답은 물론 “우리는하지 않습니다” 입니다. 모든 추상화와 마찬가지로 필요하지 않습니다.
하스켈은 모나드 추상화가 필요하지 않습니다. 순수한 언어로 IO를 수행 할 필요는 없습니다. 그만큼IO
유형 자체가 잘 처리한다. 기존 모나드 desugaring do
블록에 desugaring로 대체 될 수있다 bindIO
, returnIO
및 failIO
에 정의로서 GHC.Base
모듈. (해커에 관한 문서화 된 모듈이 아니므로 문서화를 위해 소스 를 가리켜 야 합니다.) 따라서 모나드 추상화가 필요하지 않습니다.
필요하지 않다면 왜 존재합니까? 많은 계산 패턴이 모나 딕 구조를 형성한다는 것이 밝혀졌습니다. 구조를 추상화하면 해당 구조의 모든 인스턴스에서 작동하는 코드를 작성할 수 있습니다. 더 간결하게 말하면 코드 재사용.
기능적 언어에서 코드 재사용을위한 가장 강력한 도구는 기능의 구성이었습니다. 좋은 오래된 (.) :: (b -> c) -> (a -> b) -> (a -> c)
운영자는 매우 강력합니다. 최소한의 구문이나 의미상의 오버 헤드로 작은 함수를 작성하고 쉽게 붙일 수 있습니다.
그러나 유형이 제대로 작동하지 않는 경우가 있습니다. 가지고있을 때 무엇을 foo :: (b -> Maybe c)
하고bar :: (a -> Maybe b)
? foo . bar
때문에, 유형 체킹하지 않습니다 b
와 Maybe 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
합니다. 그들은 카테고리 법을 준수하면서 적절한 카테고리를 구성 할 수있는 모든 유형에서 작동합니다.
- 왼쪽 정체성 :
id . f
=f
- 올바른 정체성 :
f . id
=f
- 연관성 :
f . (g . h)
=(f . g) . h
자신의 유형이이 세 가지 법률을 준수한다는 것을 증명할 수있는 한 Kleisli 범주로 바꿀 수 있습니다. 그리고 그에 대한 큰 문제는 무엇입니까? 모나드는 Kleisli 범주와 정확히 같은 것으로 나타났습니다. Monad
‘들 return
Kleisli 동일하다 id
. Monad
의는 (>>=)
Kleisli 동일하지 않은 (.)
,하지만 다른 측면에서 매우 쉽게 쓸 수있는 각 것으로 밝혀졌습니다. 범주 법은 모나드 법과 동일합니다.(>>=)
하고 (.)
.
그렇다면 왜이 모든 귀찮은 일을 겪어야합니까? 왜Monad
언어에 추상화가 있습니까? 위에서 언급했듯이 코드 재사용이 가능합니다. 또한 두 가지 다른 차원에서 코드를 재사용 할 수 있습니다.
코드 재사용의 첫 번째 차원은 추상화의 존재에서 직접 발생합니다. 추상화의 모든 인스턴스에서 작동하는 코드를 작성할 수 있습니다. 의 모든 인스턴스와 작동하는 루프로 구성된 전체 모나드 루프 패키지가 Monad
있습니다.
두 번째 차원은 간접적이지만 구성의 존재로 이어집니다. 구성이 쉬운 경우 재사용이 가능한 작은 덩어리로 코드를 작성하는 것이 당연합니다. 이것은 (.)
함수 연산자를 사용하여 작고 재사용 가능한 함수를 작성 하는 것과 같은 방법 입니다.
그렇다면 왜 추상화가 존재합니까? 코드에서 더 많은 구성을 가능하게하는 도구 인 것으로 입증되었으므로 재사용 가능한 코드가 생성되고 재사용 가능한 코드가 생성됩니다. 코드 재사용은 프로그래밍의 성배 중 하나입니다. 모나드 추상화는 우리를 그 성배쪽으로 조금 움직이기 때문에 존재합니다.
답변
유형 시스템은 프로그램에서 용어의 런타임 동작에 대한 일종의 정적 근사값을 계산하는 것으로 간주 될 수 있습니다.
그렇기 때문에 강력한 형식 시스템을 갖춘 언어가 형편없는 언어보다 엄격하게 표현되는 이유입니다. 같은 방식으로 모나드에 대해 생각할 수 있습니다.
@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이 설명하는 것처럼 모나드의 구성 성으로 인해) 사용자 정의 데이터 유형과 내장 프리미티브를 균일하게 처리 할 수 있습니다.
답변
나는 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 구현은 아니지만 원칙적으로 사용할 수 있습니다.
답변
모나드 는 반복되는 문제를 해결하기위한 편리한 프레임 워크 일뿐입니다. 첫째, 모나드는해야 펑 (즉, 요소 (또는 유형)을 보지 않고 매핑을 지원해야합니다), 그들은 또한 가져와야한다 바인딩 (또는 체인) 운영과 (요소 유형에서 모나드 가치를 창출 할 수있는 방법을 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"]
리스트 모나드는 결코 부작용 계산이 아님에 유의해야한다. 모나드 인 수학적 구조 (즉, 위에서 언급 한 인터페이스와 법칙을 따르는)는 부작용을 암시하지 않지만 부작용을 일으키는 현상은 종종 모나 딕 틀에 잘 맞습니다.
답변
모나드는 기본적으로 체인에서 함수를 함께 구성하는 역할을합니다. 기간.
이제 구성 방식이 기존 모나드에 따라 다르므로 다른 동작 (예 : 상태 모나드에서 변경 가능한 상태 시뮬레이션)이 발생합니다.
모나드에 대한 혼동은 매우 일반적인 것, 즉 함수를 구성하는 메커니즘이기 때문에 많은 것들에 사용될 수 있기 때문에 모나드가 “함수 구성”에 대해서만 상태, IO 등에 관한 것이라고 믿게한다. “.
모나드에 대한 흥미로운 점 중 하나는 컴포지션의 결과가 항상 “M a”유형, 즉 “M”으로 태그가 지정된 봉투 내부의 값이라는 것입니다. 이 기능은 예를 들어 불순 코드와 순수 코드를 명확하게 구분하여 구현하기에 정말 좋습니다. 모든 불순 동작을 “IO a”유형의 함수로 선언하고 IO 모나드를 정의 할 때 아무런 기능도 제공하지 않습니다. “IO a”내부의 값 결과적으로 순수한 함수는 없으며 동시에 “IO a”에서 값을 가져옵니다. 순수한 상태를 유지하면서 값을 취할 방법이 없기 때문입니다 (이 기능은 “IO”모나드 내부에 있어야만 사용할 수 있습니다) 그러한 가치). (참고 : 아무 것도 완벽하지 않으므로 “unsafePerformIO : IO a-> a”를 사용하여 “IO 구속 복귀”를 분리 할 수 있습니다.
답변
타입 생성자 와 그 타입 패밀리의 값을 반환하는 함수 가 있다면 모나드가 필요 합니다 . 결국, 이러한 종류의 기능을 함께 결합하고 싶습니다 . 이것들은 왜 대답 해야하는 세 가지 핵심 요소 입니다.
자세히 설명하겠습니다. 당신은 Int
, String
및 Real
입력의 기능 Int -> String
, String -> Real
그리고에 이렇게. 로 끝나는 이러한 기능을 쉽게 결합 할 수 있습니다 Int -> Real
. 인생은 좋다
그런 다음 언젠가 새로운 유형의 패밀리 를 작성해야합니다 . 값 없음 ( Maybe
), 오류 반환 ( Either
), 여러 결과 ( List
) 등 의 가능성을 고려해야하기 때문일 수 있습니다 .
공지 Maybe
타입 생성자이다. 같은 유형을 취하고 Int
새로운 유형을 반환합니다 Maybe Int
. 가장 먼저 기억해야 할 것은 타입 생성자도없고 모나드도 없다는 것입니다.
물론, 당신은 당신의 타입 생성자를 사용하려면 코드에서, 곧 당신은 같은 기능을 종료 Int -> Maybe String
하고 String -> Maybe Float
. 이제는 기능을 쉽게 결합 할 수 없습니다. 인생은 더 이상 좋지 않습니다.
그리고 모나드가 구조에 올 때입니다. 그들은 당신이 그런 종류의 기능들을 다시 결합 할 수있게합니다. 당신은 단지 구성을 변경해야합니다 . 대 > == .