모나드는 무엇입니까?


1414

최근 Haskell을 간략히 살펴보면 모나드가 무엇인지에 대한 간결하고 간결하며 실용적인 설명은 무엇입니까?

내가 접한 대부분의 설명은 접근하기가 어렵고 실제적인 세부 사항이 부족하다는 것을 알았습니다.


12
Eric Lippert는이 문제에 대한 답변을 작성했습니다 ( stackoverflow.com/questions/2704652/… ). 일부 문제는 별도의 페이지에 있습니다.
P Shved

70
다음은 자바 스크립트를 사용하는 새로운 소개입니다. 매우 읽기 쉽습니다.
Benjol



2
모나드는 헬퍼 조작을 가진 함수의 배열입니다. 이 답변
cibercitizen1

답변:


1059

첫째 : 모나드 라는 용어 는 수학자가 아니라면 약간 공허합니다. 다른 용어는 실제로 유용한 것에 대해 좀 더 설명하는 계산 빌더 입니다.

실용적인 예를 요청하십시오.

예 1 : 목록 이해 :

[x*2 | x<-[1..10], odd x]

이 표현식은 1에서 10 사이의 모든 홀수의 배가를 반환합니다. 매우 유용합니다!

이것은 List 모나드 내의 일부 연산에 대해 실제로 구문 설탕이라는 것이 밝혀졌습니다. 동일한 목록 이해는 다음과 같이 쓸 수 있습니다.

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

또는:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

예 2 : 입력 / 출력 :

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

두 예제 모두 AKA 계산 빌더 인 모나드를 사용합니다. 일반적인 주제는 모나드가 특정적이고 유용한 방식으로 작업연결 한다는 것입니다. 리스트 이해에서, 조작이리스트를 리턴하면리스트의 모든 항목대해 다음 조작이 수행되도록 조작이 연결 됩니다. 반면에 IO 모나드는 순차적으로 작업을 수행하지만 "세계 상태"를 나타내는 "숨겨진 변수"를 전달하여 순수한 기능 방식으로 I / O 코드를 작성할 수 있습니다.

체인 작업 의 패턴 은 매우 유용하며 Haskell의 많은 다른 것들에 사용됩니다.

또 다른 예는 예외입니다. Error모나드를 사용하면 오류가 발생하는 경우를 제외하고는 체인의 나머지 부분을 버리는 경우를 제외하고 작업이 순차적으로 수행되도록 체인이 연결됩니다.

리스트 이해 구문과 do-notation은 모두 >>=연산자를 사용하여 연쇄 연산을위한 구문 설탕입니다 . 모나드는 기본적으로 >>=연산자 를 지원하는 유형입니다 .

예 3 : 파서

이것은 인용 된 문자열이나 숫자를 구문 분석하는 매우 간단한 파서입니다.

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

작업은 char, digit, 등 매우 간단합니다. 일치하거나 일치하지 않습니다. 마술은 제어 흐름을 관리하는 모나드입니다. 일치가 실패 할 때까지 작업이 순차적으로 수행됩니다.이 경우 모나드는 최신으로 되돌아가 <|>다음 옵션을 시도합니다. 또 다른 유용한 의미 체계를 사용하여 작업을 연결하는 방법입니다.

예 4 : 비동기 프로그래밍

위의 예제는 Haskell에 있지만 F # 은 모나드도 지원합니다. 이 예는 Don Syme 에서 도난당했습니다 .

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

이 메소드는 웹 페이지를 가져옵니다. 펀치 라인을 사용합니다 GetResponseAsync-실제로는 별도의 스레드에서 응답을 기다리는 동안 주 스레드는 함수에서 돌아옵니다. 응답이 수신되면 생성 된 스레드에서 마지막 세 줄이 실행됩니다.

대부분의 다른 언어에서는 응답을 처리하는 행에 대해 별도의 함수를 명시 적으로 작성해야합니다. async모나드 자체에 블록 "분할"할 수 있고 후반의 실행을 연기. ( async {}구문은 블록의 제어 흐름이 async모나드에 의해 정의됨을 나타냅니다 .)

작동 방식

그렇다면 모나드는이 모든 멋진 제어 흐름을 어떻게 수행 할 수 있습니까? 실제로 do-block (또는 F #에서 호출 되는 계산 식 )에서 발생하는 것은 모든 작업 (기본적으로 모든 행)이 별도의 익명 함수로 래핑된다는 것입니다. 이 함수는 bind연산자를 사용하여 결합됩니다 ( >>=Haskell에서 철자 ). bind연산은 기능을 결합 하기 때문에 순차적으로, 여러 번, 반대로, 일부를 버리고, 느낌이들 때 별도의 스레드에서 일부를 실행하는 것처럼 적절하다고 생각되면 실행할 수 있습니다.

예를 들어, 이것은 예제 2의 확장 된 IO 코드 버전입니다.

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

이것은 더 나쁘지만 실제로 진행되고있는 것이 더 분명합니다. >>=연산자 마법 성분이다 : 또한, (우측) 함수와 (왼쪽) 값을 결합하여 얻어 새로운 값을 생성한다. 이 새로운 값은 다음 >>=연산자에 의해 다시 취해지고 새로운 값을 생성하는 함수와 다시 결합됩니다. >>=미니 평가자로 볼 수 있습니다.

참고 >>=모든 모나드는 자체 구현이 있으므로, 다른 종류의 오버로드 >>=. (체인의 모든 작업은 동일한 모나드 유형이어야 >>=합니다. 그렇지 않으면 작업자가 작동하지 않습니다.)

가능한 가장 간단한 구현은 >>=왼쪽의 값을 가져 와서 오른쪽의 함수에 적용하고 결과를 반환하지만 이전에 언급했듯이 모나드의 구현에 추가로 진행되는 일이있을 때 전체 패턴을 유용하게 만드는 것은 >>=.

값이 한 작업에서 다음 작업으로 전달되는 방식에는 약간의 영리함이 있지만 Haskell 유형 시스템에 대한 자세한 설명이 필요합니다.

합산

Haskell-terms에서 모나드는 모나드 타입 클래스의 인스턴스 인 매개 변수화 된 타입 >>=으로, 몇몇 다른 연산자와 함께 정의 됩니다. 평신도의 관점에서 모나드는 >>=작업이 정의 된 유형일뿐 입니다.

그 자체로 >>=는 귀찮은 연쇄 함수 방식이지만 "배관"을 숨기는 do-notation의 존재로 monadic 연산은 매우 훌륭하고 유용한 추상화이며 언어의 많은 곳에서 유용하며 유용합니다. 언어로 자신의 미니 언어를 만들 수 있습니다.

모나드는 왜 어려운가요?

많은 Haskell-learners에게 모나드는 벽돌 벽처럼 치는 장애물입니다. 모나드 자체가 복잡하지는 않지만 구현은 매개 변수화 된 유형, 유형 클래스 등과 같은 다른 많은 고급 Haskell 기능에 의존합니다. 문제는 Haskell I / O가 모나드를 기반으로한다는 것이며, I / O는 아마도 새로운 언어를 배울 때 가장 먼저 이해하고 싶을 것입니다. 결국, 전혀 생산하지 않는 프로그램을 만드는 것은 그리 재미 있지 않습니다. 산출. 다른 언어 부분에 대한 경험이 충분할 때까지 I / O를 "매직 발생"과 같이 처리하는 것을 제외하고는이 닭과 계란 문제에 대한 즉각적인 해결책은 없습니다. 죄송합니다.

모나드에 대한 훌륭한 블로그 : http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


65
모나드를 이해하는 데 많은 어려움을 겪은 사람으로서이 답변이 도움이되었다고 말할 수 있습니다. 그러나 여전히 이해하지 못하는 것이 있습니다. 목록 이해는 어떤 방식으로 모나드입니까? 그 예의 확장 된 형태가 있습니까? 이것을 포함하여 대부분의 모나드 설명에 대해 정말로 귀찮게하는 또 다른 것은 "모나드 란 무엇인가?" "모나드가 좋은 이유는 무엇입니까?" "모나드는 어떻게 구현됩니까?" "모나드는 기본적으로 >> = 연산자를 지원하는 유형입니다."라고 쓸 때 상어를 뛰어 넘었습니다. 방금 나에게 ...
Breton

82
또한 왜 모나드가 어려운지에 대한 당신의 결론에 동의하지 않습니다. 모나드 자체가 복잡하지 않은 경우 수하물이없는 것을 설명 할 수 있습니다. "모나드 란 무엇인가"라는 질문을 할 때 구현에 대해 알고 싶지 않습니다. 스크래치가 어떤 가려움증인지 알고 싶습니다. 지금까지의 대답은 "하스켈의 저자는 사도 마조히스트이며, 간단한 일을하기 위해 어리석은 일을해야한다고 결정했기 때문에, 모나드가 하스켈을 사용하는 방법을 배우는 데 도움이되지 않았습니다. 스스로 "...
Breton

69
하지만 .. 맞지 않나요? 아무도 모나드 구현이 어려워서 아무도 설명 할 방법을 모를 수 없기 때문에 모나드는 어렵다고 생각합니다. 스쿨 버스 란? 전면에 장치가있는 금속 플랫폼으로 일부 금속 피스톤을 주기적으로 구동하기 위해 세련된 석유 제품을 소비하고 일부는 휠을 구동하는 기어에 연결된 크랭크 샤프트를 회전시킵니다. 바퀴 주위에는 고무 백이 부풀어있어 재편 표면과 연결되어 시트 모음이 앞으로 움직입니다. 좌석은 앞으로 이동합니다 ...
Breton

130
나는이 모든 것을 읽었으며, 하스켈 프로그래머가 설명하기에 충분히 이해하지 못한다는 사실 외에는 모나드가 무엇인지 여전히 모른다. 모나드없이 수행 할 수있는 모든 것을 고려할 때 예제는별로 도움이되지 않으며,이 답변을 통해 모나드가 더 쉽고 더 혼란스럽게 만드는 방법을 명확하게 알 수는 없습니다. 이 답변의 유용한 부분 중 하나는 예제 # 2의 구문 설탕이 제거 된 부분이었습니다. 첫 번째 줄을 제외하고는 확장이 원본과 실제로는 비슷하지 않기 때문에 가까이 왔다고 말합니다.
Laurence Gonsalves

81
모나드에 대한 설명에 고유 한 것으로 보이는 또 다른 문제는 그것이 하스켈로 작성되었다는 것입니다. 나는 Haskell이 나쁜 언어라고 말하는 것이 아니라 모나드를 설명하기에는 나쁜 언어라고 말하는 것입니다. Haskell을 알고 있다면 모나드를 이미 이해하고 있기 때문에 모나드를 설명하려면 모나드를 모르는 사람들이 이해하기 쉬운 언어를 사용하십시오. 당신이 경우에 있어야 당신이 할 수있는 언어의 가장 작은, 간단한 부분 집합을 사용하고, 하스켈 IO의 이해를 가정하지 마십시오 - 하스켈을 사용하여 모든의 문법 설탕을 사용하지 마십시오.
Laurence Gonsalves

712

"모나드 란 무엇인가"를 설명하는 것은 "숫자는 무엇입니까?" 우리는 항상 숫자를 사용합니다. 그러나 숫자에 대해 전혀 모르는 사람을 만났다고 상상해보십시오. 어떻게 도대체 당신은 숫자가 무엇인지 설명 할 것인가? 그리고 왜 그것이 유용한 지 설명하기 시작 하시겠습니까?

모나드는 무엇입니까? 짧은 대답 : 그것은 작업을 연결하는 구체적인 방법입니다.

본질적으로 실행 단계를 작성하고이를 "바인드 기능"과 연결합니다. (Haskell에서 이름은으로 지정 >>=됩니다.) 바인드 연산자에 직접 호출을 작성하거나 컴파일러가 해당 함수 호출을 삽입하게하는 구문 sugar를 사용할 수 있습니다. 그러나 어느 쪽이든, 각 단계는이 바인드 함수에 대한 호출로 분리됩니다.

따라서 바인드 함수는 세미콜론과 같습니다. 프로세스의 단계를 분리합니다. 바인드 기능의 작업은 이전 단계에서 출력을 가져 와서 다음 단계로 공급하는 것입니다.

너무 힘들게 들리지 않습니까? 그러나 하나 이상의 모나드가 있습니다. 왜? 어떻게?

바인드 함수 한 단계에서 결과를 가져 와서 다음 단계로 전달할 수 있습니다. 그러나 그것이 "모두"라면 모나드는 그렇습니다 ... 실제로는 그다지 유용하지 않습니다. 이해하는 것이 중요합니다. 모든 유용한 모나드는 모나드 일뿐 아니라 다른 일도합니다 . 모든 유용한 모나드는 "특별한 힘"을 가지고있어 독특합니다.

( 특별히 아무 것도 하지 않는 모나드 는 "identity monad"라고 불립니다. 신원 기능과는 달리이 기능은 전혀 무의미한 것처럼 들리지만 그렇지는 않습니다 ... 그러나 그것은 또 다른 이야기입니다.)

기본적으로 각 모나드는 자체 바인딩 기능을 구현합니다. 그리고 실행 단계 사이에서 후프를 수행하도록 바인드 함수를 작성할 수 있습니다. 예를 들면 다음과 같습니다.

  • 각 단계가 성공 / 실패 표시기를 리턴하면, 이전 단계가 성공한 경우에만 다음 단계를 바인드하도록 바인드 할 수 있습니다. 이런 식으로 실패한 단계는 조건부 테스트없이 전체 시퀀스를 "자동으로"중단합니다. ( 실패 모나드 .)

  • 이 아이디어를 확장하면 "예외"를 구현할 수 있습니다. ( Error Monad 또는 Exception Monad .) 언어 기능이 아니라 직접 정의하기 때문에 작동 방식을 정의 할 수 있습니다. (예를 들어, 처음 두 예외를 무시하고 세 번째 예외가 발생할 때만 중단하려고 할 수 있습니다 .)

  • 각 단계에서 여러 개의 결과를 반환하도록 하고 bind 함수가 그 결과를 반복하도록하여 각 단계를 다음 단계로 전달할 수 있습니다. 이러한 방식으로 여러 결과를 처리 할 때 여러 곳에 루프를 계속 쓸 필요가 없습니다. 바인드 기능은 "자동으로"모든 기능을 수행합니다. ( 목록 Monad .)

  • 한 단계에서 다른 단계로 "결과"를 전달하는 것 외에도 바인드 함수 가 추가 데이터전달 하도록 할 수 있습니다 . 이 데이터는 이제 소스 코드에 표시되지 않지만 수동으로 모든 함수에 전달하지 않고도 어디서나 액세스 할 수 있습니다. ( 독자 모나드 .)

  • "추가 데이터"를 교체 할 수 있습니다. 이를 통해 실제로 파괴적인 업데이트를 수행하지 않고도 파괴적인 업데이트시뮬레이션 할 수 있습니다 . ( 주 모나드 와 그 사촌 작가 모나드 .)

  • 파괴적인 업데이트 만 시뮬레이션 하기 때문에 실제 파괴적인 업데이트 로는 불가능한 일을 간단하게 수행 할 수 있습니다. 예를 들어 마지막 업데이트를 실행 취소 하거나 이전 버전으로 되돌릴 수 있습니다.

  • 계산을 일시 중지 할 수있는 모나드를 만들 수 있으므로 프로그램을 일시 중지하고 내부 상태 데이터로 들어가서 땜질 한 다음 다시 시작할 수 있습니다.

  • "연속"을 모나드로 구현할 수 있습니다. 이를 통해 사람들의 마음아프게 할 수 있습니다 !

이 모든 것이 모나드로 가능합니다. 물론이 모든 것도 모나드 없이도 완벽하게 가능합니다 . 그것은 크게 그냥 쉽게 모나드를 사용하여.


13
귀하의 답변에 감사드립니다. 특히 모나드 없이도이 모든 것이 물론 가능하다는 마지막 양보입니다. 한 가지 요점은 모나드를 사용 하는 것이 대부분 쉽지만,이를 사용하지 않는 것보다 효율적이지 않은 경우가 많다는 것입니다. 트랜스포머가 필요하면 함수 호출 (및 함수 객체 생성)의 추가 계층화를 통해 제어 및 제어하기 어려운 비용이 발생하며 영리한 구문으로 보이지 않습니다.
seh

1
Haskell에서는 최소한 모나드의 오버 헤드가 대부분 옵티 마이저에 의해 제거됩니다. 따라서 유일한 "비용"은 필요한 뇌의 힘입니다. ( "유지 관리 성"이 관심이 있다면 이것은 중요하지 않습니다.) 그러나 일반적으로 모나드는 더 어렵지 않고 일을 더 쉽게 만듭니다 . (그렇지 않으면 왜 귀찮게하겠습니까?)
MathematicalOrchid

Haskell이 이것을 지원하는지 확실하지 않지만 수학적으로 >> = 및 return 또는 join 및 ap로 모나드를 정의 할 수 있습니다. >> = 및 return은 모나드를 실제로 유용하게 만드는 요소이지만 join과 ap는 모나드가 무엇인지에 대한 직관적 인 이해를 제공합니다.
Jeremy 목록

15
수학적이지 않고 기능적이지 않은 프로그래밍 배경에서 나온이 답변은 나에게 가장 적합했습니다.
jrahhali

10
이것은 실제로 모나드가 무엇인지에 대한 아이디어를 준 첫 번째 대답입니다. 설명 할 방법을 찾아 주셔서 감사합니다!
robotmay

186

사실, 모나드에 대한 일반적인 이해와는 달리, 국가와는 아무런 관련이 없습니다. 모나드는 단순히 물건을 포장하는 방법이며 포장을 풀지 않고 포장 된 물건에 대한 작업을 수행하는 방법을 제공합니다.

예를 들어 Haskell에서 다른 유형을 감싸는 유형을 만들 수 있습니다.

data Wrapped a = Wrap a

우리가 정의한 것을 감싸기 위해

return :: a -> Wrapped a
return x = Wrap x

풀기없이 작업을 수행하려면 함수가 있다고 가정 해 f :: a -> b다음이 작업을 수행 할 수 있습니다, 들어 포장 값에 따라 행동하는 그 기능을 :

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

이해해야 할 모든 것입니다. 그러나,이 할 수있는 일반적인 기능이 있음을 밝혀 리프팅 입니다 bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bind보다 조금 더 할 수 fmap있지만 그 반대는 아닙니다. 실제로 및 fmap로만 정의 할 수 있습니다 . 그래서, 모나드를 정의 할 때 .. 당신은 그것의 타입 (여기서)을주고 그 동작 과 작동 방식을 말합니다 .bindreturnWrapped areturnbind

멋진 점은 이것이 일반적인 패턴으로 판명되어 순수한 방식으로 상태를 캡슐화하는 것은 그중 하나 일 뿐이라는 것입니다.

Haskell의 IO 모나드에서 사용되는 것과 같이 모나드를 사용하여 기능적 종속성을 도입하고 평가 순서를 제어하는 ​​방법에 대한 좋은 기사를 보려면 IO Inside를 확인하십시오 .

모나드를 이해하는 것에 대해서는 너무 걱정하지 마십시오. 당신이 흥미로운 것을 발견하고 바로 이해하지 않아도 걱정하지 마십시오. 그렇다면 Haskell과 같은 언어로 다이빙하는 것이 좋습니다. 모나드는 연습을 통해 뇌 속으로 간계를 이해하는 순간, 언젠가 갑자기 이해한다는 사실을 깨닫게됩니다.


->는 오른쪽 연결, 미러링 기능 응용 프로그램으로 왼쪽 연결이므로 괄호를 생략해도 아무런 차이가 없습니다.
Matthias Benkard

1
나는 이것이 매우 좋은 설명이라고 생각하지 않습니다. 모나드는 단순히 길입니까? 좋아, 어느 쪽? 왜 모나드 대신 클래스를 사용하여 캡슐화하지 않습니까?
Breton

4
@ mb21 : 괄호가 너무 많다고 지적하는 경우 a-> b-> c는 실제로 a-> (b-> c)의 짧은 것입니다. 이 특정 예제를 (a-> b)-> (Ta-> Tb)로 쓰는 것은 불필요한 문자를 추가하는 것만으로 엄격하게 말하지만 fmap은 a-> 유형의 함수를 매핑한다는 것을 강조하기 때문에 도덕적으로 "해야 할 일"입니다 b-Ta-> Tb 유형의 함수 그리고 이것이 원래 범주 이론에서 펑터가하는 일이며 모나드가 유래 한 곳입니다.
Nikolaj-K

1
이 답변은 잘못된 것입니다. 일부 모나드는 "래퍼 (wrapper)"를 갖지 않습니다. 이러한 값은 고정 값입니다.

1
@ DanMandel Monads는 자체 데이터 유형 래퍼를 제공하는 디자인 패턴입니다. Monad는 상용구 코드를 추상화하는 방식으로 설계되었습니다. 따라서 코드에서 Monad를 호출하면 걱정하지 않으려는 장면 뒤에서 수행됩니다. Nullable <T> 또는 IEnumerable <T>에 대해 생각해보십시오. 장면 뒤에서 무엇을합니까? 모나스
sksallaj

168

그러나, 당신은 Monads를 발명했을 수 있습니다!

sigfpe 말한다 :

그러나이 모든 것은 모나드를 설명이 필요한 난해한 것으로 소개합니다. 그러나 내가 주장하고 싶은 것은 전혀 비전 적이 지 않다는 것입니다. 실제로 함수형 프로그래밍에서 다양한 문제에 직면했을 때 모나드의 예인 특정 솔루션으로 이어질 수밖에 없었습니다. 사실, 아직 개발하지 않았다면 지금 발명하도록하겠습니다. 그런 다음 이러한 모든 솔루션이 실제로 동일한 솔루션이라는 것을 알 수있는 작은 단계입니다. 이 글을 읽은 후에는 이미 발명 한 것으로 보이는 모든 것을 인식 할 수 있기 때문에 모나드에 대한 다른 문서를 이해하기에 더 나은 위치에있을 수 있습니다.

모나드가 해결하려고하는 많은 문제는 부작용 문제와 관련이 있습니다. 먼저 시작하겠습니다. 모나드를 사용하면 부작용을 처리하는 것 이상을 수행 할 수 있습니다. 특히 많은 유형의 컨테이너 객체를 모나드로 볼 수 있습니다. 다른 사람.)

C ++과 같은 명령형 프로그래밍 언어에서 함수는 수학 함수와 같이 동작하지 않습니다. 예를 들어, 단일 부동 소수점 인수를 사용하고 부동 소수점 결과를 리턴하는 C ++ 함수가 있다고 가정하십시오. 피상적으로는 실수를 실수에 매핑하는 수학 함수처럼 보일 수 있지만 C ++ 함수는 인수에 따라 숫자를 반환하는 것 이상을 수행 할 수 있습니다. 전역 변수의 값을 읽고 쓸 수있을뿐만 아니라 화면에 출력을 기록하고 사용자로부터 입력을받을 수 있습니다. 그러나 순수한 기능적 언어에서 함수는 인수로 제공된 내용 만 읽을 수 있으며 세상에 영향을 줄 수있는 유일한 방법은 반환되는 값을 통하는 것입니다.


9
… 인터넷뿐만 아니라 어디에서나 가장 좋은 방법입니다. ( 아래 답변에서 언급 한 함수형 프로그래밍 을 위한와 들러의 오리지널 논문 모나드 (Monads )도 좋습니다.)
ShreevatsaR

13
Sigfpe의 게시물 에 대한 이 JavaScript 번역은 고급 Haskell을 아직 이해하지 못한 사람들을 위해 모나드를 배우는 가장 좋은 새로운 방법입니다!
Sam Watkins

1
이것이 모나드가 무엇인지 알게 된 방법입니다. 개념을 발명하는 과정을 독자에게 안내하는 것이 종종 개념을 가르치는 가장 좋은 방법입니다.
Jordan

그러나 화면 개체를 인수로 받아들이고 텍스트를 수정하여 복사본을 반환하는 함수는 순수합니다.
Dmitri Zaitsev 2016 년

87

모나드는 >>=(aka bind) 및 return(aka unit)의 두 가지 연산이있는 데이터 유형입니다 . return임의의 값을 가져와 모나드 인스턴스를 만듭니다. >>=모나드의 인스턴스를 가져 와서 그 위에 함수를 매핑합니다. (모나드는 대부분의 프로그래밍 언어에서 임의의 값을 취하고 그로부터 유형을 생성하는 함수를 작성할 수 없기 때문에 모나드는 이상한 종류의 데이터 유형임을 이미 알 수 있습니다. 모나드는 일종의 파라 메트릭 다형성을 사용 합니다.)

Haskell 표기법에서 모나드 인터페이스가 작성됩니다

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

이러한 작업은 특정 "법률"을 준수하기로되어 있지만,이 굉장히 중요하지 같습니다 "법률"단지 작업의 방법 분별 구현은 기본적으로 (행동한다고 성문화, 그 >>=와는 return값이 모나드 인스턴스와로 변환 얻을 방법에 대해 동의한다고 그것은 >>=연관성이 있습니다).

모나드는 상태와 I / O에 관한 것이 아니라 상태, I / O, 예외 및 비결정론에 대한 작업을 포함하는 일반적인 계산 패턴을 추상화합니다. 아마도 가장 간단한 모나드는 목록과 옵션 유형입니다.

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

여기서 []:목록 생성자이며, ++연결 연산자이며, JustNothing이다 Maybe생성자. 이 모나드는 둘 다 공통적이고 유용한 계산 패턴을 각 데이터 유형에 캡슐화합니다 (부작용이나 I / O와 관련이 없음).

모나드가 무엇인지, 왜 유용한 지 이해하려면 사소하지 않은 Haskell 코드를 작성해야합니다.


"함수 위에 함수를 매핑"한다는 것은 정확히 무엇을 의미합니까?
Casebash

Casebash, 소개에 의도적으로 비공식적입니다. "함수 맵핑"에 수반되는 의미를 얻으려면 끝 부분에있는 예제를 참조하십시오.
Chris Conway

3
Monad는 데이터 유형이 아닙니다. 함수 구성 규칙 : stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

@DmitriZaitsev가 맞습니다. Monads는 실제로 자체 데이터 데이터 형식을 제공합니다. Monads는 데이터 형식이
아닙니다

78

먼저 functor가 무엇인지 이해해야합니다. 그 전에 고차 함수를 이해하십시오.

고차 함수는 단순히 인자로서의 기능을 취하는 함수이다.

펑는 임의의 타입 구조이다 T고차 함수가 존재하는, 호출 map, 그 변환 타입의 함수 a -> b(임의의 두 유형을 지정 a하고 b함수로) T a -> T b. 이 map함수는 또한 정체성과 구성의 법칙을 준수하여 다음 표현이 모두에게 적용되도록 p하고 q(Haskell 표기법) :

map id = id
map (p . q) = map p . map q

예를 들어, 위의 법칙을 준수하는 List유형의 함수가 장착 된 경우 , 유형 생성자라는 함수는 functor (a -> b) -> List a -> List b입니다. 실질적인 구현은 명백합니다. 결과 List a -> List b함수는 주어진 목록을 반복 (a -> b)하여 각 요소에 대한 함수를 호출 하고 결과 목록을 리턴합니다.

모나드는 본질적으로 펑터이다 T이 추가 방법과, join입력의 T (T a) -> T a, 그리고 unit(라고도 return, fork또는 pure유형) a -> T a. Haskell의 목록 :

join :: [[a]] -> [a]
pure :: a -> [a]

왜 유용한가요? 예를 들어, map목록을 반환하는 함수가있는 목록 위에 있을 수 있기 때문 입니다. Join결과 목록을 가져 와서 연결합니다. List이것이 가능하기 때문에 모나드입니다.

map그런 다음 함수를 작성할 수 있습니다 join. 이 기능을 bind, 또는 flatMap, 또는 (>>=), 또는이라고 (=<<)합니다. 이것은 일반적으로 Haskell에서 모나드 인스턴스가 제공되는 방식입니다.

모나드는 특정 법, 즉 관련 법을 충족시켜야 join합니다. 당신이 값이있는 경우 있음이 수단 x유형을 [[[a]]]다음 join (join x)과 같아야한다 join (map join x). 그리고 그런 pure정체성을 가져야합니다 .joinjoin (pure x) == x


3
'고차 함수'의 def에 약간의 추가 : OR RETURN 함수를 취할 수 있습니다. 그렇기 때문에 그들은 자신들이 스스로 일을 '높은'것입니다.
Kevin Won

9
이 정의에 따르면 덧셈은 고차 함수입니다. 숫자를 가져 와서 그 숫자를 다른 숫자에 더하는 함수를 반환합니다. 따라서 고차 함수는 도메인이 함수로 구성되는 함수입니다.
Apocalisp

비디오 ' Brian Beckman : Monad를 두려워하지 마십시오 '는 동일한 논리를 따릅니다.
icc97

48

[면책 조항 : 나는 여전히 모나드를 완전히 구르려고합니다. 다음은 내가 지금까지 이해 한 것입니다. 그것이 틀렸다면, 잘 알고있는 누군가가 카펫 위에서 나를 부를 것입니다.]

Arnar는 다음과 같이 썼습니다.

모나드는 단순히 물건을 포장하는 방법이며 포장을 풀지 않고 포장 된 물건에 대한 작업을 수행하는 방법을 제공합니다.

바로 그거야. 아이디어는 다음과 같습니다.

  1. 당신은 어떤 종류의 가치를 취하고 그것을 추가 정보로 포장합니다. 값이 특정 종류 (예 : 정수 또는 문자열) 인 것처럼 추가 정보는 특정 종류입니다.

    예를 들어, 추가 정보는 a Maybe또는 a 일 수 있습니다 IO.

  2. 그런 다음 추가 정보를 전달하면서 랩핑 된 데이터를 조작 할 수있는 일부 연산자가 있습니다. 이 연산자는 추가 정보를 사용하여 랩핑 된 값에 대한 작업 동작을 변경하는 방법을 결정합니다.

    예를 들어 a Maybe IntJust Int또는 일 수 있습니다 Nothing. 이제 당신이 추가하면, Maybe IntA와 Maybe Int운영자들이 모두 있는지 확인합니다 Just Int내부이야, 그렇게되면, 랩을 해제 Int,의를 그들에게 추가 연산자를 통과, 결과를 재 - 포장 Int새로운으로 Just Int유효한 인 ( Maybe Int)를 반환하여 a를 반환합니다 Maybe Int. 그러나 그중 하나가 Nothing내부에 있으면이 연산자는 즉시 반환 Nothing합니다 Maybe Int. 이는 다시 유효합니다 . 그런 식으로, 당신은 Maybe Ints가 단지 정상적인 숫자 인 척하고 정규 수학을 수행 할 수 있습니다. 만약 당신이을 얻는다면 Nothing, 당신의 방정식은 여전히 모든 곳에서 점검을하지 않아도Nothing 올바른 결과를 만들어 낼 것 입니다.

그러나 그 예는 단지 일어나는 일이다 Maybe. 추가 정보가 IO이면 IOs에 대해 정의 된 해당 특수 연산자 가 대신 호출되며 추가를 수행하기 전에 완전히 다른 작업을 수행 할 수 있습니다. (좋아요, 두 개의 IO Ints를 함께 추가 하는 것은 아마도 의미가 없습니다. 아직 확실하지 않습니다.) (또한 Maybe예제에 주의를 기울인다면 “추가적인 것들로 가치를 싸는 것”이 항상 올바른 것은 아니라는 것을 알았습니다. 정확하고 정확하며 정확해야합니다.)

기본적으로 "monad"는 대략 "pattern"을 의미 합니다. 그러나 비공식적으로 설명되고 구체적으로 명명 된 Patterns로 가득 찬 책 대신, 이제 새로운 구문을 프로그램에서 물건 으로 선언 할 수 있는 언어 구성 ( 구문 및 모두)이 있습니다 . (여기서 부정확 한 점은 모든 패턴이 특정 형태를 따라야한다는 것이므로 모나드는 패턴만큼 일반적이지 않습니다. 그러나 이것이 대부분의 사람들이 알고 이해하는 가장 가까운 용어라고 생각합니다.)

이것이 사람들이 모나드를 매우 혼란스럽게 생각하는 이유입니다. 왜냐하면 그들은 일반적인 개념이기 때문입니다. 무엇을 모나드로 만드는지 묻는 것은 패턴을 만드는 것이 무엇인지 묻는 것과 유사합니다.

그러나 패턴에 대한 아이디어를 언어로 구문 적으로 지원한다는 의미를 생각해보십시오. Gang of Four 책 을 읽고 특정 패턴의 구성을 암기하는 대신 이 패턴을 불가지론 적으로 구현하는 코드를 작성 하면됩니다 . 한 번 일반적인 방법으로 완료되면! 그런 다음 방문자 나 전략 또는 Façade와 같은 패턴을 반복해서 다시 구현하지 않고도 코드의 작업을 장식하여 재사용 할 수 있습니다!

그래서 모나드 를 이해하는 사람들이 그렇게 유용하다고 생각하는 이유 는 지적 상식이 이해에 대해 자부심을 갖는 상아탑 개념은 아니지만 (물론 티히) 물론 실제로 코드를 더 단순하게 만듭니다.


12
때때로 당신과 같은 "학습자"의 설명은 전문가의 설명보다 다른 학습자와 더 관련이 있습니다. 학습자 모두 :) 생각
아드리안

무언가를 모나드로 만드는 것은 type 함수의 존재입니다 M (M a) -> M a. 이것을 유형 중 하나로 바꿀 수 있다는 사실이 M a -> (a -> M b) -> M b유용합니다.
제레미 목록

"monad"는 대략 "pattern"을 의미합니다.
감사합니다

44

많은 노력을 기울인 끝에 마침내 모나드를 이해했다고 생각합니다. 압도적으로 가장 많이 투표 된 답변에 대한 긴 비평을 다시 읽은 후이 설명을 제공하겠습니다.

모나드를 이해하려면 3 가지 질문에 답해야합니다.

  1. 왜 모나드가 필요합니까?
  2. 모나드는 무엇입니까?
  3. 모나드는 어떻게 구현됩니까?

원래 의견에서 언급했듯이 3 번 질문에서 너무 많은 모나드 설명이 실제로 2 번 질문이나 1 번 질문을 다루지 않고 제대로 다루지 않고 잡 힙니다.

왜 모나드가 필요합니까?

Haskell과 같은 순수 기능 언어는 C 또는 Java와 같은 명령형 언어와 다릅니다. 순수 기능 프로그램은 반드시 한 번에 한 단계 씩 특정 순서로 실행될 필요는 없습니다. Haskell 프로그램은 수학 함수와 더 유사하며, 잠재적 인 순서로 "수식"을 풀 수 있습니다. 이는 여러 가지 이점을 제공하는데, 그 중에서도 특정 상태의 버그, 특히 "상태"와 관련된 버그가 발생할 가능성을 제거합니다.

그러나 이러한 스타일의 프로그래밍으로 해결하기가 쉽지 않은 특정 문제가 있습니다. 콘솔 프로그래밍 및 파일 I / O와 같은 일부 작업은 특정 순서로 발생하거나 상태를 유지해야합니다. 이 문제를 해결하는 한 가지 방법은 계산 상태를 나타내는 일종의 객체와 상태 객체를 입력으로 사용하여 새로운 수정 된 상태 객체를 반환하는 일련의 함수를 만드는 것입니다.

콘솔 화면의 상태를 나타내는 가상의 "상태"값을 만들어 봅시다. 이 값을 정확히 구성하는 방법은 중요하지 않지만 현재 화면에 표시되는 내용을 나타내는 바이트 길이 ASCII 문자 배열과 의사 코드에서 사용자가 입력 한 마지막 입력 행을 나타내는 배열이라고 가정 해 봅시다. 콘솔 상태를 가져 와서 수정하고 새로운 콘솔 상태를 반환하는 함수를 정의했습니다.

consolestate MyConsole = new consolestate;

따라서 콘솔 프로그래밍을 수행하려면 순수한 기능 방식으로 많은 함수 호출을 서로 중첩시켜야합니다.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

이러한 방식으로 프로그래밍하면 "순수한"기능적 스타일을 유지하면서 콘솔 변경이 특정 순서로 발생하도록합니다. 그러나 위의 예와 같이 한 번에 몇 가지 작업 이상을 수행하고 싶을 것입니다. 그런 식으로 중첩 함수는 바람직하지 않게 시작됩니다. 우리가 원하는 것은 본질적으로 위와 동일한 것을 수행하지만 다음과 같이 조금 더 작성된 코드입니다.

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

실제로 작성하는 것이 더 편리한 방법입니다. 그래도 어떻게합니까?

모나드는 무엇입니까?

해당 유형 consolestate에서 작동하도록 특별히 설계된 여러 함수와 함께 정의한 유형 (예 :)이 :있으면 자동으로 (바인드) 와 같은 연산자를 정의하여 이러한 전체 패키지를 "모 노드"로 전환 할 수 있습니다. 피드는 왼쪽의 값을 오른쪽의 함수 매개 변수와 lift일반 함수를 변환하는 연산자를 특정 종류의 바인드 연산자와 함께 작동하는 함수로 리턴합니다 .

모나드는 어떻게 구현됩니까?

다른 답변을 참조하십시오. 자세한 내용으로 뛰어들 수 있습니다.


시퀀싱 만이 모나드를 정의하는 유일한 이유는 아닙니다. 모나드는 바인딩과 리턴 기능을 가진 모든 functor입니다. 바인드 및 리턴은 시퀀싱을 제공합니다. 그러나 그들은 다른 것들도 제공합니다. 또한 좋아하는 명령 언어는 사실상 OO 클래스가있는 멋진 IO 모나드입니다. 모나드를 쉽게 정의한다는 것은 인터프리터 패턴을 사용하기 쉽다는 것을 의미합니다. dsl을 모나드로 정의하고 해석하십시오!
nomen


38

몇 년 전에이 질문에 대한 답변을 한 후, 나는 그 응답을 개선하고 단순화 할 수 있다고 생각합니다 ...

모나드는 작성 중 입력 bind을 사전 처리하기 위해 작성 기능을 사용하여 일부 입력 시나리오에 대한 처리를 외부화하는 기능 작성 기술입니다 .

정상적인 구성에서, 함수 compose (>>)는 구성된 함수를 이전의 결과에 순서대로 적용하는 데 사용됩니다. 중요하게, 구성되는 기능은 입력의 모든 시나리오를 처리해야합니다.

(x -> y) >> (y -> z)

이 디자인은 관련 상태를보다 쉽게 ​​조사 할 수 있도록 입력을 재구성함으로써 개선 될 수 있습니다. 따라서 단순히 y가치 대신에 Mb예를 들어 유효성 개념이 포함 된 (is_OK, b)경우 와 같이 값이 될 수 있습니다 y.

예를 들어, 입력이 숫자 일 가능성이있는 경우 숫자를 포함하거나 포함하지 않을 수있는 문자열을 반환하는 대신 bool유효한 숫자와 튜플과 같은 숫자가 있음을 나타내는 형식으로 형식을 재구성 할 수 bool * float있습니다. 작성된 함수는 더 이상 숫자가 존재하는지 여부를 판별하기 위해 입력 문자열을 구문 분석 할 필요가 없지만 bool튜플 의 일부만 검사 할 수 있습니다 .

(Ma -> Mb) >> (Mb -> Mc)

여기서도 구성이 자연스럽게 발생 compose하므로 각 기능은 입력의 모든 시나리오를 개별적으로 처리해야하지만 훨씬 쉽습니다.

그러나 시나리오 처리가 일상적인 시간 동안 심문 노력을 구체화 할 수 있다면 어떨까요? 예를 들어, 우리의 프로그램은 아무것도하지 않는 어떤 경우에 입력 할 때 같이 확인을하지 않을 때 is_OK입니다 false. 만약 그렇게 되었다면, 작성된 함수는 그 시나리오 자체를 처리 할 필요가 없으며, 코드를 극적으로 단순화하고 다른 수준의 재사용에 영향을 미칩니다.

이 외부화를 달성 bind (>>=)하기 위해, composition대신 을 수행 하는 함수를 사용할 수 있습니다 compose. 따라서 단순히 한 함수의 출력에서 ​​다른 함수의 입력으로 값을 전송하는 대신 일부를 Bind검사 하여 구성된 함수를에 적용할지 여부와 방법을 결정합니다 . 물론 기능 은 구조를 검사하고 원하는 유형의 응용 프로그램을 수행 할 수 있도록 특별히 정의되었습니다 . 그럼에도 불구하고 응용 프로그램이 필요한 응용 프로그램을 결정할 때 구성되지 않은 기능에 검사되지 않은 항목을 전달하기 때문에 무엇이든 될 수 있습니다 . 또한 작성된 기능 자체는 더 이상MMaabindMabindaM입력 구조의 일부를 단순화하여 그 후...

(a -> Mb) >>= (b -> Mc) 간결하게 Mb >>= (b -> Mc)

요컨대, 모나드는 외부화되어 입력이 충분히 노출되도록 설계되면 특정 입력 시나리오의 처리에 대한 표준 동작을 제공합니다. 이 디자인은 shell and content쉘에 구성된 기능의 적용과 관련된 데이터가 포함되어 있으며 해당 기능 만 조사하여 사용할 수있는 모델 bind입니다.

따라서 모나드는 세 가지입니다.

  1. M모나드 관련 정보를 담기 위한 쉘
  2. bind작성된 함수를 쉘 내에서 찾은 컨텐츠 값에 적용 할 때이 쉘 정보를 사용하도록 구현 된 함수
  3. a -> Mb모나드 관리 데이터를 포함하는 결과를 생성 하는 형태의 구성 가능한 기능 .

일반적으로 함수에 대한 입력은 오류 조건과 같은 것들을 포함 할 수있는 출력보다 훨씬 제한적입니다. 따라서 Mb결과 구조는 일반적으로 매우 유용합니다. 예를 들어, 나누기 연산자는 제수가 0 일 때 숫자를 반환하지 않습니다 0.

또한 monads에는 적용 후 결과를 래핑하여 값을 a모나드 유형으로, 랩핑 함수를 Ma일반 함수, a -> b모나드 함수 a -> Mb로 랩핑하는 랩 함수가 포함될 수 있습니다 . 물론 bind, 이러한 랩 기능은 다음과 같습니다 M. 예를 들면 :

let return a = [a]
let lift f a = return (f a)

bind기능 의 설계는 불변의 데이터 구조와 다른 것들이 복잡 해져서 보장 할 수없는 순수한 기능을 가정합니다. 따라서 수도원 법이 있습니다.

주어진...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

그때...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativity적용 bind시기와 상관없이 평가 순서 를 유지합니다 bind. 즉, 정의에 Associativity괄호의 힘, 상기 초기 평가 bindingfg만 예상하는 기능을 초래할 것이다 Ma순서가 완료 할 bind. 따라서 평가는 Ma그 가치가 적용 f되고 그 결과 가 적용되기 전에 결정되어야한다 g.


"...하지만 다른 사람들이 유용하게 사용하기를 바랍니다" 모든 강조된 문장에도 불구하고 실제로 유용했습니다. : D

이것은 내가 읽거나 보거나 들었던 모나드에 대한 가장 간결하고 명확한 설명입니다. 감사합니다!
James

Monad와 Monoid 사이에는 중요한 차이점이 있습니다. Monad는 서로 다른 유형 사이에서 함수를 "구성"하는 규칙 이므로 Monoid에 필요한 이진 연산을 형성하지 않습니다. 자세한 내용은 여기를 참조하십시오. stackoverflow.com/questions/2704652/…
Dmitri Zaitsev

예. 당신이 올바른지. 당신의 기사는 내 머리 위에있었습니다 :). 그러나, 나는이 치료법이 매우 도움이되었다는 것을 알게되었습니다. 감사합니다. stackoverflow.com/a/7829607/1612190
George

2
대수 그룹 이론 과 Monad의 범주 이론 을 혼동했을 수 있습니다 . 전자는 대수 그룹의 이론으로, 관련이 없습니다.
Dmitri Zaitsev 2016 년

37

모나드는 사실상 "유형 연산자"의 한 형태입니다. 세 가지 일을 할 것입니다. 먼저 한 유형의 값을 다른 유형 (일반적으로 "모 노드 유형"이라고 함)으로 "랩핑"(또는 그렇지 않으면 변환)합니다. 둘째, 기본 유형에서 사용 가능한 모든 작업 (또는 기능)을 monadic 유형에서 사용할 수있게합니다. 마지막으로 복합 모나드를 생성하기 위해 자체를 다른 모나드와 결합하는 지원을 제공합니다.

"아마도 모나드"는 Visual Basic / C #의 "널링 가능 유형"과 기본적으로 같습니다. Null을 허용하지 않는 형식 "T"를 사용하여 "Nullable <T>"로 변환 한 다음 Nullable <T>에서 모든 이진 연산자의 의미를 정의합니다.

부작용은 유사하게 표현됩니다. 함수의 반환 값과 함께 부작용에 대한 설명이 포함 된 구조가 생성됩니다. 그런 다음 "리프팅"작업은 값이 함수간에 전달 될 때 부작용을 복사합니다.

몇 가지 이유로 "유형 연산자"의 이해하기 쉬운 이름이 아니라 "모나드"라고합니다.

  1. 모나드는 자신이 할 수있는 일에 제한이 있습니다 (자세한 내용은 정의 참조).
  2. 이러한 제한은 세 가지 연산이 관련되어 있다는 사실과 함께 범주 이론에서 모나드 (monad)라는 구조를 따르며, 이는 모호한 수학 분기입니다.
  3. 그것들은 "순수한"기능적 언어의 지지자들에 의해 설계되었습니다
  4. 모호한 수학 가지와 같은 순수 기능 언어의 지지자
  5. 수학이 모호하고 모나드가 특정 스타일의 프로그래밍과 관련되어 있기 때문에 사람들은 모나드라는 단어를 일종의 비밀 핸드 셰이크로 사용하는 경향이 있습니다. 이 때문에 아무도 더 나은 이름에 투자하지 않아도됩니다.

1
모나드는 '설계'되지 않았으며 한 도메인 (범주 이론)에서 다른 도메인 (순전히 기능적인 프로그래밍 언어의 I / O)에 적용되었습니다. 뉴턴은 미적분학을 '설계'했습니까?
Jared Updike

1
위의 1 점과 2 점은 정확하고 유용합니다. 포인트 4와 5는 다소 사실이더라도 일종의 adhominem입니다. 그들은 모나드를 설명하는 데 실제로 도움이되지 않습니다.
Jared Updike

13
Re : 4, 5 : "비밀 악수"는 붉은 청어입니다. 프로그래밍은 전문 용어로 가득합니다. Haskell은 무언가를 재발견하는 척하지 않고 물건을 부릅니다. 수학에 이미 존재한다면 왜 새로운 이름을 구성합니까? 그 이름은 사람들이 모나드를 얻지 못하는 이유가 아닙니다. 그들은 미묘한 개념입니다. 평범한 사람은 덧셈과 곱셈을 이해할 것입니다. 왜 Abelian Group의 개념을 얻지 못합니까? 그것은 더 추상적이고 일반적이며 그 사람은 개념 주위에 머리를 감는 작업을 수행하지 않았기 때문입니다. 이름 변경은 도움이되지 않습니다.
Jared Updike

16
한숨 .. 나는 Haskell을 공격하지 않습니다 ... 나는 농담을하고있었습니다. 그래서 저는 "애드 호미 em (ad hominem)"에 대해 조금도 알지 못합니다. 예, 미적분은 "설계되었습니다". 예를 들어 미적분학 학생들에게 Netwton이 사용하는 이상한 것들이 아닌 Leibniz 표기법을 배우는 이유가 여기에 있습니다. 더 나은 디자인. 좋은 이름은 많은 것을 이해하는 데 도움이됩니다. 내가 Abelian Groups를 "주름진 꼬투리"라고 불렀다면 나를 이해하는 데 어려움이있을 수 있습니다. 당신은 "하지만 그 이름은 말도 안된다"고 말하고있을 것입니다. 카테고리 이론을 들어 본 적이없는 사람들에게 "모나드"는 말도 안되는 소리처럼 들립니다.
Scott Wisniewski

4
@ 스콧 : 내 광범위한 의견으로 인해 Haskell에 대해 방어 적이었던 것 같습니다. 나는 비밀 악수에 대한 유머를 즐긴다. 그리고 나는 그것이 사실이라고 말했다. :-) Abelian Groups를 "주름진 꼬투리"라고 불렀다면 모나드에 "더 나은 이름"(F. "계산 표현"참조)을 부여하는 것과 같은 실수를하게 될 것입니다. "따뜻한 퍼지 사물"(또는 "계산 표현")이 아닙니다. "유형 연산자"라는 용어의 올바른 사용법을 이해한다면 모나드 이외의 다른 유형 연산자가 많이 있습니다.
Jared Updike

35

( 모나드무엇입니까?에 대한 답변도 참조하십시오 . )

Monads에 대한 좋은 동기는 sigfpe (Dan Piponi)의 당신이 Monads를 발명 했을 수 있습니다! (그리고 아마도 당신은 이미 가지고 있습니다) . 있다 다른 모나드 튜토리얼의 많은 misguidedly 다양한 비유를 사용하여 "간단한 약관"에 모나드를 설명하려고 많은 것이이 :이 인 모나드 튜토리얼 사기 야 ; 피하십시오.

로 DR MacIver는 말한다 언어 짜증 이유를 말해 :

그래서 Haskell에 대해 싫어하는 것 :

명백한 것으로 시작합시다. Monad 튜토리얼. 아니요, 모나드가 아닙니다. 특히 튜토리얼. 그들은 끝이없고, 과장되었고 사랑하는 신은 지루합니다. 또한 나는 그들이 실제로 도움이된다는 설득력있는 증거를 본 적이 없다. 클래스 정의를 읽고, 코드를 작성하고, 무서운 이름을 극복하십시오.

당신은 아마도 모나드를 이해한다고 말합니까? 좋아, 넌 가고있어 다른 모나드를 사용하기 시작하면 곧 모나드가 무엇인지 이해할 수 있습니다.

[수학 지향이라면, 수십 개의 튜토리얼을 무시하고 정의를 배우거나 카테고리 이론에서 강의를 따르고 싶을 수도 있습니다.) 정의 의 주요 부분은 Monad M이 각각에 대해 정의 된 "타입 생성자"를 포함한다는 것입니다 기존 유형 "T", 새로운 유형 "MT"및 "일반"유형과 "M"유형 사이를 오가는 몇 가지 방법]

또한 놀랍게도 모나드에 대한 가장 좋은 소개 중 하나는 실제로 모나드를 소개하는 초기 학술 논문 중 하나 인 함수형 프로그래밍을위한 필립와 들러의 모나드입니다 . 실제로 많은 인공 튜토리얼과 달리 실용적이지 않은 동기 부여 예제가 있습니다.


2
Wadler의 논문에서 유일한 문제는 표기법이 다르지만 논문이 꽤 매력적이며 모나드 적용에 대한 간결한 동기 부여에 동의한다는 데 동의합니다.
Jared Updike

"monad tutorial fallacy"에 +1 모나드에 관한 튜토리얼은 정수의 개념을 설명하려는 여러 튜토리얼을 갖는 것과 유사합니다. 한 자습서에서는 "1은 사과와 비슷합니다"라고 말합니다. 다른 튜토리얼은 "2는 배와 같다"고 말합니다. 세 번째는 "3은 기본적으로 주황색"이라고 말합니다. 그러나 단일 자습서에서 전체 그림을 얻을 수는 없습니다. 내가 얻은 것은 모나드는 많은 다른 목적으로 사용될 수있는 추상적 인 개념이라는 것입니다.
stakx-더 이상

@ stakx : 그렇습니다. 그러나 모나드는 여러분이 배울 수 없거나 배울 수없는 추상화라는 것을 의미하지는 않았습니다. 하나의 기본 추상화를 인식하기에 충분한 구체적인 예제를 본 후에 배우는 것이 가장 좋습니다. 내 다른 답변은 여기를 참조 하십시오 .
ShreevatsaR

5
때때로 독자들이 복잡하거나 유용한 일을하는 코드를 사용하여 모나드가 유용하다는 것을 설득시키려는 많은 튜토리얼이 있다고 생각합니다. 그것은 몇 달 동안 나의 이해를 방해했다. 나는 그런 식으로 배우지 않습니다. 나는 정신적으로 겪을 수있는 어리석은 짓을하면서 이런 종류의 예제를 찾을 수없는 매우 간단한 코드를 선호한다. 첫 번째 예제가 복잡한 문법을 ​​파싱하는 모나드인지 배울 수 없습니다. 정수를 합산하는 모나드인지 배울 수 있습니다.
Rafael S. Calsaverini

유일한 유형의 생성자를 언급하는 것은 불완전 : stackoverflow.com/a/37345315/1614973
드미트리 Zaitsev

23

Monad는 데이터에 대한 추상 데이터 유형이 무엇인지 흐름을 제어해야합니다.

다시 말해서, 많은 개발자들은 세트, 목록, 사전 (또는 해시 또는 맵) 및 트리의 아이디어에 익숙합니다. 이러한 데이터 유형에는 여러 가지 특별한 경우가 있습니다 (예 : InsertionOrderPreservingIdentityHashMap).

그러나 프로그램 "흐름"에 직면했을 때 많은 개발자들은 if, switch / case, do, while, goto (grr) 및 (아마도) 클로저보다 더 많은 구성에 노출되지 않았습니다.

따라서 모나드는 단순히 제어 흐름 구조입니다. 모나드를 대체하는 더 좋은 표현은 'control type'입니다.

따라서 모나드는 제어 로직, 명령문 또는 함수를위한 슬롯을 가지고 있습니다. 데이터 구조와 동등한 것은 일부 데이터 구조를 통해 데이터를 추가하고 제거 할 수 있다는 것입니다.

예를 들어, "if"모나드 :

if( clause ) then block

가장 간단한 두 절-절과 블록이 있습니다. if모나드는 보통 조항의 결과를 평가하기 위해 구축, 거짓되지 않을 경우, 블록을 평가한다. 많은 개발자들은 'if'를 배울 때 모나드를 소개하지 않으며 효과적인 논리를 작성하기 위해 모나드를 이해하지 않아도됩니다.

모나드는 데이터 구조가 더 복잡 해지는 것과 같은 방식으로 더 복잡해질 수 있지만, 의미론은 비슷하지만 구현과 구문이 다른 광범위한 범주의 모나드가 있습니다.

물론, 데이터 구조가 반복되거나 횡단 될 수있는 것과 동일한 방식으로 모나드가 평가 될 수있다.

컴파일러는 사용자 정의 모나드를 지원하거나 지원하지 않을 수 있습니다. 하스켈은 확실히 그렇습니다. 모나드 (Monad)라는 용어는 언어에서 사용되지 않지만, Ioke는 비슷한 기능을 가지고 있습니다.


14

내가 가장 좋아하는 Monad 튜토리얼 :

http://www.haskell.org/haskellwiki/All_About_Monads

( '모나드 튜토리얼'에 대한 Google 검색에서 170,000 개의 검색 결과 중!)

@Stu : 모나드의 요점은 순차 시맨틱을 순수한 코드에 추가 할 수있게하는 것입니다. 예를 들어 모나드 트랜스포머를 사용하여 모나드를 구성하고 오류 처리, 공유 상태 및 로깅과 같은 구문 분석과 같이 더 흥미롭고 복잡한 결합 의미를 얻을 수 있습니다. 이 모든 것이 순수한 코드에서 가능합니다. 모나드는이를 추상화하고 모듈 형 라이브러리 (항상 프로그래밍에 우수함)에서 재사용 할 수있을뿐 아니라 편리한 구문을 제공하여 명령형으로 만들 수 있습니다.

Haskell은 이미 연산자 오버로딩을 가지고 있습니다 [1] : Java 또는 C #에서 인터페이스를 사용하는 방식과 거의 비슷한 유형 클래스를 사용하지만 Haskell은 + && 및>와 같은 영숫자가 아닌 토큰도 삽입 식별자로 허용합니다. "세미콜론 과부하"[2]를 의미하는 경우 사용자가 보는 방식으로 연산자 오버로드입니다. 그것은 흑 마법처럼 들리고 "세미콜론에 과부하가 걸리는"문제를 요구하는 것처럼 들리지만 ( 단순히 Perl 해커들이이 아이디어에 바람을 피우고 있음) 모나드 가 없으면 세미콜론이 없다는 것이 핵심입니다. 순수한 기능적 코드는 명시적인 시퀀싱을 요구하거나 허용하지 않기 때문입니다.

이 모든 것이 필요한 것보다 훨씬 더 복잡하게 들립니다. sigfpe의 기사는 꽤 멋지지만 Haskell을 사용하여 설명합니다. 이는 Haskell을 이해하기 위해 닭고기와 계란 문제를 해결하지 못합니다.

[1] 이것은 모나드와는 별개의 문제이지만 모나드는 Haskell의 연산자 오버로드 기능을 사용합니다.

[2] 모나 딕 동작을 연쇄하는 연산자는 >> = ( "바인드"로 발음 됨)이지만 중괄호와 세미콜론 및 / 또는 들여 쓰기 및 줄 바꿈을 사용할 수있는 구문 설탕 ( "do")이 있기 때문에 이것은 지나치게 단순화 된 것입니다.


9

나는 최근에 다른 방식으로 Monads를 생각하고 있습니다. 나는 그것들을 수학적 방식으로 실행 순서 를 추상화하여 새로운 종류의 다형성을 가능하게하는 것으로 생각했습니다 .

명령형 언어를 사용하고 일부 표현식을 순서대로 작성하는 경우 코드는 항상 순서대로 정확하게 실행됩니다.

간단한 경우 모나드를 사용할 때도 같은 느낌이 들며 순서대로 발생하는 표현식 목록을 정의합니다. 단, 사용하는 모나드에 따라 코드가 순서대로 (IO 모나드 에서처럼) 실행될 수 있으며 (리스트 모나드에서와 같이) 한 번에 여러 항목에 대해 병렬로 중단 될 수 있습니다 (아마도 모나드에서와 같이) , 재개 모나드에서와 같이 나중에 다시 시작하기 위해 도중에 일시 중지하거나, 트랜잭션 모나드에서와 같이 처음부터 되감기 및 시작하거나, 로직 모나드에서와 같이 다른 옵션을 시도하기 위해 부분적으로 되감기 할 수 있습니다. .

모나드는 다형성이기 때문에 필요에 따라 다른 모나드에서 동일한 코드를 실행할 수 있습니다.

또한 어떤 경우에는 모나드를 모나드 변환기와 함께 결합하여 동시에 여러 기능을 얻을 수 있습니다.


9

나는 모나드에 여전히 새로운 오전,하지만 난 읽기 정말 좋은 느낌 내가 찾은 링크를 공유 할 것이라고 생각 (사진과 함께를!) : http://www.matusiak.eu/numerodix/blog/2012/3/11/ 레이맨을위한 모나드 / (가맹 없음)

기본적으로 기사에서 얻은 따뜻하고 모호한 개념은 모나드가 기본적으로 서로 다른 기능을 구성 가능한 방식으로 작동하도록하는 어댑터라는 개념입니다. 즉, 여러 기능을 연결하여 일관성없는 리턴에 대한 걱정없이 이들을 혼합하고 일치시킬 수 있습니다 유형 등. 따라서 BIND 기능은 우리가 이러한 어댑터를 만들려고 할 때 사과를 사과로, 오렌지를 오렌지로 유지하는 역할을합니다. LIFT 기능은 "더 낮은 수준의"기능을 수행하고 BIND 기능과 함께 작동하고 작곡 할 수 있도록 "업그레이드"하는 역할을합니다.

나는 그것이 올바르게 되었기를 바랍니다. 더 중요한 것은 기사가 모나드에 대한 올바른 견해를 갖기를 바랍니다. 다른 내용이 없다면,이 기사는 모나드에 대해 더 많이 배우 겠다는 식욕을 자극하는 데 도움이되었습니다.


파이썬 예제는 이해하기 쉽게 만들었습니다! 공유해 주셔서 감사합니다.
Ryan Efendy

8

위의 훌륭한 답변 외에도 개념을 JavaScript 라이브러리 jQuery (및 DOM을 조작하기 위해 "메소드 체인"을 사용하는 방법)와 관련시켜 모나드를 설명하는 다음 기사 (Patrick Thomson)에 대한 링크를 제공하겠습니다. : jQuery는 Monad입니다

jQuery를 문서 자체는 아마 더 잘 알고있는 "빌더 패턴"에 대한 용어는 "모나드"하지만 회담을 참조하지 않습니다. 이것은 모나드를 모르는 사이에 적절한 모나드가 있다는 사실을 바꾸지 않습니다.


당신이 jQuery를 사용하는 경우,이 설명은 하스켈이 강하지 특히, 매우 도움이 될 수 있습니다
byteclub

10
JQuery는 강조 적으로 모나드가 아니다. 링크 된 기사가 잘못되었습니다.
Tony Morris

1
"만족 적"이라는 것은 매우 설득력이 없습니다. 이 주제에 대한 유용한 설명 은 jQuery가 모나드입니까-스택 오버플로
nealmcb

1
도 참조 더글러스 Crackford의 Google 토크 모나드와 생식선 및 AJAX 라이브러리와 약속의 비슷한 동작에 확대 modads을 수행하기위한 자신의 자바 스크립트 코드 : douglascrockford / 모나드 · GitHub의
nealmcb

8

Daniels는 은유 가 아니라 Daniel Spiewak이 설명하는 것처럼 일반적인 패턴에서 나오는 실질적으로 유용한 추상화입니다.


7

모나드는 공통 컨텍스트를 공유하는 계산을 결합하는 방법입니다. 파이프 네트워크를 구축하는 것과 같습니다. 네트워크를 구성 할 때 네트워크를 통해 흐르는 데이터가 없습니다. 그러나 모든 비트를 '바인드'및 '반품'과 함께 파이핑을 마치면 비슷한 것을 호출 runMyMonad monad data하고 데이터가 파이프를 통해 흐릅니다.


1
그것은 Monad보다 Applicative와 비슷합니다. Monads를 사용하면 연결할 다음 파이프를 선택하기 전에 파이프에서 데이터를 가져와야합니다.
Peaker

예, 모나드가 아닌 적용법을 설명합니다. Monad는 파이프 내부에서 해당 지점에 도달 한 데이터에 따라 그 자리에 다음 파이프 세그먼트를 구축합니다.
Will Ness

6

실제로, 모나드는 부작용과 호환되지 않는 입력 및 리턴 값 (체인)을 처리하는 함수 구성 연산자의 사용자 정의 구현입니다.



5

거기에서 배울 때 가장 도움이 된 두 가지는 :

그레이엄 허튼의 책에서 8 장, "기능 파서," 하스켈 프로그래밍 . 이것은 모나드를 전혀 언급하지는 않지만 실제로 챕터를 통해 작업하고 그 안에있는 모든 내용, 특히 일련의 바인드 연산이 평가되는 방식을 이해할 수 있다면 모나드의 내부를 이해할 수 있습니다. 여러 번 시도 할 것으로 예상됩니다.

모나드에 관한 모든 튜토리얼 . 이것은 그들의 사용에 대한 몇 가지 좋은 예를 제공하며, 나는 Appendex의 비유가 나를 위해 일했다고 말해야합니다.


5

Monoid는 Monoid 및 지원되는 유형에 정의 된 모든 작업이 항상 Monoid 내에서 지원되는 유형을 반환하도록하는 것으로 보입니다. 예 : 임의의 숫자 + 임의의 숫자 = 숫자, 오류 없음.

반면에 division은 두 개의 분수를 허용하고 분수를 반환합니다.이 분수는 haskell somewhy에서 무한대로 0으로 나눈 값을 정의합니다.

어쨌든 Monass는 작업 체인이 예측 가능한 방식으로 작동하도록하는 방법 일뿐이며 Num-> Num이라고 주장하는 함수는 Num-> Num의 x로 호출 된 다른 함수로 구성되지 않습니다. 예를 들어, 미사일을 발사하십시오.

반면에 미사일을 발사하는 기능이 있다면 미사일을 발사하는 다른 기능으로 구성 할 수 있습니다. 왜냐하면 우리의 의도가 명확하기 때문입니다. 우리는 미사일을 발사하고 싶지만 시도하지는 않습니다. 이상한 이유로 "Hello World"를 인쇄합니다.

Haskell에서 main은 IO () 또는 IO [()] 유형입니다. 분명은 이상하며 토론하지는 않지만 다음과 같이 생각합니다.

내가 main을 가지고 있다면, 일련의 액션을 원한다면, 프로그램을 실행하는 이유는 일반적으로 IO를 통해 효과를 내기 위해서입니다. 따라서 IO를 수행하기 위해 IO 작업을 기본으로 함께 연결할 수 있습니다.

"IO를 반환하지 않는"무언가를 시도하면 프로그램이 체인이 흐르지 않는다고 불평하거나 기본적으로 "이것이 우리가하려는 것과 어떻게 관련이 있습니까? IO 동작"이라고 강제합니다. 프로그래머는 미사일을 발사하거나 발사하는 것에 대해 생각하지 않고 사고에 대한 훈련을 유지하면서도 정렬 알고리즘을 만들지 않습니다.

기본적으로 Monads는 컴파일러의 팁인 것처럼 보입니다. "여기서,이 함수는 숫자를 반환하는 함수라는 것을 알고 있습니다. 실제로 항상 작동하지는 않습니다. 때로는 숫자를 생성 할 수도 있고 때로는 아무것도 생성하지 않을 수도 있습니다. 마음". 이것을 알면, 모나 딕 액션을 주장하려고하면, 모나 딕 액션은 컴파일 시간 예외로 작용할 수 있습니다. "이봐, 이건 실제로 숫자가 아니고,이 숫자는 숫자 일 수 있지만, 이것을 추측 할 수는없고, 무언가를하라" 흐름이 허용되는지 확인하십시오. " 예측할 수없는 프로그램 동작을 방지합니다.

모나드는 순도, 통제가 아니라 모든 행동을 예측하고 정의하거나 컴파일하지 않는 카테고리의 정체성을 유지하는 것에 관한 것입니다. 무언가를해야 할 때는 아무것도 할 수 없으며, 아무것도 보이지 않을 경우 (보이는) 것을 할 수 없습니다.

내가 Monads에 대해 생각할 수있는 가장 큰 이유는 다음과 같습니다. Procedural / OOP 코드를 살펴보면 프로그램이 시작되거나 끝나는 곳을 알지 못합니다. 많은 점프와 수학이 있다는 것을 알 수 있습니다. 마법, 미사일. 당신은 그것을 유지할 수 없을 것이며, 가능하다면, 프로그램의 일부를 이해하기 전에 전체 프로그램을 둘러싼 많은 시간을 소비 할 것입니다. 왜냐하면이 맥락에서의 모듈성은 상호 의존적 인 "섹션"에 기초하기 때문입니다. 효율성 / 상호 관계를 보장하기 위해 가능한 한 관련성이 있도록 코드를 최적화하는 코드 모나드는 매우 구체적이며 정의에 의해 잘 정의되어 있으며 프로그램 흐름이 분석 가능하고 분석하기 어려운 부분은 모나드이므로 분리 할 수 ​​있습니다. 모나드는 " 우주를 파괴하거나 시간을 왜곡 할 수도 있습니다. 우리는 IT가 무엇인지에 대해 전혀 모릅니다. 모나드는 IT가 무엇인지 보증합니다. 매우 강력합니다. 우주를 파괴하거나 시간을 왜곡 할 수도 있습니다. 우리는 IT가 무엇인지에 대해 전혀 모릅니다. 모나드는 IT가 무엇인지 보증합니다. 매우 강력합니다.

"실제 세계"의 모든 것은 혼동을 막는 명확한 관찰 가능한 법에 구속되어 있다는 의미에서 모나드 인 것처럼 보입니다. 그렇다고 클래스를 생성하기 위해이 객체의 모든 연산을 흉내낼 필요는 없습니다. 대신 "사각은 정사각형입니다"라고 말하면됩니다. 어떤 치수를 가지고 있든, 2D 공간의 사각형이라면, 그 영역은 절대 길이가 아닌 다른 것이 될 수 없습니다. 우리는 세상이 현재와 같은 방식으로되도록하기 위해 단언 할 필요가 없습니다. 단지 프로그램이 제대로 진행되지 못하도록 현실에 영향을줍니다.

나는 틀린 것이 거의 보장되지만 이것이 누군가를 도울 수 있다고 생각하므로 누군가를 도울 수 있기를 바랍니다.


5

스칼라의 맥락에서 다음이 가장 간단한 정의라는 것을 알게 될 것입니다. 기본적으로 flatMap (또는 bind)은 'associative'이며 ID가 있습니다.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

예 :

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

참고 엄밀히의 정의 말하기 함수형 프로그래밍의 모나드는 a의 정의와 동일하지 않습니다 분류 이론 모나드 의 회전에 정의되어, mapflatten. 특정 매핑에서는 동일합니다. 이 프리젠 테이션은 매우 좋습니다 : http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category


5

이 답변은 동기 부여 예제로 시작하고 예제를 통해 작동하며 모나드의 예를 도출하고 공식적으로 "monad"를 정의합니다.

의사 코드에서 다음 세 가지 기능을 고려하십시오.

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

f양식의 순서 쌍을 취하고 순서 쌍을 <x, messages>반환합니다. 첫 번째 항목은 그대로 "called f. "두고 두 번째 항목에 추가 합니다. 와 동일합니다 g.

이러한 함수를 작성하고 함수가 호출 된 순서를 표시하는 문자열과 함께 원래 값을 얻을 수 있습니다.

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

당신은 그 사실을 싫어 f하고 g이전의 로깅 정보를 자신의 로그 메시지를 추가 할 책임이 있습니다. (그냥 인수를 위해 상상 그 대신에 문자열을 추가, f그리고 g쌍의 두 번째 항목에 복잡한 로직을 수행해야 그것은 반복에 통증이있을 것이라고 복잡한 로직이에 -. 이상 -. 다른 기능)

더 간단한 함수를 작성하는 것을 선호합니다.

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

그러나 작성시 어떤 일이 발생하는지보십시오.

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

문제는이다 전달 함수에 한 쌍 것은 당신이 원하는 무엇을 제공하지 않습니다. 그러나 함수에 쌍을 공급할 수 있다면 어떨까요?

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

feed(f, m)"feed minto f" 로 읽 습니다 . 위해 공급 한 쌍의 <x, messages>함수로하는 f것입니다 통과 xf얻을 <y, message>밖으로 f, 반환 <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

함수로 세 가지 작업을 수행하면 어떻게되는지 확인하십시오.

첫째 : 값을 줄 바꿈 한 다음 결과 쌍을 함수에 공급 하면 :

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

이는 값을 함수에 전달 하는 것과 같습니다 .

둘째 : 당신이에 쌍을 공급하는 경우 wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

그것은 쌍을 변경하지 않습니다.

셋째, 당신이받는 함수를 정의하는 경우 x와 피드 g(x)로를 f:

h(x) := feed(f, g(x))

한 쌍을 먹이십시오.

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

이는 쌍을 g공급하고 결과 쌍을 공급 하는 것과 동일 f합니다.

대부분의 모나드가 있습니다. 이제 프로그램의 데이터 유형에 대해 알아야합니다.

어떤 유형의 가치가 <x, "called f. ">있습니까? 글쎄, 그것은 어떤 유형의 가치인지에 달려 x있습니다. x유형이 인 경우 t쌍은 "pair of tand string" 유형의 값입니다 . 해당 유형을 호출하십시오 M t.

M유형 생성자입니다. M단독으로는 유형을 참조하지 않지만 유형으로 M _공백을 채우면 유형을 나타냅니다. 는 M intint 형과 문자열의 한 쌍이다. 은 M string문자열과 문자열의 한 쌍이다. 기타.

축하합니다. 모나드를 만들었습니다.

공식적으로 모나드는 튜플 <M, feed, wrap>입니다.

모나드는 다음과 같은 튜플 <M, feed, wrap>입니다.

  • M 형식 생성자입니다.
  • feed(a 걸리는 소요 함수 t및를 반환 M u) 및 M t복귀를 M u.
  • wrapa를 가져 와서 v를 반환합니다 M v.

t, uv같거나 같지 않을 수있는 세 가지 유형입니다. 모나드는 특정 모나드에 대해 입증 된 세 가지 속성을 만족시킵니다.

  • 먹이 A는 래핑 t함수로하는 것과 동일 통과 언 래핑을 t함수로.

    공식적으로 : feed(f, wrap(x)) = f(x)

  • 먹이 M t로하는 것은 wrap받는 아무것도하지 않는다 M t.

    공식적으로 : feed(wrap, m) = m

  • 함수에 M t(호출 m) 공급

    • t안으로 전달g
    • 에서 M u(호출 n)을 얻습니다.g
    • 피드 nf

    와 같다

    • 먹이 mg
    • 에서 n오는g
    • 먹이 nf

    공식적으로 : feed(h, m) = feed(f, feed(g, m))어디서h(x) := feed(f, g(x))

일반적으로, feed호출 bind(일명 >>=하스켈)과 wrap라고합니다 return.


5

Monad하스켈의 맥락에서 설명하려고 노력할 것 입니다.

함수형 프로그래밍에서는 함수 구성이 중요합니다. 프로그램이 작고 읽기 쉬운 기능으로 구성 될 수 있습니다.

이제 우리는 두 가지 기능이 있다고 가정 해 봅시다 : g :: Int -> Stringf :: String -> Bool.

우리는 할 수있는 (f . g) x대로 그냥 같은 인 f (g x)경우, x입니다 Int값.

한 함수의 결과를 다른 함수에 구성 / 적용 할 때 유형이 일치하는 것이 중요합니다. 위의 경우에 의해 반환되는 결과 g유형은에 의해 허용 된 유형과 같아야합니다 f.

그러나 때로는 값이 컨텍스트에 있으므로 유형을 정렬하기가 조금 더 쉽습니다. 컨텍스트에 값을 갖는 것이 매우 유용합니다. 예를 들어, Maybe Int유형 Int은 없을 수도 IO String있는 String값을 나타내고 유형은 일부 부작용을 수행 한 결과 인 값을 나타냅니다 .

의 우리가 지금 있다고 가정 해 봅시다 g1 :: Int -> Maybe Stringf1 :: String -> Maybe Bool. g1f1매우 유사 g하고 f각각.

우리는 할 수없는 (f1 . g1) x또는 f1 (g1 x)경우, x입니다 Int값. 에 의해 반환 된 결과의 유형이 예상 한 g1것과 f1다릅니다.

우리는 구성 할 수 fg.운영자,하지만 지금은 우리가 구성 할 수 f1g1.. 문제는 컨텍스트에없는 값을 기대하는 함수에 컨텍스트의 값을 직접 전달할 수 없다는 것입니다.

작성하기 위해 연산자를 작성 g1하여 f1글을 쓸 수 (f1 OPERATOR g1) x있다면 좋을까요? g1컨텍스트에서 값을 반환합니다. 값은 컨텍스트에서 가져와에 적용됩니다 f1. 그렇습니다. 우리에게는 그런 연산자가 있습니다. 그것은이다 <=<.

우리는 또한 >>=약간 다른 문법으로 우리에게 똑같은 일을 하는 연산자를 가지고 있습니다 .

우리는 씁니다 g1 x >>= f1. g1 xA는 Maybe Int값. >>=운영자는이 걸릴 수 있습니다 Int은 "아마도-하지-이"문맥 값을, 그리고에 적용 f1. 결과 f1A는, Maybe Bool전체의 결과 일 것이다 >>=동작.

그리고 마지막으로 왜 Monad유용한가? 연산자 Monad를 정의하는 형식 클래스 이기 때문에 and 연산자 를 정의하는 형식 클래스 >>=와 거의 동일 Eq합니다 .==/=

결론적으로 Monad타입 클래스는 >>=컨텍스트의 값을 기대하지 않는 함수에 컨텍스트의 값 (모 노드 값이라고 함)을 전달할 수 있는 연산자를 정의합니다 . 컨텍스트가 처리됩니다.

여기서 기억해야 할 것이 있다면 Monad컨텍스트의 값을 포함하는 함수 구성을 허용한다는 것 입니다.



IOW, Monad는 일반화 된 함수 호출 프로토콜입니다.
윌 네스

당신의 대답은 내 의견에 가장 도움이됩니다. 비록 당신이 말하는 함수가 컨텍스트에 값을 포함시키는 것이 아니라 컨텍스트에 값을 적극적으로 넣는다는 사실에 중점을 둘 필요가 있다고 생각해야합니다. 예를 들어, f :: ma-> mb 함수는 다른 함수 g :: mb-> m c와 매우 쉽게 구성됩니다. 그러나 모나드는 (바인드 구체적으로) 우리는 우리가 (효과적으로 값에서 정보를 제거 할 것이다) 먼저 문맥 값을하지 않고도 같은 맥락에서 자신의 입력을 넣어 영구적으로 작성 기능을 할 수 있습니다
제임스

@ 제임스 그것이 functors를 강조해야한다고 생각합니까?
Jonas

@Jonas 나는 제대로 설명하지 않았다고 생각합니다. 함수가 컨텍스트에 값을 넣었다고 말하면 유형 (a-> mb)이 있음을 의미합니다. 컨텍스트에 값을 넣으면 새로운 정보가 추가되기 때문에 매우 유용하지만 값을 가져올 수 없기 때문에 일반적으로 (a-> mb)와 (b-> mc)를 연결하는 것은 어렵습니다. 문맥의. 따라서 특정 상황에 따라 합리적인 기능으로 이러한 기능을 함께 연결하려면 복잡한 프로세스를 사용해야하며 모나드는 상황에 관계없이 일관된 방식으로이를 수행 할 수 있습니다.
James

5

tl; dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

프롤로그

$함수 의 응용 연산자

forall a b. a -> b

정식으로 정의

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

Haskell-primitive function application f x( infixl 10)과 관련하여.

구성 .은 다음과 $같이 정의 됩니다.

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

등가를 충족 forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.연관성 id이 있으며 오른쪽과 왼쪽의 정체성입니다.

클라이 슬리 트리플

프로그래밍에서 모나드는 모나드 유형 클래스의 인스턴스가있는 functor 유형 생성자입니다. 모나드 추상화에 대해 약간 다른 직관을 갖는 정의 및 구현의 몇 가지 동등한 변형이 있습니다.

functor는 functor 타입 클래스의 인스턴스를 가진 종류의 생성자 f입니다 * -> *.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

정적으로 시행되는 유형 프로토콜을 따르는 것 외에도 functor 유형 클래스의 인스턴스는 대수 functor 법을 준수해야합니다. forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

펑터 계산 유형

forall f t. Functor f => f t

계산 c r컨텍스트결과 로 구성됩니다 .r c

단항 모나 딕 함수 또는 Kleisli 화살표

forall m a b. Functor m => a -> m b

Kleisi 화살표는 하나의 인수를 취하고 a모나드 계산을 반환하는 함수입니다 m b.

모나드는 Kleisli 트리플의 관점에서 정식으로 정의됩니다 forall m. Functor m =>

(m, return, (=<<))

타입 클래스로 구현

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

Kleisli ID는 return 값 촉진하는 Kleisli 화살표입니다 t모나드 맥락을 m. 확장 또는 Kleisli 응용 프로그램 =<< 은 Kleisli 화살표 a -> m b를 계산 결과에 적용합니다 m a.

Kleisli 구성 <=< 은 다음과 같이 확장 측면에서 정의됩니다.

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< Kleisli 화살표 2 개를 작성하고 왼쪽 화살표를 오른쪽 화살표 적용 결과에 적용합니다.

모나드 유형 클래스의 인스턴스는 Kleisli 구성 측면에서 가장 우아하게 설명 된 모나드 법칙을 준수해야합니다 .forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<연관성 return이 있으며 오른쪽과 왼쪽의 정체성입니다.

정체

신원 유형

type Id t = t

유형에 대한 항등 함수입니다

Id :: * -> *

functor로 해석

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

정식 Haskell에서 아이디 모나드는 정의됩니다

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

선택권

옵션 유형

data Maybe t = Nothing | Just t

Maybe t반드시 t“실패”할 수 있는 결과를 산출하지는 않는 계산 을 인코딩 합니다. 옵션 모나드가 정의됩니다

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe b결과를 Maybe a산출하는 경우에만 결과에 적용됩니다 .

newtype Nat = Nat Int

자연수는 0 이상의 정수로 인코딩 될 수 있습니다.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

빼기에서 자연수는 닫히지 않습니다.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

옵션 monad는 기본적인 형태의 예외 처리를 다룹니다.

(-? 20) <=< toNat :: Int -> Maybe Nat

명부

목록 유형에 대한 목록 모나드

data [] t = [] | t : [t]

infixr 5 :

그리고 부가적인 단일체 작동“추가”

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

비선형 계산 [t]을 인코딩 하여 자연스러운 0, 1, ...결과를 t냅니다.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

확장 =<<연접 ++모든 목록 [b]애플리케이션 인한 f xKleisli의 화살표 a -> [b]의 요소를 [a]단일 결과리스트로 [b].

양의 정수의 적절한 약수가 보자 n

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

그때

forall n.  let { f = f <=< divisors } in f n   =   []

확장 대신 모나드 유형 클래스를 정의 할 =<<때 Haskell 표준은 플립 연산자 인 bind 연산자를 사용 >>=합니다.

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

간단히하기 위해이 설명에서는 형식 클래스 계층 구조를 사용합니다.

class              Functor f
class Functor m => Monad m

Haskell에서 현재 표준 계층 구조는

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

모든 모나드는 functor 일뿐만 아니라 모든 적용은 functor이고 모든 모나드는 적용되기 때문입니다.

목록 모나드를 사용하여 명령형 의사 코드

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

대략적으로 변환 DO 블록 ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

동등한 모나드 이해 ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

그리고 표현

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

표기법 및 모나드 이해는 중첩 된 바인드 표현식의 구문 설탕입니다. 바인드 연산자는 모나드 결과의 로컬 이름 바인딩에 사용됩니다.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

어디

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

보호 기능이 정의됩니다

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

여기서 단위 유형 또는 "빈 튜플"

data () = ()

선택실패 를 지원하는 추가 모나드 는 타입 클래스를 사용하여 추상화 될 수 있습니다

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

어디서 fail그리고 <|>monoid를 형성forall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

fail첨가제 모나드 제로 소자 전멸 / 흡수되고

_ =<< fail  =  fail

만약에

guard (even p) >> return p

even p가드 (guard)는 가드를 [()]정의 >>하고 로컬 상수 함수를 정의함으로써

\ _ -> return p

결과에 적용됩니다 (). False 인 경우 경비원은 목록 모나드 fail( [])를 생성하여 Kleisli 화살표를 적용 >>할 결과를 얻지 않으므로 p건너 뜁니다.

상태

유감스럽게도 모나드는 상태 저장 계산을 인코딩하는 데 사용됩니다.

상태 프로세서 함수

forall st t. st -> (t, st)

상태를 전환하고 st결과를 산출합니다 t. 상태는 st 아무것도 할 수있다. 아무것도, 깃발, 카운트, 배열, 핸들, 기계, 세계.

상태 프로세서의 유형은 일반적으로

type State st t = st -> (t, st)

상태 프로세서 모나드는 종류가있는 * -> *functor State st입니다. 상태 프로세서 모나드의 Kleisli 화살표는 기능입니다

forall st a b. a -> (State st) b

표준 Haskell에서는 게으른 버전의 상태 프로세서 모나드가 정의됩니다.

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

상태 프로세서는 초기 상태를 제공하여 실행됩니다.

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

스테이트 모나드에 대한 프리미티브 getput추상화 방법으로 스테이트 액세스를 제공합니다 .

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> st모나드에 대한 상태 유형 의 기능적 종속성 을 선언합니다 . 예를 들어, a 는 상태 유형을 고유하게 결정합니다 .stmState tt

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

유사하게 사용 된 장치 유형 voidC.에서

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets 레코드 필드 접근 자와 함께 자주 사용됩니다.

변수 스레딩에 해당하는 상태 모나드

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

어디 s0 :: Int에서 똑같이 참조 투명하지만, 더 우아하고 실용적

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)형식은 다음 과 같은 효과를State Int () 제외하고 형식의 계산입니다 .return ()

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

연관성의 모나드 법칙은 >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

또는

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

식 지향 프로그래밍 (예 : Rust)에서와 같이 블록의 마지막 명령문은 수율을 나타냅니다. 바인드 연산자를 "프로그래밍 가능한 세미콜론"이라고도합니다.

구조적 명령형 프로그래밍의 반복 제어 구조 프리미티브는 모나드 방식으로 에뮬레이트됩니다.

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

입출력

data World

I / O 세계 상태 프로세서 모나드는 순수한 Haskell과 실제 세계, 기능적 표현 및 명령 운영 시맨틱의 조정입니다. 실제 엄격한 구현과 밀접한 유사 :

type IO t = World -> (t, World)

불완전한 프리미티브로 상호 작용이 촉진됩니다.

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

IO프리미티브 를 사용하는 코드의 불순은 유형 시스템에 의해 영구적으로 프로토콜됩니다. 순도는 굉장하기 때문에에 일어난 일은 IO에 남아 있습니다 IO.

unsafePerformIO :: IO t -> t

또는 적어도해야합니다.

하스켈 프로그램의 형식 서명

main :: IO ()
main = putStrLn "Hello, World!"

~로 확장

World -> ((), World)

세상을 변화시키는 기능.

발문

객체가 하스켈 유형이고 하스켈 유형 사이의 함수 인 형태는 카테고리 "빠르고 느슨 함" Hask입니다.

functor T는 카테고리에서 카테고리 C로의 맵핑입니다 D. 개체의 각 개체에 C대한D

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

그리고 형태의 각 형태에 C대해D

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

여기서 X, Y의 개체입니다 C. HomC(X, Y)는 IS 이체 동형 클래스 의 모든 morphisms의 X -> Y의는 C. 펑 터는 morphism에 정체성과 구성의 "구조"를 보존해야 CD.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

Kleisli 카테고리 범주의이 CKleisli 트리플 주어진다

<T, eta, _*>

Endofunctor의

T : C -> C

( f), ID 형태 eta( return) 및 확장 연산자 *( =<<).

각 Kleisli 형태 Hask

      f :  X -> T(Y)
      f :: a -> m b

확장 연산자에 의해

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

HaskKleisli 범주에 형태가 부여됩니다

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Kleisli 범주의 구성 .T은 확장 측면에서 제공됩니다

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

범주 공리를 충족

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

등가 변환을 적용하는

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

확장 측면에서 정식으로 제공됩니다

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Monads는 Kleislian 확장이 아니라 mu라는 프로그래밍에서 자연스러운 변형이라는 용어로 정의 할 수도 있습니다 join. 모나드는 endofunctor mu의 카테고리에 대한 트리플로 정의됩니다.C

     T :  C -> C
     f :: * -> *

그리고 두 자연 변형

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

동등성을 만족시키는

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

그런 다음 모나드 타입 클래스가 정의됩니다

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

mu옵션 모나드 의 표준 구현 :

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

concat기능

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

는 IS join리스트 모나드의는.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

join등가를 사용하여 확장 양식에서 구현을 번역 할 수 있습니다.

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

에서 mu확장 형식으로 의 역변환 은

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

그러나 왜 이론이 그렇게 추상적이어야 프로그래밍에 사용되어야 하는가?

답은 간단합니다. 컴퓨터 과학자로서 우리는 추상화중요하게 생각 합니다 ! 소프트웨어 구성 요소에 대한 인터페이스를 설계 할 때 구현에 대해 가능한 한 적은 정보를 공개하기를 원합니다 . 우리는 구현을 많은 대안, 같은 '개념'의 다른 많은 '인스턴스'로 대체 할 수 있기를 원합니다. 많은 프로그램 라이브러리에 대한 일반 인터페이스를 설계 할 때, 선택한 인터페이스가 다양한 구현을 갖는 것이 더욱 중요합니다. 우리가 매우 중요하게 생각하는 것은 모나드 개념의 일반성입니다. 범주 이론이 너무 추상적 이기 때문에 개념이 프로그래밍에 매우 유용하기 때문입니다.

그러므로 우리가 아래에 제시하는 모나드의 일반화가 범주 이론과 밀접한 관련이 있다는 것은 놀라운 일이 아닙니다. 그러나 우리는 우리의 목적이 매우 실용적이라고 강조합니다. '범주 이론을 구현하는 것'이 아니라 결합기 라이브러리를 구성하는보다 일반적인 방법을 찾는 것입니다. 수학자들이 이미 우리를 위해 많은 일을 해낸 것은 단순히 우리의 행운입니다!

에서 화살표로 일반화 모나드 존 휴즈에 의해


4

세상에 필요한 것은 또 다른 모나드 블로그 게시물이지만, 이것이 야생에서 기존 모나드를 식별하는 데 유용하다고 생각합니다.

시 에르 핀 스키 삼각형

위의 그림은 Sierpinski triangle이라는 프랙탈로, 내가 그리는 기억할 수있는 유일한 프랙탈입니다. 도형은 위의 삼각형과 같이 자기 유사 구조로, 부분이 전체와 유사합니다 (이 경우 부모 삼각형의 스케일의 정확히 절반).

모나드는 프랙탈입니다. 모나 딕 데이터 구조가 주어지면, 그 값은 데이터 구조의 다른 값을 형성하도록 구성 될 수 있습니다. 이것이 프로그래밍에 유용한 이유이며 많은 상황에서 발생하는 이유입니다.


3
"세계 필요로 하지 않는 것"을 의미 합니까? 그래도 좋은 비유!
groverboy

@ icc97 당신이 맞아요-그 의미는 분명합니다. Sarcasm은 의도하지 않은 저자에게 사과합니다.
groverboy

세상에 필요한 것은 풍자를 확인하는 또 다른 주석 스레드이지만주의 깊게 읽으면 작성 했지만 명확하게해야합니다.
유진 요코타


4

아래의 " {| a |m}"는 일부 모나드 데이터를 나타냅니다. 다음을 알리는 데이터 유형 a:

        (I got an a!)
          /        
    {| a |m}

함수 f는 모나드를 만드는 방법을 알고 있습니다 a.

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

여기에서 함수, f모나드를 평가하려고 시도하지만 꾸짖습니다.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funtion fa을 사용하여 를 추출하는 방법을 찾습니다 >>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

f모나드 는 거의 알지 >>=못하며 공모에 있습니다.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

그러나 그들은 실제로 무엇에 대해 이야기합니까? 글쎄, 그것은 모나드에 달려 있습니다. 초록에서만 말하는 것은 사용이 제한되어있다. 특정 모나드에 대한 경험이 있어야 이해를 살릴 수 있습니다.

예를 들어, 데이터 유형

 data Maybe a = Nothing | Just a

다음과 같은 모나드 인스턴스가 있습니다 ...

그 경우에 Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

그러나 Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

어쩌면 어쩌면 모나드에 실제로 a광고 가 포함되어 있으면 계산을 계속할 수 있지만 그렇지 않은 경우 계산을 중단합니다. 그러나 결과는 여전히 모나드 데이터의 일부이지만 출력은 아닙니다 f. 이러한 이유로 Maybe 모나드는 실패의 상황을 나타내는 데 사용됩니다.

다른 모나드가 다르게 동작합니다. 리스트는 모나드 인스턴스가있는 다른 유형의 데이터입니다. 그들은 다음과 같이 행동합니다 :

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

이 경우 함수는 입력에서 목록을 작성하는 방법을 알고 있었지만 추가 입력 및 추가 목록으로 수행 할 작업을 알지 못했습니다. bind 는 여러 출력을 결합하여 >>=도움이 f되었습니다. 이 예제를 포함 >>=하여 추출을 담당 a하지만 최종 바운드 출력에 액세스 할 수 있음을 보여줍니다 f. 실제로 a결과 출력에 동일한 유형의 컨텍스트가 있다는 것을 알지 않는 한 추출하지 않습니다.

다른 상황을 나타내는 데 사용되는 다른 모나드가 있습니다. 여기 몇 가지 특징이 더 있습니다. IO모나드는 실제로이없는 a,하지만 사람을 알고 그것을 얻을 것이다 a당신을 위해. State st모나드의 비밀을 숨기고있다 st가에 전달할 것이라고 f하더라도, 테이블 아래 f단지를 요구했다을 a. Reader r모나드과 유사한 State st에만 수 있지만, f에 모습을 r.

이 모든 점은 모나드로 선언 된 모든 유형의 데이터가 모나드에서 값을 추출하는 것과 관련하여 일종의 컨텍스트를 선언한다는 것입니다. 이 모든 것에서 큰 이득? 글쎄, 어떤 종류의 상황에서 계산을하기에 충분합니다. 그러나 여러 컨텍스트 기반 계산을 함께 묶을 때 혼란 스러울 수 있습니다. 모나드 작업은 프로그래머가 필요하지 않도록 컨텍스트의 상호 작용을 해결합니다.

>>=자율성 중 일부를에서 빼 냄으로써 사용이 엉망이 된다는 점에 유의하십시오 f. 즉, Nothing예를 들어 위의 경우 f더 이상 다음의 경우 수행 할 작업을 결정할 수 없습니다 Nothing. 로 인코딩되어 >>=있습니다. 이것은 트레이드 오프입니다. 것이 필요하다고하면 f결정의 경우에 무엇을 할 Nothing다음 f에서 함수 있었어야 Maybe aMaybe b. 이 경우 Maybe모나드가되는 것은 중요하지 않습니다.

그러나 때로는 데이터 유형이 데이터 생성자를 내 보내지 않고 (IO를보고) 광고 된 값으로 작업하려면 선택의 여지가 없지만 모나드 인터페이스로 작업하는 것이 좋습니다.


3

모나드는 상태가 변하는 객체를 캡슐화하는 데 사용되는 것입니다. 수정 가능한 상태 (예 : Haskell)를 갖지 못하는 언어에서 가장 자주 발생합니다.

파일 I / O를 예로들 수 있습니다.

파일 I / O에 모나드를 사용하여 변경 상태 특성을 Monad를 사용한 코드로 분리 할 수 ​​있습니다. 모나드 내부의 코드는 모나드 외부의 변화하는 세계 상태를 효과적으로 무시할 수 있으므로 프로그램의 전반적인 효과를 추론하기가 훨씬 쉽습니다.


3
내가 이해하는 것처럼 모나드는 그 이상입니다. "순수한"기능 언어로 변경 가능한 상태를 캡슐화하는 것은 모나드의 한 응용 프로그램 일뿐입니다.
thSoft
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.