최근 Haskell을 간략히 살펴보면 모나드가 무엇인지에 대한 간결하고 간결하며 실용적인 설명은 무엇입니까?
내가 접한 대부분의 설명은 접근하기가 어렵고 실제적인 세부 사항이 부족하다는 것을 알았습니다.
최근 Haskell을 간략히 살펴보면 모나드가 무엇인지에 대한 간결하고 간결하며 실용적인 설명은 무엇입니까?
내가 접한 대부분의 설명은 접근하기가 어렵고 실제적인 세부 사항이 부족하다는 것을 알았습니다.
답변:
첫째 : 모나드 라는 용어 는 수학자가 아니라면 약간 공허합니다. 다른 용어는 실제로 유용한 것에 대해 좀 더 설명하는 계산 빌더 입니다.
실용적인 예를 요청하십시오.
예 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
"모나드 란 무엇인가"를 설명하는 것은 "숫자는 무엇입니까?" 우리는 항상 숫자를 사용합니다. 그러나 숫자에 대해 전혀 모르는 사람을 만났다고 상상해보십시오. 어떻게 도대체 당신은 숫자가 무엇인지 설명 할 것인가? 그리고 왜 그것이 유용한 지 설명하기 시작 하시겠습니까?
모나드는 무엇입니까? 짧은 대답 : 그것은 작업을 연결하는 구체적인 방법입니다.
본질적으로 실행 단계를 작성하고이를 "바인드 기능"과 연결합니다. (Haskell에서 이름은으로 지정 >>=
됩니다.) 바인드 연산자에 직접 호출을 작성하거나 컴파일러가 해당 함수 호출을 삽입하게하는 구문 sugar를 사용할 수 있습니다. 그러나 어느 쪽이든, 각 단계는이 바인드 함수에 대한 호출로 분리됩니다.
따라서 바인드 함수는 세미콜론과 같습니다. 프로세스의 단계를 분리합니다. 바인드 기능의 작업은 이전 단계에서 출력을 가져 와서 다음 단계로 공급하는 것입니다.
너무 힘들게 들리지 않습니까? 그러나 하나 이상의 모나드가 있습니다. 왜? 어떻게?
바인드 함수 는 한 단계에서 결과를 가져 와서 다음 단계로 전달할 수 있습니다. 그러나 그것이 "모두"라면 모나드는 그렇습니다 ... 실제로는 그다지 유용하지 않습니다. 이해하는 것이 중요합니다. 모든 유용한 모나드는 모나드 일뿐 아니라 다른 일도합니다 . 모든 유용한 모나드는 "특별한 힘"을 가지고있어 독특합니다.
( 특별히 아무 것도 하지 않는 모나드 는 "identity monad"라고 불립니다. 신원 기능과는 달리이 기능은 전혀 무의미한 것처럼 들리지만 그렇지는 않습니다 ... 그러나 그것은 또 다른 이야기입니다.)
기본적으로 각 모나드는 자체 바인딩 기능을 구현합니다. 그리고 실행 단계 사이에서 후프를 수행하도록 바인드 함수를 작성할 수 있습니다. 예를 들면 다음과 같습니다.
각 단계가 성공 / 실패 표시기를 리턴하면, 이전 단계가 성공한 경우에만 다음 단계를 바인드하도록 바인드 할 수 있습니다. 이런 식으로 실패한 단계는 조건부 테스트없이 전체 시퀀스를 "자동으로"중단합니다. ( 실패 모나드 .)
이 아이디어를 확장하면 "예외"를 구현할 수 있습니다. ( Error Monad 또는 Exception Monad .) 언어 기능이 아니라 직접 정의하기 때문에 작동 방식을 정의 할 수 있습니다. (예를 들어, 처음 두 예외를 무시하고 세 번째 예외가 발생할 때만 중단하려고 할 수 있습니다 .)
각 단계에서 여러 개의 결과를 반환하도록 하고 bind 함수가 그 결과를 반복하도록하여 각 단계를 다음 단계로 전달할 수 있습니다. 이러한 방식으로 여러 결과를 처리 할 때 여러 곳에 루프를 계속 쓸 필요가 없습니다. 바인드 기능은 "자동으로"모든 기능을 수행합니다. ( 목록 Monad .)
한 단계에서 다른 단계로 "결과"를 전달하는 것 외에도 바인드 함수 가 추가 데이터 를 전달 하도록 할 수 있습니다 . 이 데이터는 이제 소스 코드에 표시되지 않지만 수동으로 모든 함수에 전달하지 않고도 어디서나 액세스 할 수 있습니다. ( 독자 모나드 .)
"추가 데이터"를 교체 할 수 있습니다. 이를 통해 실제로 파괴적인 업데이트를 수행하지 않고도 파괴적인 업데이트 를 시뮬레이션 할 수 있습니다 . ( 주 모나드 와 그 사촌 작가 모나드 .)
파괴적인 업데이트 만 시뮬레이션 하기 때문에 실제 파괴적인 업데이트 로는 불가능한 일을 간단하게 수행 할 수 있습니다. 예를 들어 마지막 업데이트를 실행 취소 하거나 이전 버전으로 되돌릴 수 있습니다.
계산을 일시 중지 할 수있는 모나드를 만들 수 있으므로 프로그램을 일시 중지하고 내부 상태 데이터로 들어가서 땜질 한 다음 다시 시작할 수 있습니다.
"연속"을 모나드로 구현할 수 있습니다. 이를 통해 사람들의 마음 을 아프게 할 수 있습니다 !
이 모든 것이 모나드로 가능합니다. 물론이 모든 것도 모나드 없이도 완벽하게 가능합니다 . 그것은 크게 그냥 쉽게 모나드를 사용하여.
사실, 모나드에 대한 일반적인 이해와는 달리, 국가와는 아무런 관련이 없습니다. 모나드는 단순히 물건을 포장하는 방법이며 포장을 풀지 않고 포장 된 물건에 대한 작업을 수행하는 방법을 제공합니다.
예를 들어 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
로만 정의 할 수 있습니다 . 그래서, 모나드를 정의 할 때 .. 당신은 그것의 타입 (여기서)을주고 그 동작 과 작동 방식을 말합니다 .bind
return
Wrapped a
return
bind
멋진 점은 이것이 일반적인 패턴으로 판명되어 순수한 방식으로 상태를 캡슐화하는 것은 그중 하나 일 뿐이라는 것입니다.
Haskell의 IO 모나드에서 사용되는 것과 같이 모나드를 사용하여 기능적 종속성을 도입하고 평가 순서를 제어하는 방법에 대한 좋은 기사를 보려면 IO Inside를 확인하십시오 .
모나드를 이해하는 것에 대해서는 너무 걱정하지 마십시오. 당신이 흥미로운 것을 발견하고 바로 이해하지 않아도 걱정하지 마십시오. 그렇다면 Haskell과 같은 언어로 다이빙하는 것이 좋습니다. 모나드는 연습을 통해 뇌 속으로 간계를 이해하는 순간, 언젠가 갑자기 이해한다는 사실을 깨닫게됩니다.
sigfpe 말한다 :
그러나이 모든 것은 모나드를 설명이 필요한 난해한 것으로 소개합니다. 그러나 내가 주장하고 싶은 것은 전혀 비전 적이 지 않다는 것입니다. 실제로 함수형 프로그래밍에서 다양한 문제에 직면했을 때 모나드의 예인 특정 솔루션으로 이어질 수밖에 없었습니다. 사실, 아직 개발하지 않았다면 지금 발명하도록하겠습니다. 그런 다음 이러한 모든 솔루션이 실제로 동일한 솔루션이라는 것을 알 수있는 작은 단계입니다. 이 글을 읽은 후에는 이미 발명 한 것으로 보이는 모든 것을 인식 할 수 있기 때문에 모나드에 대한 다른 문서를 이해하기에 더 나은 위치에있을 수 있습니다.
모나드가 해결하려고하는 많은 문제는 부작용 문제와 관련이 있습니다. 먼저 시작하겠습니다. 모나드를 사용하면 부작용을 처리하는 것 이상을 수행 할 수 있습니다. 특히 많은 유형의 컨테이너 객체를 모나드로 볼 수 있습니다. 다른 사람.)
C ++과 같은 명령형 프로그래밍 언어에서 함수는 수학 함수와 같이 동작하지 않습니다. 예를 들어, 단일 부동 소수점 인수를 사용하고 부동 소수점 결과를 리턴하는 C ++ 함수가 있다고 가정하십시오. 피상적으로는 실수를 실수에 매핑하는 수학 함수처럼 보일 수 있지만 C ++ 함수는 인수에 따라 숫자를 반환하는 것 이상을 수행 할 수 있습니다. 전역 변수의 값을 읽고 쓸 수있을뿐만 아니라 화면에 출력을 기록하고 사용자로부터 입력을받을 수 있습니다. 그러나 순수한 기능적 언어에서 함수는 인수로 제공된 내용 만 읽을 수 있으며 세상에 영향을 줄 수있는 유일한 방법은 반환되는 값을 통하는 것입니다.
모나드는 >>=
(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
여기서 []
및 :
목록 생성자이며, ++
연결 연산자이며, Just
및 Nothing
이다 Maybe
생성자. 이 모나드는 둘 다 공통적이고 유용한 계산 패턴을 각 데이터 유형에 캡슐화합니다 (부작용이나 I / O와 관련이 없음).
모나드가 무엇인지, 왜 유용한 지 이해하려면 사소하지 않은 Haskell 코드를 작성해야합니다.
먼저 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
정체성을 가져야합니다 .join
join (pure x) == x
[면책 조항 : 나는 여전히 모나드를 완전히 구르려고합니다. 다음은 내가 지금까지 이해 한 것입니다. 그것이 틀렸다면, 잘 알고있는 누군가가 카펫 위에서 나를 부를 것입니다.]
Arnar는 다음과 같이 썼습니다.
모나드는 단순히 물건을 포장하는 방법이며 포장을 풀지 않고 포장 된 물건에 대한 작업을 수행하는 방법을 제공합니다.
바로 그거야. 아이디어는 다음과 같습니다.
당신은 어떤 종류의 가치를 취하고 그것을 추가 정보로 포장합니다. 값이 특정 종류 (예 : 정수 또는 문자열) 인 것처럼 추가 정보는 특정 종류입니다.
예를 들어, 추가 정보는 a Maybe
또는 a 일 수 있습니다 IO
.
그런 다음 추가 정보를 전달하면서 랩핑 된 데이터를 조작 할 수있는 일부 연산자가 있습니다. 이 연산자는 추가 정보를 사용하여 랩핑 된 값에 대한 작업 동작을 변경하는 방법을 결정합니다.
예를 들어 a Maybe Int
는 Just Int
또는 일 수 있습니다 Nothing
. 이제 당신이 추가하면, Maybe Int
A와 Maybe Int
운영자들이 모두 있는지 확인합니다 Just Int
내부이야, 그렇게되면, 랩을 해제 Int
,의를 그들에게 추가 연산자를 통과, 결과를 재 - 포장 Int
새로운으로 Just Int
유효한 인 ( Maybe Int
)를 반환하여 a를 반환합니다 Maybe Int
. 그러나 그중 하나가 Nothing
내부에 있으면이 연산자는 즉시 반환 Nothing
합니다 Maybe Int
. 이는 다시 유효합니다 . 그런 식으로, 당신은 Maybe Int
s가 단지 정상적인 숫자 인 척하고 정규 수학을 수행 할 수 있습니다. 만약 당신이을 얻는다면 Nothing
, 당신의 방정식은 여전히 모든 곳에서 점검을하지 않아도Nothing
올바른 결과를 만들어 낼 것 입니다.
그러나 그 예는 단지 일어나는 일이다 Maybe
. 추가 정보가 IO
이면 IO
s에 대해 정의 된 해당 특수 연산자 가 대신 호출되며 추가를 수행하기 전에 완전히 다른 작업을 수행 할 수 있습니다. (좋아요, 두 개의 IO Int
s를 함께 추가 하는 것은 아마도 의미가 없습니다. 아직 확실하지 않습니다.) (또한 Maybe
예제에 주의를 기울인다면 “추가적인 것들로 가치를 싸는 것”이 항상 올바른 것은 아니라는 것을 알았습니다. 정확하고 정확하며 정확해야합니다.)
기본적으로 "monad"는 대략 "pattern"을 의미 합니다. 그러나 비공식적으로 설명되고 구체적으로 명명 된 Patterns로 가득 찬 책 대신, 이제 새로운 구문을 프로그램에서 물건 으로 선언 할 수 있는 언어 구성 ( 구문 및 모두)이 있습니다 . (여기서 부정확 한 점은 모든 패턴이 특정 형태를 따라야한다는 것이므로 모나드는 패턴만큼 일반적이지 않습니다. 그러나 이것이 대부분의 사람들이 알고 이해하는 가장 가까운 용어라고 생각합니다.)
이것이 사람들이 모나드를 매우 혼란스럽게 생각하는 이유입니다. 왜냐하면 그들은 일반적인 개념이기 때문입니다. 무엇을 모나드로 만드는지 묻는 것은 패턴을 만드는 것이 무엇인지 묻는 것과 유사합니다.
그러나 패턴에 대한 아이디어를 언어로 구문 적으로 지원한다는 의미를 생각해보십시오. Gang of Four 책 을 읽고 특정 패턴의 구성을 암기하는 대신 이 패턴을 불가지론 적으로 구현하는 코드를 작성 하면됩니다 . 한 번 일반적인 방법으로 완료되면! 그런 다음 방문자 나 전략 또는 Façade와 같은 패턴을 반복해서 다시 구현하지 않고도 코드의 작업을 장식하여 재사용 할 수 있습니다!
그래서 모나드 를 이해하는 사람들이 그렇게 유용하다고 생각하는 이유 는 지적 상식이 이해에 대해 자부심을 갖는 상아탑 개념은 아니지만 (물론 티히) 물론 실제로 코드를 더 단순하게 만듭니다.
M (M a) -> M a
. 이것을 유형 중 하나로 바꿀 수 있다는 사실이 M a -> (a -> M b) -> M b
유용합니다.
많은 노력을 기울인 끝에 마침내 모나드를 이해했다고 생각합니다. 압도적으로 가장 많이 투표 된 답변에 대한 긴 비평을 다시 읽은 후이 설명을 제공하겠습니다.
모나드를 이해하려면 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
일반 함수를 변환하는 연산자를 특정 종류의 바인드 연산자와 함께 작동하는 함수로 리턴합니다 .
모나드는 어떻게 구현됩니까?
다른 답변을 참조하십시오. 자세한 내용으로 뛰어들 수 있습니다.
몇 년 전에이 질문에 대한 답변을 한 후, 나는 그 응답을 개선하고 단순화 할 수 있다고 생각합니다 ...
모나드는 작성 중 입력 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
검사 하여 구성된 함수를에 적용할지 여부와 방법을 결정합니다 . 물론 기능 은 구조를 검사하고 원하는 유형의 응용 프로그램을 수행 할 수 있도록 특별히 정의되었습니다 . 그럼에도 불구하고 응용 프로그램이 필요한 응용 프로그램을 결정할 때 구성되지 않은 기능에 검사되지 않은 항목을 전달하기 때문에 무엇이든 될 수 있습니다 . 또한 작성된 기능 자체는 더 이상M
Ma
a
bind
M
a
bind
a
M
입력 구조의 일부를 단순화하여 그 후...
(a -> Mb) >>= (b -> Mc)
간결하게 Mb >>= (b -> Mc)
요컨대, 모나드는 외부화되어 입력이 충분히 노출되도록 설계되면 특정 입력 시나리오의 처리에 대한 표준 동작을 제공합니다. 이 디자인은 shell and content
쉘에 구성된 기능의 적용과 관련된 데이터가 포함되어 있으며 해당 기능 만 조사하여 사용할 수있는 모델 bind
입니다.
따라서 모나드는 세 가지입니다.
M
모나드 관련 정보를 담기 위한 쉘 bind
작성된 함수를 쉘 내에서 찾은 컨텐츠 값에 적용 할 때이 쉘 정보를 사용하도록 구현 된 함수 a -> Mb
모나드 관리 데이터를 포함하는 결과를 생성 하는 형태의 구성 가능한 기능 .일반적으로 함수에 대한 입력은 오류 조건과 같은 것들을 포함 할 수있는 출력보다 훨씬 제한적입니다. 따라서 Mb
결과 구조는 일반적으로 매우 유용합니다. 예를 들어, 나누기 연산자는 제수가 0 일 때 숫자를 반환하지 않습니다 0
.
또한 monad
s에는 적용 후 결과를 래핑하여 값을 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
괄호의 힘, 상기 초기 평가 binding
들 f
과 g
만 예상하는 기능을 초래할 것이다 Ma
순서가 완료 할 bind
. 따라서 평가는 Ma
그 가치가 적용 f
되고 그 결과 가 적용되기 전에 결정되어야한다 g
.
모나드는 사실상 "유형 연산자"의 한 형태입니다. 세 가지 일을 할 것입니다. 먼저 한 유형의 값을 다른 유형 (일반적으로 "모 노드 유형"이라고 함)으로 "랩핑"(또는 그렇지 않으면 변환)합니다. 둘째, 기본 유형에서 사용 가능한 모든 작업 (또는 기능)을 monadic 유형에서 사용할 수있게합니다. 마지막으로 복합 모나드를 생성하기 위해 자체를 다른 모나드와 결합하는 지원을 제공합니다.
"아마도 모나드"는 Visual Basic / C #의 "널링 가능 유형"과 기본적으로 같습니다. Null을 허용하지 않는 형식 "T"를 사용하여 "Nullable <T>"로 변환 한 다음 Nullable <T>에서 모든 이진 연산자의 의미를 정의합니다.
부작용은 유사하게 표현됩니다. 함수의 반환 값과 함께 부작용에 대한 설명이 포함 된 구조가 생성됩니다. 그런 다음 "리프팅"작업은 값이 함수간에 전달 될 때 부작용을 복사합니다.
몇 가지 이유로 "유형 연산자"의 이해하기 쉬운 이름이 아니라 "모나드"라고합니다.
( 모나드 는 무엇입니까?에 대한 답변도 참조하십시오 . )
Monads에 대한 좋은 동기는 sigfpe (Dan Piponi)의 당신이 Monads를 발명 했을 수 있습니다! (그리고 아마도 당신은 이미 가지고 있습니다) . 있다 다른 모나드 튜토리얼의 많은 misguidedly 다양한 비유를 사용하여 "간단한 약관"에 모나드를 설명하려고 많은 것이이 :이 인 모나드 튜토리얼 사기 야 ; 피하십시오.
로 DR MacIver는 말한다 언어 짜증 이유를 말해 :
그래서 Haskell에 대해 싫어하는 것 :
명백한 것으로 시작합시다. Monad 튜토리얼. 아니요, 모나드가 아닙니다. 특히 튜토리얼. 그들은 끝이없고, 과장되었고 사랑하는 신은 지루합니다. 또한 나는 그들이 실제로 도움이된다는 설득력있는 증거를 본 적이 없다. 클래스 정의를 읽고, 코드를 작성하고, 무서운 이름을 극복하십시오.
당신은 아마도 모나드를 이해한다고 말합니까? 좋아, 넌 가고있어 다른 모나드를 사용하기 시작하면 곧 모나드가 무엇인지 이해할 수 있습니다.
[수학 지향이라면, 수십 개의 튜토리얼을 무시하고 정의를 배우거나 카테고리 이론에서 강의를 따르고 싶을 수도 있습니다.) 정의 의 주요 부분은 Monad M이 각각에 대해 정의 된 "타입 생성자"를 포함한다는 것입니다 기존 유형 "T", 새로운 유형 "MT"및 "일반"유형과 "M"유형 사이를 오가는 몇 가지 방법]
또한 놀랍게도 모나드에 대한 가장 좋은 소개 중 하나는 실제로 모나드를 소개하는 초기 학술 논문 중 하나 인 함수형 프로그래밍을위한 필립와 들러의 모나드입니다 . 실제로 많은 인공 튜토리얼과 달리 실용적이지 않은 동기 부여 예제가 있습니다.
Monad는 데이터에 대한 추상 데이터 유형이 무엇인지 흐름을 제어해야합니다.
다시 말해서, 많은 개발자들은 세트, 목록, 사전 (또는 해시 또는 맵) 및 트리의 아이디어에 익숙합니다. 이러한 데이터 유형에는 여러 가지 특별한 경우가 있습니다 (예 : InsertionOrderPreservingIdentityHashMap).
그러나 프로그램 "흐름"에 직면했을 때 많은 개발자들은 if, switch / case, do, while, goto (grr) 및 (아마도) 클로저보다 더 많은 구성에 노출되지 않았습니다.
따라서 모나드는 단순히 제어 흐름 구조입니다. 모나드를 대체하는 더 좋은 표현은 'control type'입니다.
따라서 모나드는 제어 로직, 명령문 또는 함수를위한 슬롯을 가지고 있습니다. 데이터 구조와 동등한 것은 일부 데이터 구조를 통해 데이터를 추가하고 제거 할 수 있다는 것입니다.
예를 들어, "if"모나드 :
if( clause ) then block
가장 간단한 두 절-절과 블록이 있습니다. if
모나드는 보통 조항의 결과를 평가하기 위해 구축, 거짓되지 않을 경우, 블록을 평가한다. 많은 개발자들은 'if'를 배울 때 모나드를 소개하지 않으며 효과적인 논리를 작성하기 위해 모나드를 이해하지 않아도됩니다.
모나드는 데이터 구조가 더 복잡 해지는 것과 같은 방식으로 더 복잡해질 수 있지만, 의미론은 비슷하지만 구현과 구문이 다른 광범위한 범주의 모나드가 있습니다.
물론, 데이터 구조가 반복되거나 횡단 될 수있는 것과 동일한 방식으로 모나드가 평가 될 수있다.
컴파일러는 사용자 정의 모나드를 지원하거나 지원하지 않을 수 있습니다. 하스켈은 확실히 그렇습니다. 모나드 (Monad)라는 용어는 언어에서 사용되지 않지만, Ioke는 비슷한 기능을 가지고 있습니다.
내가 가장 좋아하는 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")이 있기 때문에 이것은 지나치게 단순화 된 것입니다.
나는 최근에 다른 방식으로 Monads를 생각하고 있습니다. 나는 그것들을 수학적 방식으로 실행 순서 를 추상화하여 새로운 종류의 다형성을 가능하게하는 것으로 생각했습니다 .
명령형 언어를 사용하고 일부 표현식을 순서대로 작성하는 경우 코드는 항상 순서대로 정확하게 실행됩니다.
간단한 경우 모나드를 사용할 때도 같은 느낌이 들며 순서대로 발생하는 표현식 목록을 정의합니다. 단, 사용하는 모나드에 따라 코드가 순서대로 (IO 모나드 에서처럼) 실행될 수 있으며 (리스트 모나드에서와 같이) 한 번에 여러 항목에 대해 병렬로 중단 될 수 있습니다 (아마도 모나드에서와 같이) , 재개 모나드에서와 같이 나중에 다시 시작하기 위해 도중에 일시 중지하거나, 트랜잭션 모나드에서와 같이 처음부터 되감기 및 시작하거나, 로직 모나드에서와 같이 다른 옵션을 시도하기 위해 부분적으로 되감기 할 수 있습니다. .
모나드는 다형성이기 때문에 필요에 따라 다른 모나드에서 동일한 코드를 실행할 수 있습니다.
또한 어떤 경우에는 모나드를 모나드 변환기와 함께 결합하여 동시에 여러 기능을 얻을 수 있습니다.
나는 모나드에 여전히 새로운 오전,하지만 난 읽기 정말 좋은 느낌 내가 찾은 링크를 공유 할 것이라고 생각 (사진과 함께를!) : http://www.matusiak.eu/numerodix/blog/2012/3/11/ 레이맨을위한 모나드 / (가맹 없음)
기본적으로 기사에서 얻은 따뜻하고 모호한 개념은 모나드가 기본적으로 서로 다른 기능을 구성 가능한 방식으로 작동하도록하는 어댑터라는 개념입니다. 즉, 여러 기능을 연결하여 일관성없는 리턴에 대한 걱정없이 이들을 혼합하고 일치시킬 수 있습니다 유형 등. 따라서 BIND 기능은 우리가 이러한 어댑터를 만들려고 할 때 사과를 사과로, 오렌지를 오렌지로 유지하는 역할을합니다. LIFT 기능은 "더 낮은 수준의"기능을 수행하고 BIND 기능과 함께 작동하고 작곡 할 수 있도록 "업그레이드"하는 역할을합니다.
나는 그것이 올바르게 되었기를 바랍니다. 더 중요한 것은 기사가 모나드에 대한 올바른 견해를 갖기를 바랍니다. 다른 내용이 없다면,이 기사는 모나드에 대해 더 많이 배우 겠다는 식욕을 자극하는 데 도움이되었습니다.
위의 훌륭한 답변 외에도 개념을 JavaScript 라이브러리 jQuery (및 DOM을 조작하기 위해 "메소드 체인"을 사용하는 방법)와 관련시켜 모나드를 설명하는 다음 기사 (Patrick Thomson)에 대한 링크를 제공하겠습니다. : jQuery는 Monad입니다
jQuery를 문서 자체는 아마 더 잘 알고있는 "빌더 패턴"에 대한 용어는 "모나드"하지만 회담을 참조하지 않습니다. 이것은 모나드를 모르는 사이에 적절한 모나드가 있다는 사실을 바꾸지 않습니다.
Daniels는 은유 가 아니라 Daniel Spiewak이 설명하는 것처럼 일반적인 패턴에서 나오는 실질적으로 유용한 추상화입니다.
모나드는 공통 컨텍스트를 공유하는 계산을 결합하는 방법입니다. 파이프 네트워크를 구축하는 것과 같습니다. 네트워크를 구성 할 때 네트워크를 통해 흐르는 데이터가 없습니다. 그러나 모든 비트를 '바인드'및 '반품'과 함께 파이핑을 마치면 비슷한 것을 호출 runMyMonad monad data
하고 데이터가 파이프를 통해 흐릅니다.
실제로, 모나드는 부작용과 호환되지 않는 입력 및 리턴 값 (체인)을 처리하는 함수 구성 연산자의 사용자 정의 구현입니다.
내가 올바르게 이해했다면 IEnumerable은 모나드에서 파생됩니다. 이것이 C # 세계의 사람들에게 흥미로운 접근 방식인지 궁금합니다.
가치있는 것을 위해 여기에 도움이 된 자습서 링크가 있습니다 (그리고 모나드가 무엇인지 여전히 이해하지 못했습니다).
거기에서 배울 때 가장 도움이 된 두 가지는 :
그레이엄 허튼의 책에서 8 장, "기능 파서," 하스켈 프로그래밍 . 이것은 모나드를 전혀 언급하지는 않지만 실제로 챕터를 통해 작업하고 그 안에있는 모든 내용, 특히 일련의 바인드 연산이 평가되는 방식을 이해할 수 있다면 모나드의 내부를 이해할 수 있습니다. 여러 번 시도 할 것으로 예상됩니다.
모나드에 관한 모든 튜토리얼 . 이것은 그들의 사용에 대한 몇 가지 좋은 예를 제공하며, 나는 Appendex의 비유가 나를 위해 일했다고 말해야합니다.
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 공간의 사각형이라면, 그 영역은 절대 길이가 아닌 다른 것이 될 수 없습니다. 우리는 세상이 현재와 같은 방식으로되도록하기 위해 단언 할 필요가 없습니다. 단지 프로그램이 제대로 진행되지 못하도록 현실에 영향을줍니다.
나는 틀린 것이 거의 보장되지만 이것이 누군가를 도울 수 있다고 생각하므로 누군가를 도울 수 있기를 바랍니다.
스칼라의 맥락에서 다음이 가장 간단한 정의라는 것을 알게 될 것입니다. 기본적으로 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의 정의와 동일하지 않습니다 분류 이론 모나드 의 회전에 정의되어, map
와 flatten
. 특정 매핑에서는 동일합니다. 이 프리젠 테이션은 매우 좋습니다 : http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category
이 답변은 동기 부여 예제로 시작하고 예제를 통해 작동하며 모나드의 예를 도출하고 공식적으로 "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 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
쌍은 "pair of t
and string" 유형의 값입니다 . 해당 유형을 호출하십시오 M t
.
M
유형 생성자입니다. M
단독으로는 유형을 참조하지 않지만 유형으로 M _
공백을 채우면 유형을 나타냅니다. 는 M int
int 형과 문자열의 한 쌍이다. 은 M string
문자열과 문자열의 한 쌍이다. 기타.
축하합니다. 모나드를 만들었습니다.
공식적으로 모나드는 튜플 <M, feed, wrap>
입니다.
모나드는 다음과 같은 튜플 <M, feed, wrap>
입니다.
M
형식 생성자입니다.feed
(a 걸리는 소요 함수 t
및를 반환 M u
) 및 M t
복귀를 M u
.wrap
a를 가져 와서 v
를 반환합니다 M v
.t
, u
및 v
같거나 같지 않을 수있는 세 가지 유형입니다. 모나드는 특정 모나드에 대해 입증 된 세 가지 속성을 만족시킵니다.
먹이 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
n
에f
와 같다
m
로g
n
오는g
n
로f
공식적으로 : feed(h, m) = feed(f, feed(g, m))
어디서h(x) := feed(f, g(x))
일반적으로, feed
호출 bind
(일명 >>=
하스켈)과 wrap
라고합니다 return
.
Monad
하스켈의 맥락에서 설명하려고 노력할 것 입니다.
함수형 프로그래밍에서는 함수 구성이 중요합니다. 프로그램이 작고 읽기 쉬운 기능으로 구성 될 수 있습니다.
이제 우리는 두 가지 기능이 있다고 가정 해 봅시다 : g :: Int -> String
와 f :: String -> Bool
.
우리는 할 수있는 (f . g) x
대로 그냥 같은 인 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
와 .
. 문제는 컨텍스트에없는 값을 기대하는 함수에 컨텍스트의 값을 직접 전달할 수 없다는 것입니다.
작성하기 위해 연산자를 작성 g1
하여 f1
글을 쓸 수 (f1 OPERATOR g1) x
있다면 좋을까요? g1
컨텍스트에서 값을 반환합니다. 값은 컨텍스트에서 가져와에 적용됩니다 f1
. 그렇습니다. 우리에게는 그런 연산자가 있습니다. 그것은이다 <=<
.
우리는 또한 >>=
약간 다른 문법으로 우리에게 똑같은 일을 하는 연산자를 가지고 있습니다 .
우리는 씁니다 g1 x >>= f1
. g1 x
A는 Maybe Int
값. >>=
운영자는이 걸릴 수 있습니다 Int
은 "아마도-하지-이"문맥 값을, 그리고에 적용 f1
. 결과 f1
A는, Maybe Bool
전체의 결과 일 것이다 >>=
동작.
그리고 마지막으로 왜 Monad
유용한가? 연산자 Monad
를 정의하는 형식 클래스 이기 때문에 and 연산자 를 정의하는 형식 클래스 >>=
와 거의 동일 Eq
합니다 .==
/=
결론적으로 Monad
타입 클래스는 >>=
컨텍스트의 값을 기대하지 않는 함수에 컨텍스트의 값 (모 노드 값이라고 함)을 전달할 수 있는 연산자를 정의합니다 . 컨텍스트가 처리됩니다.
여기서 기억해야 할 것이 있다면 Monad
컨텍스트의 값을 포함하는 함수 구성을 허용한다는 것 입니다.
{-# 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 x
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 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
스테이트 풀 모나드에 대한 프리미티브 get
및 put
추상화 방법으로 스테이트 액세스를 제공합니다 .
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st
모나드에 대한 상태 유형 의 기능적 종속성 을 선언합니다 . 예를 들어, a 는 상태 유형을 고유하게 결정합니다 .st
m
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)
유사하게 사용 된 장치 유형 void
C.에서
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에 정체성과 구성의 "구조"를 보존해야 C
에 D
.
Tmor Tobj
T(id) = id : T(X) -> T(X) Identity
T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
Kleisli 카테고리 범주의이 C
Kleisli 트리플 주어진다
<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)
Hask
Kleisli 범주에 형태가 부여됩니다
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
Philip Wadler : 기능 프로그래밍을위한 Monads
Simon L Peyton Jones, Philip Wadler : 명령형 기능 프로그래밍
Jonathan MD Hill, Keith Clarke : 카테고리 이론 소개, 카테고리 이론 모나드 및 기능 프로그래밍과의 관계 ´
Eugenio Moggi : 계산 개념과 모나드
그러나 왜 이론이 그렇게 추상적이어야 프로그래밍에 사용되어야 하는가?
답은 간단합니다. 컴퓨터 과학자로서 우리는 추상화 를 중요하게 생각 합니다 ! 소프트웨어 구성 요소에 대한 인터페이스를 설계 할 때 구현에 대해 가능한 한 적은 정보를 공개하기를 원합니다 . 우리는 구현을 많은 대안, 같은 '개념'의 다른 많은 '인스턴스'로 대체 할 수 있기를 원합니다. 많은 프로그램 라이브러리에 대한 일반 인터페이스를 설계 할 때, 선택한 인터페이스가 다양한 구현을 갖는 것이 더욱 중요합니다. 우리가 매우 중요하게 생각하는 것은 모나드 개념의 일반성입니다. 범주 이론이 너무 추상적 이기 때문에 개념이 프로그래밍에 매우 유용하기 때문입니다.
그러므로 우리가 아래에 제시하는 모나드의 일반화가 범주 이론과 밀접한 관련이 있다는 것은 놀라운 일이 아닙니다. 그러나 우리는 우리의 목적이 매우 실용적이라고 강조합니다. '범주 이론을 구현하는 것'이 아니라 결합기 라이브러리를 구성하는보다 일반적인 방법을 찾는 것입니다. 수학자들이 이미 우리를 위해 많은 일을 해낸 것은 단순히 우리의 행운입니다!
에서 화살표로 일반화 모나드 존 휴즈에 의해
세상에 필요한 것은 또 다른 모나드 블로그 게시물이지만, 이것이 야생에서 기존 모나드를 식별하는 데 유용하다고 생각합니다.
위의 그림은 Sierpinski triangle이라는 프랙탈로, 내가 그리는 기억할 수있는 유일한 프랙탈입니다. 도형은 위의 삼각형과 같이 자기 유사 구조로, 부분이 전체와 유사합니다 (이 경우 부모 삼각형의 스케일의 정확히 절반).
모나드는 프랙탈입니다. 모나 딕 데이터 구조가 주어지면, 그 값은 데이터 구조의 다른 값을 형성하도록 구성 될 수 있습니다. 이것이 프로그래밍에 유용한 이유이며 많은 상황에서 발생하는 이유입니다.
http://code.google.com/p/monad-tutorial/ 은이 질문을 정확하게 해결하기 위해 진행중인 작업입니다.
아래의 " {| 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 f
은 a
을 사용하여 를 추출하는 방법을 찾습니다 >>=
.
(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 a
에 Maybe b
. 이 경우 Maybe
모나드가되는 것은 중요하지 않습니다.
그러나 때로는 데이터 유형이 데이터 생성자를 내 보내지 않고 (IO를보고) 광고 된 값으로 작업하려면 선택의 여지가 없지만 모나드 인터페이스로 작업하는 것이 좋습니다.
모나드는 상태가 변하는 객체를 캡슐화하는 데 사용되는 것입니다. 수정 가능한 상태 (예 : Haskell)를 갖지 못하는 언어에서 가장 자주 발생합니다.
파일 I / O를 예로들 수 있습니다.
파일 I / O에 모나드를 사용하여 변경 상태 특성을 Monad를 사용한 코드로 분리 할 수 있습니다. 모나드 내부의 코드는 모나드 외부의 변화하는 세계 상태를 효과적으로 무시할 수 있으므로 프로그램의 전반적인 효과를 추론하기가 훨씬 쉽습니다.