최근에 Haskell을 간략하게 살펴보았는데, 모나드가 본질적으로 무엇인지에 대한 간단하고 간결하며 실용적인 설명은 무엇입니까?
나는 내가 접한 대부분의 설명이 상당히 접근하기 어렵고 실용적인 세부 사항이 부족하다는 것을 발견했습니다.
질문자 :Community Wiki
최근에 Haskell을 간략하게 살펴보았는데, 모나드가 본질적으로 무엇인지에 대한 간단하고 간결하며 실용적인 설명은 무엇입니까?
나는 내가 접한 대부분의 설명이 상당히 접근하기 어렵고 실용적인 세부 사항이 부족하다는 것을 발견했습니다.
첫째: 수학자가 아닌 경우 모나드 라는 용어는 다소 모호합니다. 대체 용어는 실제로 유용한 것에 대해 좀 더 설명하는 계산 작성기입니다.
그것들은 작업을 연결하는 패턴입니다. 객체 지향 언어의 메서드 체인과 약간 비슷하지만 메커니즘이 약간 다릅니다.
패턴은 주로 함수형 언어(특히 모나드를 널리 사용하는 Haskell)에서 사용되지만 고차 함수(즉, 다른 함수를 인수로 사용할 수 있는 함수)를 지원하는 모든 언어에서 사용할 수 있습니다.
JavaScript의 배열은 패턴을 지원하므로 이를 첫 번째 예로 사용하겠습니다.
패턴의 요지는 함수를 인수로 사용하는 메서드가 있는 유형( Array
제공된 작업은 동일한 유형의 인스턴스를 반환해야 합니다(즉, Array
반환).
먼저 모나드 패턴을 사용 하지 않는 메소드 체이닝의 예:
[1,2,3].map(x => x + 1)
결과는 [2,3,4]
입니다. 코드는 모나드 패턴을 따르지 않습니다. 우리가 인수로 제공하는 함수가 배열이 아닌 숫자를 반환하기 때문입니다. 모나드 형식의 동일한 논리는 다음과 같습니다.
[1,2,3].flatMap(x => [x + 1])
Array
를 반환하는 연산을 제공하므로 이제 패턴을 따릅니다. flatMap
메서드는 배열의 모든 요소에 대해 제공된 함수를 실행합니다. 단일 값이 아닌 각 호출에 대한 결과로 배열을 예상하지만 결과 배열 집합을 단일 배열로 병합합니다. 따라서 최종 결과는 [2,3,4]
배열과 같습니다.
map
이나 flatMap
과 같은 메소드에 제공되는 함수 인수는 자바스크립트에서 종종 "콜백"이라고 합니다. 더 일반적이기 때문에 "작업"이라고 부르겠습니다.)
여러 작업을 연결하는 경우(전통적인 방식으로):
[1,2,3].map(a => a + 1).filter(b => b != 3)
배열의 결과 [2,4]
모나드 형식의 동일한 연결:
[1,2,3].flatMap(a => [a + 1]).flatMap(b => b != 3 ? [b] : [])
동일한 결과를 생성합니다. 배열 [2,4]
.
모나드 형식이 모나드가 아닌 형식보다 훨씬 못하다는 것을 즉시 알 수 있습니다! 이것은 모나드가 반드시 "좋은" 것은 아니라는 것을 보여줍니다. 그것들은 때로는 유익하고 때로는 그렇지 않은 패턴입니다.
모나드 패턴은 다른 방식으로 결합될 수 있습니다.
[1,2,3].flatMap(a => [a + 1].flatMap(b => b != 3 ? [b] : []))
여기서 바인딩은 연결되지 않고 중첩되지만 결과는 동일합니다. 이것은 나중에 보게 될 모나드의 중요한 속성입니다. 이는 두 가지 작업을 결합하여 단일 작업으로 동일하게 취급할 수 있음을 의미합니다.
연산은 다른 요소 유형을 가진 배열을 반환할 수 있습니다. 예를 들어 숫자 배열을 문자열 배열 또는 다른 것으로 변환합니다. 여전히 배열인 한.
이것은 Typescript 표기법을 사용하여 좀 더 형식적으로 설명할 수 있습니다. 배열의 유형은 Array<T>
이며, 여기서 T
는 배열의 요소 유형입니다. 메소드 flatMap()
형태의 함수의 인수 얻어 T => Array<U>
하고 다시 표시 Array<U>
.
일반화 모나드는 타입 Foo<Bar>
유형의 함수 인수 취하는 "바인딩"방법 갖는 Bar => Foo<Baz>
과 리턴 Foo<Baz>
.
이것은 모나드가 무엇인지 대답합니다. 이 답변의 나머지 부분에서는 모나드를 잘 지원하는 Haskell과 같은 언어에서 모나드가 유용한 패턴이 될 수 있는 이유를 예제를 통해 설명하려고 합니다.
Haskell과 Do 표기법
지도/필터 예제를 Haskell로 직접 번역하기 위해 flatMap
을 >>=
연산자로 바꿉니다.
[1,2,3] >>= \a -> [a+1] >>= \b -> if b == 3 then [] else [b]
>>=
연산자는 Haskell의 바인드 함수입니다. flatMap
과 동일하지만 다른 유형의 경우 다른 의미로 오버로드됩니다.
그러나 Haskell에는 모나드 표현식을 위한 전용 구문인 do
-block도 있습니다. 이 구문은 바인드 연산자를 완전히 숨깁니다.
do a <- [1,2,3] b <- [a+1] if b == 3 then [] else [b]
이렇게 하면 "배관"이 숨겨지고 각 단계에서 적용되는 실제 작업에 집중할 수 있습니다.
do
블록에서 각 행은 작업입니다. 제약 조건은 블록의 모든 작업이 동일한 유형을 반환해야 한다는 것을 여전히 유지합니다. 첫 번째 식은 목록이므로 다른 작업도 목록을 반환해야 합니다.
뒤로 화살표 <-
는 할당처럼 보이지만 바인드에서 전달된 매개변수입니다. 따라서 오른쪽의 표현식이 정수 목록일 때 왼쪽의 변수는 단일 정수이지만 목록의 각 정수에 대해 실행됩니다.
예: 안전한 탐색(아마도 유형)
목록에 대해서는 충분히 알고 모나드 패턴이 다른 유형에 어떻게 유용할 수 있는지 살펴보겠습니다.
일부 함수는 항상 유효한 값을 반환하지 않을 수 있습니다. Haskell에서 이것은 Some value
또는 Nothing
옵션인 Maybe
-type으로 표현됩니다.
항상 유효한 값을 반환하는 연결 작업은 물론 간단합니다.
streetName = getStreetName (getAddress (getUser 17))
그러나 함수 중 하나라도 Nothing
반환할 수 있다면 어떨까요? Nothing
이 아닌 경우에만 다음 함수에 값을 전달해야 합니다.
case getUser 17 of Nothing -> Nothing Just user -> case getAddress user of Nothing -> Nothing Just address -> getStreetName address
꽤 많은 반복 검사! 체인이 더 길다고 상상해보십시오. Maybe
대한 모나드 패턴으로 이 문제를 해결합니다.
do user <- getUser 17 addr <- getAddress user getStreetName addr
이것은 do
을 위해 - 블록 원용에게 바인드 기능을 Maybe
입력 (첫 번째 식의 결과가 있기 때문에 Maybe
). Just value
인 경우에만 다음 작업을 실행하고, 그렇지 않으면 그냥 Nothing
을 전달합니다.
여기서 모나드 패턴은 반복적인 코드를 피하기 위해 사용됩니다. 이것은 매크로가 매우 다른 방식으로 동일한 목표를 달성하지만 일부 다른 언어가 구문을 단순화하기 위해 매크로를 사용하는 방법과 유사합니다.
모나드 패턴과 Haskell의 모나드 친화적인 구문의 조합으로 코드가 더 깔끔해집니다. 모나드에 대한 특별한 구문 지원이 없는 JavaScript와 같은 언어에서는 이 경우 모나드 패턴이 코드를 단순화할 수 있을지 의심됩니다.
변경 가능한 상태
Haskell은 변경 가능한 상태를 지원하지 않습니다. 모든 변수는 상수이고 모든 값은 변경할 수 없습니다. 그러나 State
유형은 변경 가능한 상태로 프로그래밍을 에뮬레이트하는 데 사용할 수 있습니다.
add2 :: State Integer Integer add2 = do -- add 1 to state x <- get put (x + 1) -- increment in another way modify (+1) -- return state get evalState add2 7 => 9
add2
함수는 모나드 체인을 빌드한 다음 초기 상태로 7로 평가됩니다.
분명히 이것은 Haskell에서만 의미가 있는 것입니다. 다른 언어는 기본적으로 변경 가능한 상태를 지원합니다. Haskell은 일반적으로 언어 기능에 대해 "선택"합니다. 필요할 때 변경 가능한 상태를 활성화하고 유형 시스템은 효과가 명시적임을 보장합니다. IO는 이것의 또 다른 예입니다.
IO
IO
유형은 "불순한" 기능을 연결하고 실행하는 데 사용됩니다.
putStrLine
, readLine
등 외부 세계와 인터페이스하는 내장 함수가 많이 있습니다. 이러한 함수는 부작용을 일으키거나 비결정적 결과를 가져오기 때문에 "순수하지 않음"이라고 합니다. 시간을 가져오는 것과 같은 간단한 작업도 결과가 비결정적이기 때문에 불순한 것으로 간주됩니다. 동일한 인수로 두 번 호출하면 다른 값이 반환될 수 있습니다.
순수 함수는 결정적입니다. 그 결과는 순전히 전달된 인수에 따라 달라지며 값을 반환하는 것 외에 환경에 부작용이 없습니다.
Haskell은 순수 함수의 사용을 강력하게 권장합니다. 이것이 언어의 주요 판매 포인트입니다. 불행히도 순수주의자에게는 유용한 작업을 수행하기 위해 일부 불순한 함수가 필요합니다. Haskell 절충안은 순수와 순수를 완전히 분리하고 순수 함수가 순수 함수가 직접 또는 간접적으로 순수 함수를 실행할 수 있는 방법이 없음을 보장하는 것입니다.
이것은 모든 불순한 함수에 IO
유형을 제공함으로써 보장됩니다. Haskell 프로그램의 진입점 IO
유형을 갖는 main
함수이므로 최상위 수준에서 불순한 함수를 실행할 수 있습니다.
그러나 언어는 순수 함수가 순수 함수를 실행하는 것을 어떻게 방지합니까? 이것은 Haskell의 게으른 특성 때문입니다. 함수는 출력이 다른 함수에 의해 소비되는 경우에만 실행됩니다. IO
main
에 할당하는 것 외에는 소비할 방법이 없습니다. 따라서 함수가 불순한 함수를 실행하려면 main
IO
유형을 가져야 합니다.
IO 작업에 모나드 체인을 사용하면 명령형 언어의 명령문과 마찬가지로 선형적이고 예측 가능한 순서로 실행됩니다.
이것은 대부분의 사람들이 Haskell로 작성할 첫 번째 프로그램을 제공합니다.
main :: IO () main = do putStrLn ”Hello World”
do
키워드는 단일 작업만 있고 바인딩할 항목이 없는 경우 불필요하지만 일관성을 위해 유지합니다.
()
유형은 "무효"를 의미합니다. 이 특별한 반환 유형은 부작용에 대해 호출된 IO 함수에만 유용합니다.
더 긴 예:
main = do putStrLn "What is your name?" name <- getLine putStrLn "hello" ++ name
이것은 일련의 IO
작업을 main
기능에 할당되므로 실행됩니다.
IO
를 Maybe
와 비교하면 모나드 패턴의 다양성을 알 수 있습니다. Maybe
의 경우 패턴은 조건부 논리를 바인딩 함수로 이동하여 반복적인 코드를 방지하는 데 사용됩니다. IO
IO
유형의 모든 작업이 순서가 지정되고 IO
작업이 순수 기능으로 "유출"되지 않도록 하는 데 사용됩니다.
합산
내 주관적인 의견으로는 모나드 패턴은 패턴에 대한 일부 내장 지원이 있는 언어에서만 정말 가치가 있습니다. 그렇지 않으면 지나치게 복잡한 코드로 이어집니다. 그러나 Haskell(및 일부 다른 언어)에는 지루한 부분을 숨기는 내장 지원이 있으며 패턴은 다양한 유용한 용도로 사용될 수 있습니다. 좋다:
Maybe
)IO
)Parser
)"모나드란 무엇인가"를 설명하는 것은 "숫자가 무엇입니까?"라고 말하는 것과 약간 비슷합니다. 우리는 항상 숫자를 사용합니다. 그러나 숫자에 대해 전혀 모르는 사람을 만났다고 상상해 보십시오. 도대체 무슨 숫자인지 어떻게 설명할 수 있겠습니까? 그리고 그것이 왜 유용할 수 있는지 어떻게 설명하기 시작하겠습니까?
모나드란? 짧은 대답: 작업을 함께 연결하는 특정 방법입니다.
본질적으로 실행 단계를 작성하고 "바인드 기능"과 함께 연결합니다. >>=
라고 이름이 지정되었습니다.) bind 연산자에 대한 호출을 직접 작성하거나 컴파일러가 해당 함수 호출을 삽입하도록 하는 구문 설탕을 사용할 수 있습니다. 그러나 어느 쪽이든 각 단계는 이 바인드 함수에 대한 호출로 구분됩니다.
따라서 bind 함수는 세미콜론과 같습니다. 프로세스의 단계를 분리합니다. bind 함수의 작업은 이전 단계의 출력을 가져와 다음 단계로 전달하는 것입니다.
너무 어렵게 들리지 않죠? 그러나 모나드에는 한 가지 이상의 종류가 있습니다. 왜요? 어떻게?
바인드 함수 는 한 단계의 결과를 가져와 다음 단계에 전달할 수 있습니다. 그러나 그것이 "모든" 모나드가 하는 것이라면... 실제로는 그다지 유용하지 않습니다. 그리고 그 이해하는 것이 중요합니다 : 모든 유용한 모나드는 모나드 것 외에 다른 작업을 수행. 모든 유용한 모나드는 그것을 독특하게 만드는 "특별한 힘"을 가지고 있습니다.
(특별한 기능을 하지 않는 모나드를 "identity 모나드"라고 합니다. 이것은 마치 항등 함수처럼 완전히 무의미한 것처럼 들리지만 실제로는 그렇지 않습니다... 하지만 그건 또 다른 이야기입니다™.)
기본적으로 각 모나드는 고유한 바인드 기능 구현을 가지고 있습니다. 그리고 실행 단계 사이에 엉뚱한 일을 하도록 바인드 함수를 작성할 수 있습니다. 예를 들어:
각 단계가 성공/실패 표시기를 반환하는 경우 이전 단계가 성공한 경우에만 bind가 다음 단계를 실행하도록 할 수 있습니다. 이런 식으로 실패한 단계는 조건부 테스트 없이 전체 시퀀스를 "자동으로" 중단합니다. ( 실패 모나드 .)
이 아이디어를 확장하여 "예외"를 구현할 수 있습니다. ( Error Monad 또는 Exception Monad .) 언어 기능이 아니라 직접 정의하기 때문에 작동 방식을 정의할 수 있습니다. (예를 들어, 처음 두 예외를 무시하고 세 번째 예외가 발생했을 때만 중단하고 싶을 수도 있습니다.)
각 단계가 여러 결과를 반환하도록 하고, 바인드 기능이 루프를 돌도록 하여 각 단계를 다음 단계로 제공할 수 있습니다. 이런 식으로 여러 결과를 처리할 때 계속해서 루프를 작성할 필요가 없습니다. 바인드 기능은 "자동으로" 모든 작업을 수행합니다. ( 목록 모나드 .)
한 단계에서 다른 단계로 "결과"를 전달할 뿐만 아니라 bind 함수가 추가 데이터도 전달하도록 할 수 있습니다. 이 데이터는 이제 소스 코드에 표시되지 않지만 모든 기능에 수동으로 전달할 필요 없이 어디서나 액세스할 수 있습니다. ( 리더 모나드 .)
"추가 데이터"를 대체할 수 있도록 만들 수 있습니다. 이렇게 하면 실제로 파괴적인 업데이트를 수행하지 않고도 파괴적인 업데이트 를 시뮬레이션할 수 있습니다. ( State Monad 와 그 사촌 Writer Monad .)
파괴적인 업데이트만 시뮬레이션 하기 때문에 실제 파괴적인 업데이트로는 불가능한 일을 사소하게 할 수 있습니다. 예를 들어 마지막 업데이트를 실행 취소 하거나 이전 버전으로 되돌릴 수 있습니다.
계산을 일시 중지 할 수 있는 모나드를 만들 수 있으므로 프로그램을 일시 중지하고 내부 상태 데이터를 수정한 다음 다시 시작할 수 있습니다.
"continuations"를 모나드로 구현할 수 있습니다. 이것은 당신이 사람들의 마음을 깰 수 있습니다!
이 모든 것 이상은 모나드로 가능합니다. 물론 이 모든 것은 모나드 없이도 완벽하게 가능합니다. 모나드를 사용하는 것이 훨씬 쉽습니다.
사실 모나드에 대한 일반적인 이해와 달리 국가와 관련이 없습니다. 모나드는 단순히 물건을 래핑하고 래핑을 풀지 않고 래핑된 물건에 대한 작업을 수행하는 방법을 제공하는 방법입니다.
예를 들어 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 (fx)
그것이 이해해야 할 전부입니다. 그러나 이 리프팅 을 수행하는 보다 일반적인 기능이 있음이 밝혀졌습니다. 이는 bind
.
bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b) bind f (Wrap x) = fx
bind
fmap
보다 조금 더 많은 일을 할 수 있지만 그 반대는 불가능합니다. 사실 fmap
bind
와 return
만 정의할 수 있습니다. 따라서 모나드를 정의할 때 .. 유형을 제공 Wrapped a
return
및 bind
작업이 작동하는 방식을 말합니다.
멋진 점은 이것이 모든 곳에서 나타나는 일반적인 패턴으로 밝혀졌으며 순수한 방식으로 상태를 캡슐화하는 것은 그 중 하나일 뿐입니다.
Haskell의 IO 모나드에서 사용되는 것처럼 모나드를 사용하여 기능적 종속성을 도입하고 평가 순서를 제어하는 방법에 대한 좋은 기사는 IO Inside를 확인하십시오.
모나드를 이해하는 것과 관련하여 너무 걱정하지 마십시오. 그들에 대해 읽고 흥미롭게 생각하고 즉시 이해하지 못하더라도 걱정하지 마십시오. 그런 다음 Haskell과 같은 언어로 다이빙하는 것이 좋습니다. 모나드는 연습을 통해 이해가 뇌에 조금씩 스며드는 이러한 것들 중 하나입니다. 어느 날 갑자기 이해하고 있다는 것을 깨닫게 됩니다.
sigfpe 말한다:
그러나 이 모든 것은 모나드를 설명이 필요한 난해한 것으로 소개합니다. 그러나 내가 주장하고 싶은 것은 그것들이 전혀 난해하지 않다는 것입니다. 사실, 함수형 프로그래밍의 다양한 문제에 직면했을 때, 당신은 필연적으로 모나드의 예인 특정 솔루션으로 이끌렸을 것입니다. 사실, 아직 발명하지 않았다면 지금 바로 발명할 수 있기를 바랍니다. 그런 다음 이러한 모든 솔루션이 사실상 동일한 솔루션을 위장한 것임을 알아차리는 것은 작은 단계입니다. 그리고 이 글을 읽고 나면, 당신이 보는 모든 것을 이미 발명한 것으로 인식할 것이기 때문에 모나드에 대한 다른 문서를 더 잘 이해할 수 있을 것입니다.
모나드가 해결하려고 하는 많은 문제는 부작용 문제와 관련이 있습니다. 그래서 우리는 그들과 함께 시작할 것입니다. (모나드를 사용하면 부작용을 처리하는 것 이상을 할 수 있습니다. 특히 많은 유형의 컨테이너 객체를 모나드로 볼 수 있습니다. 모나드에 대한 소개 중 일부는 모나드의 이 두 가지 다른 용도를 조화시키고 하나 또는 다른.)
C++와 같은 명령형 프로그래밍 언어에서 함수는 수학의 함수처럼 작동하지 않습니다. 예를 들어 단일 부동 소수점 인수를 사용하고 부동 소수점 결과를 반환하는 C++ 함수가 있다고 가정합니다. 표면적으로는 실수를 실수로 매핑하는 수학 함수처럼 보일 수 있지만 C++ 함수는 인수에 따라 숫자를 반환하는 것 이상을 수행할 수 있습니다. 전역 변수의 값을 읽고 쓸 수 있으며 화면에 출력을 쓰고 사용자로부터 입력을 받을 수 있습니다. 그러나 순수한 함수형 언어에서 함수는 인수에서 제공된 내용만 읽을 수 있으며 세상에 영향을 줄 수 있는 유일한 방법은 반환하는 값을 통해서입니다.
>>=
(일명 bind
) 및 return
(일명 unit
)의 두 가지 연산이 있는 데이터 유형입니다. return
은 임의의 값을 가져와서 모나드의 인스턴스를 만듭니다. >>=
는 모나드의 인스턴스를 가져와서 그 위에 함수를 매핑합니다. (대부분의 프로그래밍 언어에서 임의의 값을 가져와서 유형을 생성하는 함수를 작성할 수 없기 때문에 모나드가 이상한 종류의 데이터 유형임을 이미 알 수 있습니다. 모나드는 일종의 매개변수 다형성을 사용 합니다.)
Haskell 표기법에서 모나드 인터페이스는 다음과 같이 작성됩니다.
class Monad m where return :: a -> ma (>>=) :: forall ab . ma -> (a -> mb) -> mb
이러한 연산은 특정 "법칙"을 준수해야 하지만 그다지 중요하지 않습니다. "법률"은 연산의 합리적인 구현이 작동해야 하는 방식을 성문화합니다(기본적으로 >>=
및 return
은 값이 변환되는 방식에 대해 동의해야 합니다. 모나드 인스턴스에 추가하고 >>=
는 연관됨).
모나드는 상태 및 I/O에 관한 것만이 아닙니다. 상태, I/O, 예외 및 비결정론에 대한 작업을 포함하는 일반적인 계산 패턴을 추상화합니다. 아마도 가장 이해하기 쉬운 모나드는 목록과 옵션 유형일 것입니다:
instance Monad [ ] where [] >>= k = [] (x:xs) >>= k = kx ++ (xs >>= k) return x = [x] instance Monad Maybe where Just x >>= k = kx Nothing >>= k = Nothing return x = Just x
여기서 []
및 :
는 목록 생성자, ++
는 연결 연산자, Just
및 Nothing
은 Maybe
생성자입니다. 이 두 모나드는 각각의 데이터 유형에 대한 일반적이고 유용한 계산 패턴을 캡슐화합니다(둘 다 부작용이나 I/O와 관련이 없음을 유의하십시오).
모나드가 무엇인지, 왜 유용한지 이해하려면 사소하지 않은 Haskell 코드를 작성해야 합니다.
먼저 functor가 무엇인지 이해해야 합니다. 그 전에 고차 함수를 이해하십시오.
고차 함수 는 단순히 함수를 인수로 취하는 함수입니다.
펑터 어떤 형 건설 인 T
, 그것이 호출 고차 함수가 존재하는 map
변환 형의 함수 것을 a -> b
(두 종류의 특정 및 a
b
함수로) T a -> T b
. 이 map
p
및 q
(하스켈 표기법)에 대해 true를 반환하도록 항등 및 구성 법칙도 준수해야 합니다.
map id = id map (p . q) = map p . map q
List
라는 유형 생성자는 위의 법칙을 준수하는 (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
은 연관되어야 합니다. [[[a]]]
유형 x
가 있는 경우 join (join x)
join (map join x)
과 같아야 합니다. 그리고 pure
에 대한 정체성해야이 join
하는 등 join (pure x) == x
.
[면책 조항: 나는 여전히 모나드를 완전히 grok하려고 노력하고 있습니다. 다음은 지금까지 내가 이해한 것입니다. 틀리면 잘 아는 사람이 카페에서 저를 부를 것입니다.]
Arnar는 다음과 같이 썼습니다.
모나드는 단순히 물건을 래핑하고 래핑을 풀지 않고 래핑된 물건에 대한 작업을 수행하는 방법을 제공하는 방법입니다.
바로 그것입니다. 아이디어는 다음과 같습니다.
당신은 어떤 종류의 가치를 취하고 그것을 몇 가지 추가 정보로 포장합니다. 값이 특정 종류(예: 정수 또는 문자열)인 것처럼 추가 정보도 특정 종류입니다.
예를 들어, 추가 정보는 Maybe
또는 IO
일 수 있습니다.
그런 다음 추가 정보를 전달하면서 래핑된 데이터에 대해 작업할 수 있는 일부 연산자가 있습니다. 이러한 연산자는 추가 정보를 사용하여 래핑된 값에 대한 작업 동작을 변경하는 방법을 결정합니다.
예를 들어, Maybe Int
Just Int
또는 Nothing
수 있습니다. 이제 당신이 추가하면, Maybe Int
A와 Maybe Int
, 운영자들이 모두 있는지 확인합니다 Just Int
의 내부, 그래서 경우 랩을 해제 Int
,들 그들에게 또한 연산자를 통과, 그 결과 재 - 포장 Int
새로운 Just Int
(유효한 Maybe Int
)로 변환하여 Maybe Int
를 반환합니다. 그러나 그 중 하나가 Nothing
이면 이 연산자는 즉시 Nothing
반환합니다. 이는 다시 유효한 Maybe Int
입니다. Maybe Int
가 그냥 일반 숫자인 것처럼 가장하고 일반 수학을 수행할 수 있습니다. Nothing
을 얻는다면 방정식은 여전히 올바른 결과를 산출할 것입니다. Nothing
곳에나 없는 검사를 할 필요가 없습니다.
그러나 예는 Maybe
일어나는 일입니다. 추가 정보는라면 IO
에 대해 정의 특수 연산자 그 IO
의 대신 호출 할 것, 그리고 추가를 수행하기 전에 완전히 다른 무언가를 할 수 있습니다. (OK, 두 개의 IO Int
함께 추가하는 것은 아마도 무의미할 것입니다. 아직 확실하지 않습니다.) (또한, Maybe
예제에 주의를 기울이면 "값을 추가 항목으로 래핑"하는 것이 항상 올바른 것은 아님을 알 수 있습니다. 그러나 불가해하지 않고는 정확하고 정확하며 정확하기는 어렵다.)
기본적으로 "모나드"는 대략 "패턴"을 의미 합니다. 그러나 비공식적으로 설명되고 구체적으로 이름이 지정된 Patterns로 가득 찬 책 대신 이제 새 패턴을 프로그램의 항목 으로 선언할 수 있는 언어 구성( 구문 및 모든 것)이 있습니다. (여기서 부정확성은 모든 패턴이 특정 형식을 따라야 하므로 모나드는 패턴만큼 일반적이지 않습니다. 그러나 이것이 대부분의 사람들이 알고 이해하는 가장 가까운 용어라고 생각합니다.)
이것이 사람들이 모나드를 혼동하는 이유입니다. 왜냐하면 모나드는 일반적인 개념이기 때문입니다. 무엇이 무언가를 모나드로 만드는지 묻는 것은 무엇이 무언가를 패턴으로 만드는지 묻는 것과 유사하게 모호합니다.
그러나 패턴 아이디어에 대한 언어의 구문 지원이 갖는 의미를 생각해 보십시오. Gang of Four 책을 읽고 특정 패턴의 구성을 암기하는 대신 불가지론자에서 이 패턴을 구현하는 코드를 작성하기만 하면 됩니다. 일반적인 방법으로 한 번만 수행하면 완료됩니다! 그런 다음 반복해서 다시 구현할 필요 없이 코드에서 작업을 장식하기만 하면 방문자, 전략 또는 파사드와 같은 이 패턴을 재사용할 수 있습니다!
그래서 이것이 모나드를 이해하는 사람들이 모나드를 매우 유용하다고 생각하는 이유입니다. 지식인이 이해하는 것을 자랑스럽게 여기는 상아탑 개념이 아니라(물론 그것도 물론입니다), 실제로는 코드를 더 간단하게 만듭니다.
많은 노력 끝에 마침내 모나드를 이해하게 된 것 같습니다. 압도적으로 많은 투표를 받은 답변에 대한 내 자신의 긴 비판을 다시 읽은 후 이 설명을 제공하겠습니다.
모나드를 이해하려면 다음 세 가지 질문에 답해야 합니다.
내 원래 의견에서 언급했듯이 너무 많은 모나드 설명이 질문 2 또는 질문 1을 실제로 적절하게 다루기 전에 질문 번호 3에 갇히게 됩니다.
왜 모나드가 필요합니까?
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
모나드는 어떻게 구현됩니까?
다른 답변을 참조하십시오. 그 세부 사항으로 뛰어드는 것은 매우 자유롭습니다.
몇 년 전에 이 질문에 대한 답변을 제공한 후 다음을 통해 답변을 개선하고 단순화할 수 있다고 생각합니다.
모나드는 구성 중 입력을 사전 처리하기 위해 bind
사용하여 일부 입력 시나리오에 대한 처리를 외부화하는 함수 구성 기술입니다.
일반 합성에서 함수 compose (>>)
는 합성된 함수를 선행 작업의 결과에 순서대로 적용하는 데 사용됩니다. 중요한 것은 구성되는 함수가 입력의 모든 시나리오를 처리하는 데 필요하다는 것입니다.
(x -> y) >> (y -> z)
이 디자인은 관련 상태를 더 쉽게 조사할 수 있도록 입력을 재구성하여 개선할 수 있습니다. 따라서 단순히 y
Mb
가 될 수 있습니다(예: y
에 유효성 개념이 포함되어 있으면 (is_OK, b)
예를 들어, 입력은 할 수 구조 조정에 유형, 대신에 충실 번호를 포함하거나 할 수없는 문자열을 반환하는 경우에만 가능 숫자입니다 bool
같은 튜플의 유효 숫자의 존재와 수를 나타내는 bool * float
. 구성된 함수는 이제 숫자가 존재하는지 여부를 결정하기 위해 입력 문자열을 구문 분석할 필요가 없지만 튜플 bool
(Ma -> Mb) >> (Mb -> Mc)
여기서도 합성은 compose
을 사용하여 자연스럽게 발생하므로 각 함수는 입력의 모든 시나리오를 개별적으로 처리해야 하지만 그렇게 하는 것이 훨씬 쉬워졌습니다.
그러나 시나리오 처리가 일상적인 시간에 대한 심문 노력을 외부화할 수 있다면 어떨까요? is_OK
가 false
때와 같이 입력이 정상이 아닐 때 우리 프로그램이 아무것도 하지 않는다면 어떻게 될까요? 이 작업이 완료되면 구성된 함수가 해당 시나리오 자체를 처리할 필요가 없어 코드를 극적으로 단순화하고 다른 수준의 재사용에 영향을 줍니다.
이 구체화 우리 함수를 사용할 수 달성하기 위해 bind (>>=)
는 수행하는 composition
대신 compose
. 따라서 단순히 한 함수의 출력에서 다른 입력으로 값을 전송하는 대신 Bind
는 Ma
M
부분을 a
에 적용할지 여부와 적용 방법을 결정합니다. 물론 bind
함수는 구조를 검사하고 원하는 애플리케이션 유형을 수행할 수 있도록 M
대해 특별히 정의됩니다. 그럼에도 불구하고, a
이후 무엇이든 될 수 bind
단순히 전달 a
그것이 필요한 애플리케이션 결정할 때 합성 함수에서는 미. 또한 구성된 함수 자체는 더 이상 M
부분을 처리할 필요가 없으므로 단순화합니다. 따라서...
(a -> Mb) >>= (b -> Mc)
또는 더 간결하게 Mb >>= (b -> Mc)
간단히 말해서, 모나드는 외부화하여 입력이 충분히 노출되도록 설계되면 특정 입력 시나리오의 처리에 대한 표준 동작을 제공합니다. 이 디자인은 쉘이 구성된 기능의 응용 프로그램과 관련된 데이터를 포함하고 bind
기능에 shell and content
따라서 모나드는 세 가지입니다.
M
bind
a -> Mb
형식의 구성 가능한 함수는 모나딕 관리 데이터를 포함하는 결과를 생성합니다. 일반적으로 말하면, 함수에 대한 입력은 오류 조건과 같은 것을 포함할 수 있는 출력보다 훨씬 더 제한적입니다. 따라서 Mb
결과 구조는 일반적으로 매우 유용합니다. 예를 들어, 나눗셈 연산자는 제수가 0
때 숫자를 반환하지 않습니다.
또한 monad
에는 적용 후 결과를 래핑하여 값 a
를 모나딕 유형 Ma
로 래핑하는 랩 함수와 일반 함수 a -> b
를 모나딕 함수 a -> Mb
로 래핑하는 랩 함수가 포함될 수 있습니다. bind
와 마찬가지로 이러한 랩 함수는 M
에만 해당됩니다. 예:
let return a = [a] let lift fa = return (fa)
bind
함수의 디자인은 변경할 수 없는 데이터 구조와 순수 함수를 가정하고 다른 것들은 복잡해지며 보장할 수 없습니다. 이와 같이 모나딕 법칙이 있습니다.
주어진...
M_ return = (a -> Ma) f = (a -> Mb) g = (b -> Mc)
그 다음에...
Left Identity : (return a) >>= f === fa Right Identity : Ma >>= return === Ma Associative : Ma >>= (f >>= g) === Ma >>= ((fun x -> fx) >>= g)
Associativity
bind
가 적용되는 시점에 관계없이 bind
의미합니다. 즉,의 정의에 Associativity
위 괄호의 힘 초기 평가 binding
의 f
와 g
단지 필요한 함수 초래할 것이다 Ma
완전한 위해서 bind
. Ma
의 평가는 f
적용되고 그 결과가 g
적용되기 전에 결정되어야 합니다.
모나드는 사실상 "유형 연산자"의 한 형태입니다. 그것은 세 가지를 할 것입니다. 먼저 한 유형의 값을 다른 유형(일반적으로 "모나딕 유형"이라고 함)으로 "래핑"(또는 변환)합니다. 두 번째로 모나딕 유형에서 사용할 수 있는 기본 유형에서 모든 작업(또는 함수)을 사용할 수 있습니다. 마지막으로 자신을 다른 모나드와 결합하여 합성 모나드를 생성하는 지원을 제공합니다.
"maybe monad"는 본질적으로 Visual Basic/C#의 "nullable 형식"과 동일합니다. nullable이 아닌 형식 "T"를 사용하여 "Nullable<T>"로 변환한 다음 Nullable<T>에서 모든 이진 연산자가 의미하는 바를 정의합니다.
부작용도 유사하게 나타납니다. 함수의 반환 값과 함께 부작용에 대한 설명을 포함하는 구조가 생성됩니다. 그런 다음 "들어올린" 작업은 함수 간에 값이 전달될 때 부작용을 복사합니다.
다음과 같은 몇 가지 이유로 "유형 연산자"의 이해하기 쉬운 이름이 아닌 "모나드"라고 합니다.
(모나드란 무엇입니까? 의 답변도 참조하십시오.)
Monads에 대한 좋은 동기는 sigfpe(Dan Piponi)의 You could Have Invented Monads입니다! (그리고 어쩌면 당신은 이미 가지고 있습니다) . 다른 많은 모나드 튜토리얼이 있으며, 그 중 많은 것들이 다양한 비유를 사용하여 "간단한 용어"로 모나드를 잘못 설명하려고 합니다. 이것이 모나드 튜토리얼의 오류입니다 . 그들을 피하십시오.
DR MacIver가 Tell us Why your language 에서 말했듯이 :
그래서 내가 Haskell에 대해 싫어하는 것:
명백한 것부터 시작합시다. 모나드 튜토리얼. 아니요, 모나드가 아닙니다. 특히 튜토리얼. 그것들은 끝이 없고, 과장되고, 신이시여, 그들은 지루합니다. 게다가, 나는 그들이 실제로 도움이 된다는 설득력 있는 증거를 본 적이 없습니다. 클래스 정의를 읽고, 코드를 작성하고, 무서운 이름을 극복하십시오.
당신은 Maybe 모나드를 이해한다고 말합니까? 좋아, 가는 중이야. 다른 모나드를 사용하기 시작하면 조만간 모나드가 일반적으로 무엇인지 이해할 수 있을 것입니다.
[수학적 지향적이라면 수십 개의 튜토리얼을 무시하고 정의를 배우거나 범주 이론의 강의를 따르기를 원할 수 있습니다.) 정의의 주요 부분은 Monad M이 각각에 대해 정의하는 "유형 생성자"를 포함한다는 것입니다. 기존 유형 "T" 새로운 유형 "MT" 및 "일반" 유형과 "M" 유형 사이를 오가는 몇 가지 방법.]
또한 놀랍게도 모나드에 대한 최고의 소개 중 하나는 실제로 모나드를 소개한 초기 학술 논문 중 하나인 Philip Wadler의 함수형 프로그래밍을 위한 모나드입니다. 실제로 많은 인공 튜토리얼과 달리 실용적이고 사소하지 않은 동기 부여 예제가 있습니다.
모나드는 데이터에 대한 추상 데이터 유형이 무엇인지 흐름을 제어하기 위한 것입니다.
다시 말해, 많은 개발자는 집합, 목록, 사전(또는 해시 또는 지도) 및 트리에 대한 아이디어에 익숙합니다. 이러한 데이터 유형 내에는 많은 특별한 경우가 있습니다(예: InsertionOrderPreservingIdentityHashMap).
그러나 프로그램 "흐름"에 직면했을 때 많은 개발자는 if, switch/case, do, while, goto(grr) 및 (아마도) 클로저보다 더 많은 구문에 노출되지 않았습니다.
따라서 모나드는 단순히 제어 흐름 구조입니다. 모나드를 대체하는 더 좋은 문구는 '제어 유형'입니다.
이와 같이 모나드는 제어 논리, 명령문 또는 기능을 위한 슬롯을 가지고 있습니다. 데이터 구조에서 이에 상응하는 것은 일부 데이터 구조에서 데이터를 추가하고 제거할 수 있다는 것입니다.
예를 들어 "if" 모나드는 다음과 같습니다.
if( clause ) then block
가장 단순하게 두 개의 슬롯이 있습니다. 절과 블록입니다. if
모나드는 일반적으로 절의 결과를 평가하기 위해 빌드되고, false가 아니면 블록을 평가합니다. 많은 개발자가 'if'를 배울 때 모나드를 소개하지 않으며 효과적인 논리를 작성하기 위해 모나드를 이해할 필요가 없습니다.
모나드는 데이터 구조가 더 복잡해질 수 있는 것과 같은 방식으로 더 복잡해질 수 있지만 유사한 의미를 가질 수 있지만 구현 및 구문이 다른 광범위한 범주의 모나드가 있습니다.
물론 데이터 구조가 반복되거나 순회되는 것과 같은 방식으로 모나드가 평가될 수 있습니다.
컴파일러는 사용자 정의 모나드를 지원하거나 지원하지 않을 수 있습니다. Haskell은 확실히 그렇습니다. 모나드라는 용어는 언어에서 사용되지 않지만 Ioke에는 몇 가지 유사한 기능이 있습니다.
내가 가장 좋아하는 모나드 튜토리얼:
http://www.haskell.org/haskellwiki/All_About_Monads
(Google 검색에서 "monad tutorial"에 대한 170,000번의 조회수 중!)
@Stu: 모나드의 요점은 (보통) 순차 의미 체계를 순수 코드에 추가할 수 있도록 하는 것입니다. (Monad Transformers를 사용하여) 모나드를 구성하고 오류 처리, 공유 상태 및 로깅을 사용한 구문 분석과 같은 더 흥미롭고 복잡한 결합 의미를 얻을 수도 있습니다. 이 모든 것이 순수 코드에서 가능하며, 모나드는 그것을 추상화하고 모듈식 라이브러리에서 재사용할 수 있게 해줄 뿐만 아니라(항상 프로그래밍에서 우수함) 필수적인 것처럼 보이게 하는 편리한 구문을 제공합니다.
Haskell에는 이미 연산자 오버로딩이 있습니다[1]: Java 또는 C#에서 인터페이스를 사용할 수 있는 방식으로 유형 클래스를 사용하지만 Haskell은 + && 및 >와 같은 영숫자가 아닌 토큰도 중위 식별자로 허용합니다. "세미콜론 오버로딩"[2]을 의미하는 경우 보는 방식으로 연산자 오버로딩입니다. 흑마법사처럼 들리고 "세미콜론 과부하"(진취적인 Perl 해커가 이 아이디어를 알고 있는 그림)에 문제를 제기하는 것처럼 들리지만 요점은 순수 함수형 코드가 명시적 시퀀싱을 요구하거나 허용하지 않기 때문에 모나드가 없으면 세미콜론이 없다는 것입니다.
이 모든 것이 필요한 것보다 훨씬 더 복잡하게 들립니다. sigfpe의 기사는 꽤 훌륭하지만 Haskell을 사용하여 설명합니다. Haskell을 이해하여 Monad를 grok하고 Monad를 이해하여 Haskell을 이해하는 닭고기와 계란 문제를 해결하는 데 실패했습니다.
[1] 이것은 모나드와는 별개의 문제이지만 모나드는 Haskell의 연산자 오버로딩 기능을 사용합니다.
[2] 이것은 또한 모나딕 동작을 연결하는 연산자가 >>=("바인드"로 발음됨)이지만 중괄호와 세미콜론 및/또는 들여쓰기 및 줄 바꿈을 사용할 수 있는 구문 설탕("do")이 있기 때문에 지나치게 단순화합니다.
나는 여전히 모나드를 처음 접하지만 읽기에 정말 좋은 링크를 공유할 것이라고 생각했습니다(그림과 함께!!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ monads-for-the-layman/ (제휴)
기본적으로 이 기사에서 얻은 따뜻하고 모호한 개념은 모나드는 기본적으로 이질적인 기능이 구성 가능한 방식으로 작동하도록 하는 어댑터라는 개념이었습니다. 유형 등이 있습니다. 그래서 BIND 함수는 우리가 이러한 어댑터를 만들려고 할 때 사과를 사과로, 오렌지를 오렌지로 유지하는 역할을 합니다. 그리고 LIFT 기능은 "하위 수준" 기능을 가져오고 BIND 기능과 함께 작동하고 구성 가능하도록 "업그레이드"하는 역할을 합니다.
내가 옳았으면 좋겠고, 더 중요한 것은 이 기사에 모나드에 대한 유효한 견해가 있기를 바랍니다. 이 글은 모나드에 대해 더 배우고 싶은 욕구를 불러일으키는 데 도움이 되었습니다.
저는 최근에 다른 방식으로 Monads를 생각하고 있습니다. 나는 그것들을 수학적 방식으로 실행 순서 를 추상화하여 새로운 종류의 다형성을 가능하게 하는 것으로 생각했습니다.
명령형 언어를 사용하고 일부 표현식을 순서대로 작성하면 코드는 항상 그 순서대로 정확하게 실행됩니다.
그리고 간단한 경우에, 모나드를 사용할 때 동일한 느낌이 듭니다. 순서대로 발생하는 표현식 목록을 정의합니다. 사용하는 모나드에 따라 코드가 순서대로(IO 모나드에서와 같이) 한 번에 여러 항목에 대해 병렬로 실행될 수 있다는 점을 제외하고(List 모나드에서와 같이) 중간에 멈출 수 있습니다(May 모나드에서와 같이) , 나중에 다시 시작하기 위해 중간에 일시 중지될 수도 있고(Resumption 모나드에서와 같이), 되감고 처음부터 시작할 수도 있습니다(트랜잭션 모나드에서처럼), 또는 다른 옵션을 시도하기 위해 중간에 되감기할 수도 있습니다(Logic 모나드에서와 같이). .
그리고 모나드는 다형성이기 때문에 필요에 따라 다른 모나드에서 동일한 코드를 실행할 수 있습니다.
또한 어떤 경우에는 모나드를 함께 결합하여(모나드 변환기와 함께) 동시에 여러 기능을 얻을 수 있습니다.
모나드는 은유 가 아니지만 Daniel Spiewak이 설명하는 것처럼 공통 패턴에서 나오는 실질적으로 유용한 추상화입니다.
{-# 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) = fx
함수의 응용 연산자 $
forall a b. a -> b
정식으로 정의됨
($) :: (a -> b) -> a -> b f $ x = fx infixr 0 $
Haskell 원시 함수 응용 프로그램 fx
( infixl 10
) 측면에서.
구성 .
$
로 다음과 같이 정의됩니다.
(.) :: (b -> c) -> (a -> b) -> (a -> c) f . g = \ x -> f $ gx infixr 9 .
forall fg 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
는 오른쪽 및 왼쪽 ID입니다.
프로그래밍에서 모나드는 모나드 유형 클래스의 인스턴스가 있는 펑터 유형 생성자입니다. 정의 및 구현에는 몇 가지 동등한 변형이 있으며, 각각은 모나드 추상화에 대해 약간 다른 직관을 가지고 있습니다.
펑터는 타입 생성자 인 f
가지 * -> *
펑 형 클래스의 인스턴스.
{-# LANGUAGE KindSignatures #-} class Functor (f :: * -> *) where map :: (a -> b) -> (fa -> fb)
정적으로 시행되는 유형 프로토콜을 따르는 것 외에도 functor 유형 클래스의 인스턴스는 forall f g.
대한 대수적 함수자 법칙을 따라야 합니다.
map id = id :: ft -> ft Identity map f . map g = map (f . g) :: fa -> fc Composition / short cut fusion
Functor 계산 에는 다음 유형이 있습니다.
forall f t. Functor f => ft
계산 cr
은 컨텍스트 c
내의 결과 r
로 구성됩니다.
단항 모나딕 함수 또는 Kleisli 화살표 에는 다음 유형이 있습니다.
forall ma b. Functor m => a -> mb
Kleisi 화살표는 하나의 인수 a
를 취하고 모나딕 계산 mb
반환하는 함수입니다.
모나드는 Kleisli triple forall m. Functor m =>
관점에서 정식으로 정의됩니다. forall m. Functor m =>
(m, return, (=<<))
유형 클래스로 구현
class Functor m => Monad m where return :: t -> mt (=<<) :: (a -> mb) -> ma -> mb infixr 1 =<<
Kleisli ID return
t
를 모나딕 컨텍스트 m
으로 승격시키는 Kleisli 화살표입니다. 확장 또는 Kleisli 응용 프로그램 =<<
Kleisli 화살표 a -> mb
를 계산 ma
결과에 적용합니다.
Kleisli 구성 <=<
은 확장 측면에서 다음과 같이 정의됩니다.
(<=<) :: Monad m => (b -> mc) -> (a -> mb) -> (a -> mc) f <=< g = \ x -> f =<< gx infixr 1 <=<
<=<
두 개의 Kleisli 화살표를 구성하여 왼쪽 화살표를 오른쪽 화살표의 적용 결과에 적용합니다.
모나드 유형 클래스의 인스턴스는 모나드 법칙을 준수해야 하며, Kleisli 구성 측면에서 가장 우아하게 명시되어 있습니다. forall fg h.
f <=< return = f :: c -> md Right identity return <=< g = g :: b -> mc Left identity (f <=< g) <=< h = f <=< (g <=< h) :: a -> md Associativity
<=<
는 연관이고 return
은 오른쪽 및 왼쪽 ID입니다.
ID 유형
type Id t = t
유형에 대한 식별 기능입니다.
Id :: * -> *
펑터로 해석하면,
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 (fx) instance Monad Id where return :: t -> Id t return = Id (=<<) :: (a -> Id b) -> Id a -> Id b f =<< (Id x) = fx
옵션 유형
data Maybe t = Nothing | Just t
t
산출하지 않는 Maybe t
계산을 인코딩합니다. 계산은 "실패"할 수 있습니다. 옵션 모나드가 정의됨
instance Functor Maybe where map :: (a -> b) -> (Maybe a -> Maybe b) map f (Just x) = Just (fx) map _ Nothing = Nothing instance Monad Maybe where return :: t -> Maybe t return = Just (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b f =<< (Just x) = fx _ =<< 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 -?
옵션 모나드는 예외 처리의 기본 형식을 다룹니다.
(-? 20) <=< toNat :: Int -> Maybe Nat
목록 유형 위의 목록 모나드
data [] t = [] | t : [t] infixr 5 :
그리고 그것의 덧셈 모노이드 연산 "append"
(++) :: [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) = fx : map f xs map _ [] = [] instance Monad [] where return :: t -> [t] return = (: []) (=<<) :: (a -> [b]) -> [a] -> [b] f =<< (x : xs) = fx ++ (f =<< xs) _ =<< [] = []
확장 =<<
연접 ++
모든 목록 [b]
어플리케이션으로부터 생성이 fx
화살표와의 Kleisli 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 fn = []
모나드 유형 클래스를 정의할 때 확장자 =<<
대신 Haskell 표준은 바인딩 연산자 >>=
합니다.
class Applicative m => Monad m where (>>=) :: forall a b. ma -> (a -> mb) -> mb (>>) :: forall a b. ma -> mb -> mb m >> k = m >>= \ _ -> k {-# INLINE (>>) #-} return :: a -> ma return = pure
단순함을 위해 이 설명에서는 유형 클래스 계층을 사용합니다.
class Functor f class Functor m => Monad m
Haskell에서 현재 표준 계층 구조는 다음과 같습니다.
class Functor f class Functor p => Applicative p class Applicative m => Monad m
모든 모나드가 펑터일 뿐만 아니라 모든 응용 프로그램이 펑터이고 모든 모나드도 응용 프로그램이기 때문입니다.
list 모나드를 사용하여 명령형 의사 코드
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 ) )
Do 표기법과 모나드 이해는 중첩된 바인드 표현식의 구문 설탕입니다. 바인드 연산자는 모나딕 결과의 로컬 이름 바인딩에 사용됩니다.
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 :: mt (<|>) :: mt -> mt -> mt infixl 3 <|> instance Additive Maybe where fail = Nothing Nothing <|> m = m m <|> _ = m instance Additive [] where fail = [] (<|>) = (++)
여기서 fail
및 <|>
forall kl m.
대한 모노이드를 형성합니다.
k <|> fail = k fail <|> l = l (k <|> l) <|> m = k <|> (l <|> m)
fail
는 추가 모나드의 흡수/소멸 제로 요소입니다.
_ =<< fail = fail
만약에
guard (even p) >> return p
even p
가 참이면 가드가 [()]
>>
정의에 따라 로컬 상수 함수
\ _ -> return p
()
적용됩니다. false인 경우 가드는 목록 모나드의 fail
( []
)를 생성하고, 이는 Kleisli 화살표를 >>
적용할 결과를 생성하지 않으므로 이 p
는 건너뜁니다.
악명 높게, 모나드는 상태 저장 계산을 인코딩하는 데 사용됩니다.
상태 프로세서 는 함수입니다.
forall st t. st -> (t, st)
st
상태를 전환하고 t
생성합니다. state st
는 무엇이든 될 수 있습니다. 아무것도, 플래그, 개수, 배열, 핸들, 기계, 세계.
상태 프로세서의 유형은 일반적으로
type State st t = st -> (t, st)
상태 프로세서 모나드는 종류 * -> *
펑터 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 (fx, 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 (fx) 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
상태 액세스는 상태 저장 모나드에 대한 추상화 방법 get
및 put
프리미티브에 의해 제공됩니다.
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-} class Monad m => Stateful m st | m -> st where get :: m st put :: st -> m ()
m -> st
m
에 대한 상태 유형 st
의 기능적 종속성 을 선언합니다. State t
는 상태 유형을 고유하게 t
instance Stateful (State st) st where get :: State st st get = State $ \ s -> (s, s) put :: st -> State st () put s = State $ \ _ -> ((), s)
C void
와 유사하게 사용되는 단위 유형을 사용합니다.
modify :: Stateful m st => (st -> st) -> m () modify f = do s <- get put (fs) gets :: Stateful m st => (st -> t) -> mt gets f = do s <- get return (fs)
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)
return ()
과 동일한 효과를 제외하고 State Int ()
유형의 계산입니다.
(flip run) 34 (modify (+ 1) >> gets (* 12) >>= (\ n -> modify (+ 7) >> return (show n) ) )
결합의 모나드 법칙은 >>=
forall mf g.
(m >>= f) >>= g = m >>= (\ x -> fx >>= g)
또는
do { do { do { r1 <- do { x <- m; r0 <- m; r0 <- m; = do { = r1 <- f r0; f r0 r1 <- fx; g r1 }; g r1 } g r1 } } }
표현식 지향 프로그래밍(예: Rust)에서와 같이 블록의 마지막 문은 산출량을 나타냅니다. 바인드 연산자는 "프로그래밍 가능한 세미콜론"이라고도 합니다.
구조화된 명령형 프로그래밍의 반복 제어 구조 기본 요소는 모나드 방식으로 에뮬레이트됩니다.
for :: Monad m => (a -> mb) -> [a] -> m () for f = foldr ((>>) . f) (return ()) while :: Monad m => m Bool -> mt -> m () while cm = do b <- c if b then m >> while cm else return () forever :: Monad m => mt 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
또는 최소한 그래야 합니다.
Haskell 프로그램의 형식 서명
main :: IO () main = putStrLn "Hello, World!"
확장
World -> ((), World)
세상을 바꾸는 기능.
객체가 Haskell 유형이고 형태가 Haskell 유형 간의 Hask
범주는 "빠르고 느슨한" 범주인 Hask 입니다.
펑터 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) -> (fa -> fb)
여기서 X
, Y
C
객체입니다. HomC(X, Y)
는 C
의 모든 형태 X -> Y
의 동형 클래스 입니다. D
에서 C
의 "구조"인 형태론적 동일성과 구성을 유지해야 합니다.
Tmor Tobj T(id) = id : T(X) -> T(X) Identity T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
카테고리 C
의 Kleisli 카테고리 는 Kleisli 트리플에 의해 주어집니다.
<T, eta, _*>
엔도펑터의
T : C -> C
( f
), 항등 형태 eta
( return
) 및 확장 연산자 *
( =<<
).
Hask
각 Kleisli 모피즘
f : X -> T(Y) f :: a -> mb
확장 연산자에 의해
(_)* : Hom(X, T(Y)) -> Hom(T(X), T(Y)) (=<<) :: (a -> mb) -> (ma -> mb)
Hask
의 Kleisli 범주에서 형태가 부여됩니다.
f* : T(X) -> T(Y) (f =<<) :: ma -> mb
Kleisli 카테고리 .T
은 확장자로 표시됩니다.
f .T g = f* . g : X -> T(Z) f <=< g = (f =<<) . g :: a -> mc
범주 공리를 충족합니다.
eta .T g = g : Y -> T(Z) Left identity return <=< g = g :: b -> mc f .T eta = f : Z -> T(U) Right identity f <=< return = f :: c -> md (f .T g) .T h = f .T (g .T h) : X -> T(U) Associativity (f <=< g) <=< h = f <=< (g <=< h) :: a -> md
등가 변환 적용
eta .T g = g eta* . g = g By definition of .T eta* . g = id . g forall f. id . f = f eta* = id forall fg 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 fg h. f . h = g . h ==> f = g
확장 측면에서 정식으로 제공됩니다.
eta* = id : T(X) -> T(X) Left identity (return =<<) = id :: mt -> mt f* . eta = f : Z -> T(U) Right identity (f =<<) . return = f :: c -> md (f* . g)* = f* . g* : T(X) -> T(Z) Associativity (((f =<<) . g) =<<) = (f =<<) . (g =<<) :: ma -> mc
모나드는 또한 Kleislian 확장이 아니라 join
이라는 프로그래밍에서 mu
의 관점에서 정의될 수 있습니다. 모나드는 엔도펑터의 카테고리 C
mu
로 정의됩니다.
T : C -> C f :: * -> *
그리고 두 가지 자연적 변화
eta : Id -> T return :: t -> ft mu : T . T -> T join :: f (ft) -> ft
등가물 충족
mu . T(mu) = mu . mu : T . T . T -> T . T Associativity join . map join = join . join :: f (f (ft)) -> ft mu . T(eta) = mu . eta = id : T -> T Identity join . map return = join . return = id :: ft -> ft
그런 다음 모나드 유형 클래스가 정의됩니다.
class Functor m => Monad m where return :: t -> mt join :: m (mt) -> mt
옵션 모나드의 정식 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 [] = []
목록 모나드의 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 (mt) -> mt
mu
에서 확장 형식으로의 역 번역은 다음과 같습니다.
f* = mu . T(f) : T(X) -> T(Y) (f =<<) = join . map f :: ma -> mb
Philip Wadler: 함수형 프로그래밍을 위한 모나드
Simon L Peyton Jones, Philip Wadler: 명령형 함수 프로그래밍
Jonathan MD Hill, Keith Clarke: 범주 이론 소개, 범주 이론 모나드 및 함수 프로그래밍과의 관계 ´
Eugenio Moggi: 계산과 모나드의 개념
그러나 이론이 프로그래밍에 사용하기 위해 그렇게 추상적이어야 하는 이유는 무엇입니까?
답은 간단합니다. 컴퓨터 과학자로서 우리는 추상화를 중요하게 생각합니다! 소프트웨어 구성 요소에 대한 인터페이스를 설계할 때 구현에 대해 가능한 한 적게 나타내 기를 원합니다. 우리는 구현을 많은 대안, 동일한 '개념'의 다른 많은 '인스턴스'로 대체할 수 있기를 원합니다. 많은 프로그램 라이브러리에 대한 일반 인터페이스를 설계할 때 우리가 선택한 인터페이스가 다양한 구현을 갖는 것이 훨씬 더 중요합니다. 그것은 우리가 카테고리 이론의 개념 프로그래밍을위한 매우 유용한 너무 추상적이기 때문에 너무 높게, 그것이 가치 모나드 개념의 보편성이다.
그러므로 우리가 아래에 제시하는 모나드의 일반화 또한 범주 이론과 밀접한 관련이 있다는 것은 놀라운 일이 아닙니다. 그러나 우리는 우리의 목적이 매우 실용적이라고 강조합니다. 그것은 '범주 이론을 구현'하는 것이 아니라 결합자 라이브러리를 구성하는 보다 일반적인 방법을 찾는 것입니다. 수학자들이 이미 우리를 위해 많은 일을 해냈다는 것은 우리에게 행운입니다!
John Hughes의 모나드 일반화에서 화살표로
위의 훌륭한 답변 외에도 개념을 JavaScript 라이브러리 jQuery 와 연관시켜 모나드를 설명하는 다음 기사(Patrick Thomson 작성)에 대한 링크를 제공하겠습니다. : jQuery는 모나드입니다.
jQuery 문서 자체는 "모나드"라는 용어를 언급하지 않지만 아마도 더 친숙한 "빌더 패턴"에 대해 이야기합니다. 이것은 당신이 깨닫지 못하는 사이에 적절한 모나드가 있다는 사실을 바꾸지 않습니다.
모나드는 공통 컨텍스트를 공유하는 계산을 함께 결합하는 방법입니다. 파이프 네트워크를 구축하는 것과 같습니다. 네트워크를 구성할 때 네트워크를 통해 흐르는 데이터는 없습니다. 그러나 'bind' 및 'return'으로 모든 비트를 연결하는 작업을 완료하면 runMyMonad monad data
와 같은 것을 호출하고 데이터는 파이프를 통해 흐릅니다.
실제로 모나드는 부작용과 호환되지 않는 입력 및 반환 값(연결용)을 처리하는 함수 구성 연산자의 사용자 정의 구현입니다.
내가 올바르게 이해했다면 IEnumerable은 모나드에서 파생됩니다. 그것이 C# 세계의 우리들에게 흥미로운 접근 각도가 아닐까?
그만한 가치가 있기 때문에 여기에 저를 도운 튜토리얼에 대한 링크가 있습니다 (아직도 모나드가 무엇인지 이해하지 못했습니다).
거기에 대해 배울 때 가장 도움이 된 두 가지는 다음과 같습니다.
Graham Hutton의 책 Programming in Haskell 에서 8장, "Functional Parsers". 이것은 실제로 모나드에 대해 전혀 언급하지 않지만, 챕터를 통해 작업하고 그 안의 모든 것을 정말로 이해할 수 있다면 특히 바인드 작업의 시퀀스가 평가되는 방식을 이해할 수 있다면 모나드의 내부를 이해할 수 있을 것입니다. 여러 시도가 필요할 것으로 예상됩니다.
튜토리얼 모나드에 관한 모든 것 . 이것은 그것들의 사용에 대한 몇 가지 좋은 예를 제공하며 부록의 비유가 나를 위해 일했다고 말해야 합니다.
Monoid는 Monoid와 지원되는 유형에 정의된 모든 작업이 항상 Monoid 내부에서 지원되는 유형을 반환하도록 하는 것 같습니다. 예: 임의의 숫자 + 임의의 숫자 = 숫자, 오류 없음.
나눗셈은 두 개의 분수를 허용하고 하스켈에서 0으로 나누기를 무한대로 정의한 분수를 반환하는 반면(이는 어떻게든 분수가 됨)...
어쨌든 Monad는 연산 체인이 예측 가능한 방식으로 작동하도록 하는 방법일 뿐이며, Num -> Num이라고 주장하는 함수는 x로 호출되는 Num->Num의 다른 함수로 구성되어 있지 않습니다. 말하자면 미사일을 발사합니다.
반면에 미사일을 발사하는 기능이 있다면 미사일을 발사하는 다른 기능과 함께 구성할 수 있습니다. 의도가 분명하기 때문입니다. 미사일을 발사하고 싶지만 시도하지 않습니다. 이상한 이유로 "Hello World"를 인쇄합니다.
Haskell에서 main은 IO() 또는 IO [()] 유형이며 구분이 이상하고 이에 대해 논의하지 않겠지만 다음과 같이 생각합니다.
메인이 있다면 일련의 작업을 수행하기를 원합니다. 프로그램을 실행하는 이유는 일반적으로 IO를 통해 효과를 생성하기 위함입니다. 따라서 메인에서 IO 작업을 함께 연결할 수 있습니다. IO를 수행하고 다른 작업은 수행하지 않습니다.
"IO를 반환"하지 않는 작업을 하려고 하면 프로그램에서 체인이 흐르지 않는다고 불평하거나 기본적으로 "이것이 우리가 하려는 일과 어떤 관련이 있습니까 -- IO 작업"을 강제하는 것처럼 보입니다. 프로그래머는 미사일 발사에 대해 생각하지 않고 생각의 흐름을 유지하면서 정렬 알고리즘을 생성합니다. 이는 흐르지 않습니다.
기본적으로 Monads는 컴파일러에게 "여기에 숫자를 반환하는 이 함수를 알고 있지만 실제로 항상 작동하는 것은 아닙니다. 때로는 숫자를 생성할 수 있고 때로는 아무것도 생성하지 않을 수 있습니다. 정신". 이것을 알면 모나딕 액션을 주장하려고 하면 모나딕 액션이 컴파일 타임 예외처럼 작동할 수 있습니다. 흐름을 수용할 수 있는지 확인합니다." 예측할 수 없는 프로그램 동작을 방지합니다.
모나드는 순수성이나 제어에 관한 것이 아니라 모든 동작이 예측 가능하고 정의되거나 컴파일되지 않는 범주의 정체성을 유지하는 것 같습니다. 당신이 무언가를 할 것으로 예상되면 아무것도 할 수 없으며, 당신이 아무것도 하지 않을 것으로 예상되면 당신은 무언가를 할 수 없습니다(가시적).
내가 Monads에 대해 생각할 수 있는 가장 큰 이유는 -- 가서 Procedural/OOP 코드를 보면 프로그램이 어디서 시작하고 끝나는지 알지 못함을 알 수 있습니다. , 마법, 그리고 미사일. 당신은 그것을 유지할 수 없을 것이고, 가능하다면 당신이 그것의 어떤 부분도 이해하기 전에 전체 프로그램을 둘러싸는 데 꽤 많은 시간을 할애하게 될 것입니다. 왜냐하면 이 컨텍스트의 모듈성은 상호 의존적인 "섹션"을 기반으로 하기 때문입니다. 코드는 효율성/상호 관계를 약속하기 위해 가능한 한 관련되도록 최적화됩니다. 모나드는 매우 구체적이고 정의에 따라 잘 정의되어 있으며 프로그램의 흐름을 분석하고 분석하기 어려운 부분을 분리할 수 있도록 합니다. 그 자체가 모나드이기 때문입니다. 모나드는 "완전히 이해했을 때 예측할 수 있는 이해 가능한 단위"로 나타납니다. -- "아마도" 모나드를 이해하면 사소한 것처럼 보이지만 대부분의 모나드가 아닌 경우 "아마도" 외에는 아무 것도 할 수 없습니다. 코드, 간단한 기능 "helloworld"는 미사일을 발사하거나, 아무 것도 하지 않거나, 우주를 파괴하거나 심지어 시간을 왜곡할 수 있습니다. 모나드는 그것이 무엇인지 보장합니다. 이것은 매우 강력합니다.
"실제 세계"의 모든 것은 혼동을 방지하는 관찰 가능한 명확한 법칙으로 묶여 있다는 의미에서 모나드처럼 보입니다. 이것은 클래스를 생성하기 위해 이 객체의 모든 작업을 모방해야 한다는 것을 의미하지 않습니다. 대신 "정사각형은 정사각형입니다"라고 말할 수 있습니다. 기존 치수 중 하나의 길이에 자체를 곱한 값입니다. 어떤 정사각형을 가지고 있든 2D 공간의 정사각형이라면 면적은 절대적으로 길이의 제곱 외에는 아무 것도 될 수 없습니다. 증명하는 것은 거의 간단합니다. 이것은 매우 강력하기 때문에 우리는 우리의 세계가 있는 그대로인지 확인하기 위해 주장할 필요가 없습니다. 우리는 프로그램이 궤도에서 벗어나지 않도록 현실의 함축을 사용합니다.
나는 거의 틀릴 것이라고 보장하지만 이것이 누군가를 도울 수 있다고 생각하므로 희망적으로 누군가를 도울 것입니다.
Scala의 맥락에서 다음이 가장 간단한 정의임을 알 수 있습니다. 기본적으로 flatMap(또는 bind)은 '연관성'이고 아이덴티티가 존재합니다.
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)
참고 엄밀히 말해서 함수형 프로그래밍에서 모나드 map
과 flatten
교대로 정의되는 범주 이론에서 의 모나드의 정의와 동일하지 않습니다. 특정 매핑에서는 동일하지만. 이 프레젠테이션은 매우 훌륭합니다. http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category
이 답변은 동기를 부여하는 예제로 시작하여 예제를 통해 작동하고 모나드의 예제를 파생하고 "모나드"를 공식적으로 정의합니다.
의사 코드에서 다음 세 가지 기능을 고려하십시오.
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 m
into f
"로 읽습니다. 한 쌍의 피드 <x, messages>
함수로 f
통과하는 x
로 f
얻을 <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
t
와 문자열 쌍" 유형의 값입니다. 그 유형을 M t
라고 부릅니다.
M
은 유형 생성자입니다. M
단독으로는 유형을 참조하지 않지만 M _
은 공백을 유형으로 채우면 유형을 참조합니다. M int
는 int와 string의 쌍입니다. M string
은 문자열과 문자열의 쌍입니다. 등.
축하합니다. 모나드를 만들었습니다!
공식적으로 모나드는 <M, feed, wrap>
튜플입니다.
모나드는 다음과 같은 튜플 <M, feed, wrap>
입니다.
M
은 형식 생성자입니다.feed
(a 얻어 함수 얻어 t
및 다시 표시 M u
및) M t
및 다시 표시 M u
.wrap
은 v
M v
반환합니다. t
, u
및 v
는 동일하거나 동일하지 않을 수 있는 세 가지 유형입니다. 모나드는 특정 모나드에 대해 증명한 세 가지 속성을 충족합니다.
t
를 함수에 공급 하는 것은 래핑되지 않은 t
를 함수에 전달하는 것과 같습니다.
공식적으로: feed(f, wrap(x)) = f(x)
먹이 M t
에 wrap
받는 아무것도하지 않습니다 M t
.
공식적으로: feed(wrap, m) = m
다음을 수행하는 함수에 M t
m
이라고 부름)를 제공합니다.
t
를 g
로 전달g
M u
n
이라고 부름)를 얻습니다.n
을 f
공급와 같다
m
을 g
공급g
에서 n
가져오기n
을 f
공급 공식적으로: feed(h, m) = feed(f, feed(g, m))
여기서 h(x) := feed(f, g(x))
일반적으로 feed
는 bind
(Haskell의 경우 >>=
)라고 wrap
return
이라고 합니다.
나는 Haskell의 맥락에서 Monad
를 설명하려고 노력할 것입니다.
함수형 프로그래밍에서는 함수 구성이 중요합니다. 그것은 우리 프로그램이 작고 읽기 쉬운 기능으로 구성되도록 합니다.
g :: Int -> String
및 f :: String -> Bool
두 가지 함수가 있다고 가정해 보겠습니다.
f (gx)
와 동일한 (f . g) x
수행할 수 있습니다. 여기서 x
는 Int
값입니다.
한 함수의 결과를 다른 함수에 합성/적용할 때 유형을 일치시키는 것이 중요합니다. 위의 경우 g
f
허용하는 유형과 동일해야 합니다.
그러나 때로는 값이 컨텍스트에 있으므로 유형을 정렬하기가 조금 더 쉽습니다. (컨텍스트에 값을 갖는 것은 매우 유용합니다. 예를 들어, Maybe Int
유형은 거기에 없을 수도 Int
IO String
유형은 일부 부작용을 수행한 결과로 존재 String
g1 :: Int -> Maybe String
및 f1 :: String -> Maybe Bool
이 있다고 가정해 보겠습니다. g1
및 f1
은 각각 g
및 f
와 매우 유사합니다.
(f1 . g1) x
또는 f1 (g1 x)
할 수 없습니다. 여기서 x
는 Int
값입니다. g1
이 반환한 결과의 유형은 f1
예상하는 것과 다릅니다.
f
와 g
를 구성할 수 .
연산자이지만 이제 f1
과 g1
을 .
. 문제는 컨텍스트에 없는 값을 기대하는 함수에 컨텍스트의 값을 직접 전달할 수 없다는 것입니다.
(f1 OPERATOR g1) x
쓸 수 있도록 g1
과 f1
을 구성하는 연산자를 도입하면 좋지 않을까요? g1
은 컨텍스트에서 값을 반환합니다. 값은 컨텍스트에서 벗어나 f1
적용됩니다. 그리고 예, 우리에게는 그러한 연산자가 있습니다. <=<
입니다.
약간 다른 구문이지만 완전히 동일한 작업을 수행 >>=
연산자도 있습니다.
우리는 다음과 같이 씁니다: g1 x >>= f1
. g1 x
는 Maybe Int
값입니다. >>=
연산자는 "perhaps-not-there" 컨텍스트에서 Int
f1
적용하는 데 도움이 됩니다. Maybe Bool
인 f1
의 결과는 전체 >>=
연산의 결과가 됩니다.
마지막으로 Monad
유용한 이유는 무엇입니까? Monad
>>=
연산자를 정의하는 유형 클래스이기 때문에 ==
및 /=
연산자를 정의하는 Eq
유형 클래스와 매우 동일합니다.
결론적으로, Monad
유형 클래스는 컨텍스트에서 값을 예상하지 않는 함수에 컨텍스트의 값(이를 모나딕 값이라고 함)을 전달할 수 있도록 >>=
컨텍스트가 처리됩니다.
여기서 한 가지 기억해야 할 것이 있다면 Monad
는 contexts 의 값을 포함하는 함수 구성을 허용한다는 것 입니다.
세상에 필요한 것은 또 다른 모나드 블로그 게시물이지만 이것이 야생에서 기존 모나드를 식별하는 데 유용하다고 생각합니다.
위의 그림은 Sierpinski 삼각형이라고 하는 프랙탈로, 제가 기억하는 유일한 프랙탈입니다. 프랙탈은 부분이 전체와 유사한 위의 삼각형과 같은 자기 유사 구조입니다(이 경우 모체 삼각형의 비율의 정확히 절반).
모나드는 프랙탈입니다. 모나딕 데이터 구조가 주어지면 그 값을 구성하여 데이터 구조의 다른 값을 형성할 수 있습니다. 이것이 프로그래밍에 유용한 이유이며 많은 상황에서 발생하는 이유입니다.
모나드는 상태가 변하는 객체를 캡슐화하는 데 사용되는 것입니다. 수정 가능한 상태를 허용하지 않는 언어(예: Haskell)에서 가장 자주 발생합니다.
파일 I/O에 대한 예입니다.
파일 I/O에 모나드를 사용하여 상태 변화를 모나드를 사용한 코드로만 분리할 수 있습니다. Monad 내부의 코드는 Monad 외부의 변화하는 상태를 효과적으로 무시할 수 있습니다. 이렇게 하면 프로그램의 전반적인 효과에 대해 훨씬 쉽게 추론할 수 있습니다.
http://code.google.com/p/monad-tutorial/ 은 이 질문을 정확히 해결하기 위해 진행 중인 작업입니다.
매우 간단한 대답은 다음과 같습니다.
모나드는 값을 캡슐화하고, 새 캡슐화된 값을 계산하고, 캡슐화된 값을 풀기 위한 인터페이스를 제공하는 추상화입니다.
실제로 편리한 점은 stateful이 아닌 상태를 모델링하는 데이터 유형을 생성하기 위한 균일한 인터페이스를 제공 한다는 것입니다.
Monad는 추상화 , 즉 특정 종류의 데이터 구조를 다루기 위한 추상 인터페이스라는 것을 이해하는 것이 중요합니다. 그런 다음 해당 인터페이스는 모나딕 동작이 있는 데이터 유형을 빌드하는 데 사용됩니다.
Monads in Ruby, Part 1: Introduction 에서 매우 훌륭하고 실용적인 소개를 찾을 수 있습니다.
출처 : http:www.stackoverflow.com/questions/44965/what-is-a-monad
SQL Server에서 INNER JOIN을 사용하여 어떻게 삭제할 수 있습니까? (0) | 2022.02.24 |
---|---|
init으로 생성된 git 저장소를 완전히 삭제하는 방법은 무엇입니까? (0) | 2022.02.24 |
varchar와 nvarchar의 차이점은 무엇입니까? (0) | 2022.02.24 |
다른 스레드에서 GUI를 어떻게 업데이트합니까? (0) | 2022.02.24 |
UNION과 UNION ALL의 차이점은 무엇입니까? (0) | 2022.02.19 |