모나드는 무엇입니까?
최근 하스켈을 간략히 살펴 보았는데 , 모나드가 본질적으로 무엇인지에 대한 간단하고 간결하며 실용적인 설명은 무엇일까요?
나는 내가 본 대부분의 설명이 상당히 접근하기 어렵고 실제적인 세부 사항이 부족하다는 것을 발견했습니다.
첫째 : 수학자가 아닌 경우 모나드 라는 용어 는 약간 공허합니다. 대체 용어는 실제로 유용한 것에 대해 좀 더 설명하는 계산 빌더 입니다.
실용적인 예를 요청합니다.
예 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 용어에서 모나드는 매개 변수화 된 유형으로 Monad 유형 클래스의 인스턴스이며 >>=몇 가지 다른 연산자와 함께 정의 됩니다. 평신도의 관점에서 모나드는 >>=연산이 정의 된 유형일뿐 입니다.
그 자체 >>=로 함수를 연결하는 번거로운 방법이지만 "배관"을 숨기는 do-notation이 있으면 모나 딕 연산이 매우 훌륭하고 유용한 추상화, 언어의 여러 곳에서 유용하며 유용합니다. 언어로 자신 만의 미니 언어를 만들 수 있습니다.
모나드는 왜 어려운가요?
많은 하스켈 학습자에게 모나드는 벽돌 벽처럼 부딪히는 장애물입니다. 모나드 자체가 복잡하지는 않지만 구현은 매개 변수화 된 유형, 유형 클래스 등과 같은 다른 많은 고급 Haskell 기능에 의존합니다. 문제는 Haskell I / O가 모나드를 기반으로하고 있다는 것입니다. 그리고 I / O는 아마도 새로운 언어를 배울 때 가장 먼저 이해하고 싶은 것들 중 하나 일 것입니다. 산출. 나는 당신이 언어의 다른 부분에 대한 충분한 경험을 가질 때까지 I / O를 "마법이 여기에서 일어난다"처럼 취급하는 것을 제외하고는이 닭과 계란 문제에 대한 즉각적인 해결책이 없습니다. 죄송합니다.
모나드에 대한 우수한 블로그 : http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
"모나드가 무엇인지"를 설명하는 것은 "숫자가 무엇입니까?"라고 말하는 것과 비슷합니다. 우리는 항상 숫자를 사용합니다. 하지만 숫자에 대해 전혀 모르는 사람을 만났다고 상상해보십시오. 어떻게 도대체 당신은 숫자가 무엇인지 설명 할 것인가? 그리고 그것이 왜 유용 할 수 있는지 설명하기 시작하겠습니까?
모나드는 무엇입니까? 짧은 대답 : 작업을 함께 연결하는 특정 방법입니다.
본질적으로 실행 단계를 작성하고 "바인딩 기능"과 함께 연결합니다. (Haskell에서는 이름이 >>=.) bind 연산자에 대한 호출을 직접 작성하거나 컴파일러가 해당 함수 호출을 삽입하도록하는 구문 설탕을 사용할 수 있습니다. 그러나 어느 쪽이든 각 단계는이 바인드 함수에 대한 호출로 구분됩니다.
따라서 bind 함수는 세미콜론과 같습니다. 프로세스의 단계를 분리합니다. 바인드 함수의 역할은 이전 단계의 출력을 가져와 다음 단계로 공급하는 것입니다.
너무 어렵게 들리지 않죠? 그러나 모나드 는 여러 종류가 있습니다. 왜? 어떻게?
바인드 함수 는 한 단계에서 결과를 가져 와서 다음 단계로 제공 할 수 있습니다. 그러나 그것이 "모두"라면 모나드는 실제로 그렇게 유용하지 않습니다. 그리고 그것은 이해하는 것이 중요합니다 : 모든 유용한 모나드는 단지 모나드가되는 것 외에도 다른 일을합니다. 모든 유용한 모나드는 "특별한 힘"을 가지고있어 고유하게 만듭니다.
( 특별한 작업 을 수행하지 않는 모나드 는 "정체성 모나드"라고합니다. 신원 함수와 같이 이것은 완전히 무의미한 것으로 들리지만 그렇지 않은 것으로 밝혀졌습니다 ... 그러나 그것은 또 다른 이야기입니다 ™.)
기본적으로 각 모나드는 bind 함수의 자체 구현을 가지고 있습니다. 그리고 실행 단계 사이에 험난한 작업을 수행하도록 bind 함수를 작성할 수 있습니다. 예를 들면 :
각 단계가 성공 / 실패 표시기를 반환하면 이전 단계가 성공한 경우에만 bind가 다음 단계를 실행하도록 할 수 있습니다. 이러한 방식으로 실패한 단계는 사용자의 조건부 테스트없이 전체 시퀀스를 "자동으로"중단합니다. ( 실패 모나드 .)
이 아이디어를 확장하면 "예외"를 구현할 수 있습니다. ( Error Monad 또는 Exception Monad .) 언어 기능이 아니라 직접 정의하기 때문에 작동 방식을 정의 할 수 있습니다. (예 : 처음 두 예외를 무시하고 세 번째 예외가 발생할 때만 중단 할 수 있습니다.)
각 단계가 여러 결과를 반환하도록 할 수 있으며 bind 함수를 반복하여 각 단계를 다음 단계로 제공 할 수 있습니다. 이런 식으로 여러 결과를 처리 할 때 모든 곳에서 루프를 계속 작성할 필요가 없습니다. 바인드 기능은 "자동으로"모든 작업을 수행합니다. ( 리스트 모나드 .)
한 단계에서 다른 단계로 "결과"를 전달하는 것 외에도 bind 함수 가 추가 데이터 를 전달 하도록 할 수 있습니다 . 이 데이터는 이제 소스 코드에 표시되지 않지만 모든 함수에 수동으로 전달할 필요없이 어디서나 계속 액세스 할 수 있습니다. ( 리더 모나드 .)
"추가 데이터"를 대체 할 수 있도록 만들 수 있습니다. 이를 통해 실제로 파괴적인 업데이트를 수행하지 않고도 파괴적인 업데이트 를 시뮬레이션 할 수 있습니다 . ( 주 모나드 와 그 사촌 작가 모나드 .)
파괴적인 업데이트 만 시뮬레이션 하기 때문에 실제 파괴적인 업데이트 로는 불가능한 일을 간단하게 수행 할 수 있습니다. 예를 들어 마지막 업데이트를 실행 취소 하거나 이전 버전으로 되돌릴 수 있습니다.
계산을 일시 중지 할 수있는 모나드를 만들 수 있으므로 프로그램을 일시 중지하고 내부 상태 데이터를 수정 한 다음 다시 시작할 수 있습니다.
"연속"을 모나드로 구현할 수 있습니다. 이것은 사람들의 마음 을 깨뜨릴 수 있습니다 !
이 모든 것이 모나드로 가능합니다. 물론이 모든 것은 모나드 없이도 완벽하게 가능합니다 . 그것은 크게 그냥 쉽게 모나드를 사용하여.
사실, Monads에 대한 일반적인 이해와는 달리 상태와 관련이 없습니다. 모나드는 단순히 물건을 래핑하고 래핑을 풀지 않고 래핑 된 물건에 대한 작업을 수행하는 메서드를 제공하는 방법입니다.
예를 들어 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 ++ 함수는 인수에 따라 숫자를 반환하는 것 이상을 수행 할 수 있습니다. 전역 변수의 값을 읽고 쓸 수있을뿐만 아니라 화면에 출력을 쓰고 사용자로부터 입력을받을 수도 있습니다. 그러나 순수한 기능적 언어에서 함수는 인수로 제공된 내용 만 읽을 수 있으며 세상에 영향을 미칠 수있는 유일한 방법은 반환하는 값을 통해서입니다.
모나드는 >>=(일명 bind) 및 return(일명 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 코드를 작성해야합니다.
먼저 펑터가 무엇인지 이해해야합니다. 그 전에 고차 함수를 이해하십시오.
고차 함수는 단순히 인자로서의 기능을 취하는 함수이다.
펑는 임의의 타입 구조이다 T고차 함수가 존재하는, 호출 map, 그 변환 타입의 함수 a -> b(임의의 두 유형을 지정 a하고 b함수로) T a -> T b. 이 map함수는 또한 다음식이 모두 p및 q(Haskell 표기법)에 대해 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해야합니다. 즉 연관성 이 있어야합니다. 당신이 값이있는 경우 있음이 수단 x유형을 [[[a]]]다음 join (join x)과 같아야한다 join (map join x). 그리고 그런 것에 pure대한 정체성이어야합니다 .joinjoin (pure x) == x
[면책 조항 : 나는 여전히 모나드를 완전히 그룩하려고 노력하고 있습니다. 다음은 내가 지금까지 이해 한 것입니다. 그것이 틀렸다면, 아는 사람이 카펫 위에서 나를 부를 것입니다.]
Arnar는 다음과 같이 썼습니다.
모나드는 단순히 물건을 래핑하고 래핑을 풀지 않고 래핑 된 물건에 대한 작업을 수행하는 메서드를 제공하는 방법입니다.
그게 바로 그것입니다. 아이디어는 다음과 같습니다.
당신은 어떤 종류의 가치를 가지고 몇 가지 추가 정보로 포장합니다. 값이 특정 종류 (예 : 정수 또는 문자열) 인 것처럼 추가 정보도 특정 종류입니다.
예를 들어 추가 정보는
Maybe또는IO.그런 다음 추가 정보를 전달하면서 래핑 된 데이터에 대해 작업 할 수있는 연산자가 있습니다. 이러한 연산자는 추가 정보를 사용하여 래핑 된 값에 대한 작업 동작을 변경하는 방법을 결정합니다.
예를 들어 a
Maybe Int는 aJust Int또는Nothing. 이제 aMaybe Int를에 추가Maybe Int하면 연산자가 둘 다Just Int내부에 있는지 확인하고, 그렇다면Ints 를 풀고 더하기 연산자를 전달한 다음 결과Int를 새 항목으로 다시 래핑합니다Just Int(유효한Maybe Int), 따라서Maybe Int. 그러나 그들 중 하나가Nothing내부이면이 연산자는 즉시를 반환Nothing합니다Maybe Int. 이는 다시 유효합니다 . 그런 식으로, 당신은 당신Maybe Int의 s가 정상적인 숫자 인 척하고 그들에 대해 정규 수학을 수행 할 수 있습니다. 만약 당신이를 얻는다면Nothing, 당신의 방정식은 모든 곳 에서 쓰레기 수표를 할 필요없이Nothing여전히 올바른 결과를 산출 할 것 입니다.
그러나 예는 Maybe. 추가 정보가 IO이면 IOs에 대해 정의 된 특수 연산자 가 대신 호출되고 추가를 수행하기 전에 완전히 다른 작업을 수행 할 수 있습니다. (좋아요, 두 개의 IO Ints를 더하는 것은 아마도 말도 안되는 일입니다. 아직 확실하지 않습니다.) (또한 Maybe예제에 주의를 기울인 경우 "추가 항목으로 값을 래핑"하는 것이 항상 올바른 것은 아니라는 것을 알았습니다.하지만 어렵습니다. 정확하고 정확하고 정확해야합니다.)
기본적으로 "monad"는 대략 "패턴"을 의미 합니다. 그러나 비공식적으로 설명되고 구체적으로 명명 된 Patterns로 가득 찬 책 대신, 이제 프로그램에서 새로운 패턴 을 선언 할 수있는 구문 및 모든 언어 구조 가 있습니다 . (여기서 부정확 한 것은 모든 패턴이 특정 형식을 따라야한다는 것이므로 모나드는 패턴만큼 일반적이지는 않습니다.하지만 이것이 대부분의 사람들이 알고 이해하는 가장 가까운 용어라고 생각합니다.)
이것이 사람들이 모나드를 매우 혼란스럽게 생각하는 이유입니다. 모나드는 그러한 일반적인 개념이기 때문입니다. 무엇을 모나드로 만드는지 묻는 것은 무엇을 패턴으로 만드는지 묻는 것과 비슷하게 모호합니다.
그러나 패턴의 아이디어에 대한 언어의 구문 지원이 갖는 의미를 생각해보십시오. Gang of Four 책 을 읽고 특정 패턴의 구성을 암기하는 대신 이 패턴을 구현하는 코드를 불가지론 자로 작성하면됩니다. 한 번 일반적인 방법으로 완료됩니다! 그런 다음 방문자, 전략 또는 Façade와 같은 패턴을 반복해서 다시 구현하지 않고도 코드에서 작업을 장식하여 재사용 할 수 있습니다!
그래서 모나드 를 이해하는 사람들 이 모나드를 매우 유용하다고 생각하는 이유 입니다. 지적 속물들이 이해에 자부심을 느끼는 것은 상아탑 개념이 아니라 실제로 코드를 더 간단하게 만드는 것입니다.
많은 노력 끝에 마침내 모나드를 이해했다고 생각합니다. 압도적으로 가장 많이 득표 한 답변에 대한 내 자신의 긴 비판을 다시 읽은 후이 설명을 제공 할 것입니다.
모나드를 이해하기 위해 답해야 할 세 가지 질문이 있습니다.
- 모나드가 필요한 이유는 무엇입니까?
- 모나드는 무엇입니까?
- 모나드는 어떻게 구현됩니까?
내 원래 의견에서 언급했듯이 질문 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특정 유형에서 작동하도록 특별히 설계된 여러 함수와 함께 정의 하는 유형 (예 :)이 :있으면 자동으로 (bind) 와 같은 연산자를 정의하여 이러한 것들의 전체 패키지를 "모나드"로 바꿀 수 있습니다. 왼쪽의 반환 값을 오른쪽의 함수 매개 변수에 공급하고 lift일반 함수를 특정 종류의 바인드 연산자와 함께 작동하는 함수로 바꾸는 연산자를 제공합니다.
모나드는 어떻게 구현됩니까?
그 세부 사항으로 뛰어들 수있는 다른 답변을 참조하십시오.
( What is a monad? 에서 답변을 참조하십시오 . )
모나드에 대한 좋은 동기는 sigfpe (Dan Piponi)의 You could Have Invented Monads입니다! (그리고 아마도 당신은 이미) . 있다 다른 모나드 튜토리얼의 많은 misguidedly 다양한 비유를 사용하여 "간단한 약관"에 모나드를 설명하려고 많은 것이이 :이 인 모나드 튜토리얼 사기 야 ; 그들을 피하십시오.
DR MacIver가 말한대로 귀하의 언어가 좋지 않은 이유 를 알려주십시오 .
그래서 Haskell에 대해 내가 싫어하는 것 :
명백한 것부터 시작합시다. 모나드 튜토리얼. 아니, 모나드가 아닙니다. 특히 튜토리얼. 그들은 끝이없고 과장되어 있으며 지루한 신입니다. 또한 실제로 도움이된다는 설득력있는 증거를 본 적이 없습니다. 클래스 정의를 읽고, 코드를 작성하고, 무서운 이름을 극복하십시오.
Maybe 모나드를 이해한다고? 좋아,가는 중이 야. 다른 모나드를 사용하기 시작하면 조만간 일반적으로 모나드가 무엇인지 이해하게 될 것입니다.
[수학적 지향적이라면 수십 개의 튜토리얼을 무시하고 정의를 배우거나 카테고리 이론의 강의를 따르기를 원할 수 있습니다.) 정의 의 주요 부분은 Monad M이 각각에 대해 정의하는 "유형 생성자"를 포함한다는 것입니다. 기존 유형 "T", 새로운 유형 "MT", "일반"유형과 "M"유형 사이를 오가는 몇 가지 방법]
또한 놀랍게도 모나드에 대한 최고의 소개 중 하나는 실제로 함수형 프로그래밍을위한 Philip Wadler의 모나드 인 모나드를 소개하는 초기 학술 논문 중 하나입니다 . 실제로 많은 인공 튜토리얼과 달리 실용적이고 사소하지 않은 동기 부여 예제가 있습니다.
모나드는 사실상 "유형 연산자"의 한 형태입니다. 세 가지를 할 것입니다. 먼저 한 유형의 값을 다른 유형 (일반적으로 "모나드 유형"이라고 함)으로 "래핑"(또는 그렇지 않으면 변환)합니다. 둘째, 모나 딕 유형에서 사용할 수있는 기본 유형에서 모든 연산 (또는 함수)을 사용할 수 있도록합니다. 마지막으로 합성 모나드를 생성하기 위해 자기 자신을 다른 모나드와 결합하는 것을 지원합니다.
"maybe monad"는 기본적으로 Visual Basic / C #의 "nullable 유형"과 동일합니다. nullable이 아닌 형식 "T"를 사용하여 "Nullable <T>"로 변환 한 다음 Nullable <T>에서 모든 이항 연산자가 의미하는 바를 정의합니다.
부작용은 유사하게 표현됩니다. 함수의 반환 값과 함께 부작용에 대한 설명을 포함하는 구조가 생성됩니다. "리프트 된"연산은 함수간에 값이 전달 될 때 부작용을 복사합니다.
여러 가지 이유로 이해하기 쉬운 "유형 연산자"이름이 아니라 "모나드"라고합니다.
- 모나드는 수행 할 수있는 작업에 제한이 있습니다 (자세한 내용은 정의 참조).
- 세 가지 연산이 관련되어 있다는 사실과 함께 이러한 제한은 수학의 모호한 분기 인 범주 이론에서 모나드라는 구조를 따릅니다.
- "순수한"기능적 언어의 지지자들에 의해 설계되었습니다.
- 모호한 수학 분야와 같은 순수한 기능적 언어의 지지자
- 수학이 모호하고 모나드는 특정 프로그래밍 스타일과 관련되어 있기 때문에 사람들은 모나드라는 단어를 일종의 비밀 악수로 사용하는 경향이 있습니다. 이 때문에 아무도 더 나은 이름에 투자하지 않았습니다.
몇 년 전에이 질문에 대한 답변을 제공 한 후 다음을 통해 해당 답변을 개선하고 단순화 할 수 있다고 생각합니다.
모나드는 합성 중 입력 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함수 에서만 사용 가능한 상태로 유지되는 모델 입니다.
따라서 모나드는 세 가지입니다.
M모나드 관련 정보를 보관 하는 쉘bind구성된 함수를 쉘 내에서 찾은 콘텐츠 값에 적용 할 때이 쉘 정보를 사용하도록 구현 된 함수a -> Mb모나 딕 관리 데이터를 포함하는 결과를 생성하는 , 형식의 구성 가능한 함수 .
일반적으로 함수에 대한 입력은 오류 조건과 같은 것을 포함 할 수있는 출력보다 훨씬 더 제한적입니다. 따라서 Mb결과 구조는 일반적으로 매우 유용합니다. 예를 들어 나누기 연산자는 제수가 일 때 숫자를 반환하지 않습니다 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괄호의 힘, 상기 초기 평가 binding들 f과 g만 예상하는 기능을 초래할 것이다 Ma순서가 완료 할 bind. 따라서에 대한 평가는 Ma해당 값이에 적용되고 f결과 가에 적용되기 전에 결정되어야합니다 g.
모나드는 데이터에 대한 추상 데이터 유형이 무엇인지 흐름을 제어합니다.
즉, 많은 개발자가 세트, 목록, 사전 (또는 해시 또는 맵) 및 트리에 대한 아이디어에 익숙합니다. 이러한 데이터 유형에는 특수한 경우가 많이 있습니다 (예 : InsertionOrderPreservingIdentityHashMap).
그러나 프로그램 "흐름"에 직면했을 때 많은 개발자는 if, switch / case, do, while, goto (grr) 및 (아마도) 클로저보다 더 많은 구조에 노출되지 않았습니다.
따라서 모나드는 단순히 제어 흐름 구조입니다. 모나드를 대체하는 더 좋은 문구는 '제어 유형'입니다.
따라서 모나드는 제어 로직, 명령문 또는 함수를위한 슬롯을 가지고 있습니다. 데이터 구조에서 동등한 것은 일부 데이터 구조를 사용하여 데이터를 추가하고 제거 할 수 있다는 것입니다.
예를 들어, "if"모나드 :
if( clause ) then block
가장 단순한 부분에는 절과 블록이라는 두 개의 슬롯이 있습니다. if모나드는 보통 조항의 결과를 평가하기 위해 구축, 거짓되지 않을 경우, 블록을 평가한다. 많은 개발자가 'if'를 배울 때 모나드를 접하지 않으며 효과적인 논리를 작성하기 위해 모나드를 이해할 필요가 없습니다.
모나드는 데이터 구조가 더 복잡해질 수있는 것과 같은 방식으로 더 복잡해질 수 있지만, 유사한 의미를 가질 수 있지만 구현과 구문이 다른 모나드의 광범위한 범주가 많이 있습니다.
물론 데이터 구조가 반복되거나 순회되는 것과 동일한 방식으로 모나드가 평가 될 수 있습니다.
컴파일러는 사용자 정의 모나드를 지원하거나 지원하지 않을 수 있습니다. Haskell은 확실히 그렇습니다. Ioke에는 모나드라는 용어가 언어에서 사용되지 않지만 유사한 기능이 있습니다.
내가 가장 좋아하는 Monad 튜토리얼 :
http://www.haskell.org/haskellwiki/All_About_Monads
(Google 검색에서 "monad tutorial"에 대한 17 만 건의 히트 중!)
@Stu : 모나드의 요점은 (보통) 순차 의미론을 순수한 코드에 추가 할 수 있도록하는 것입니다. 예를 들어, 모나드를 구성하고 (Monad Transformers 사용) 오류 처리, 공유 상태 및 로깅을 사용한 구문 분석과 같은 더 흥미롭고 복잡한 결합 의미를 얻을 수도 있습니다. 이 모든 것은 순수한 코드에서 가능합니다. 모나드는 그것을 추상화하고 모듈 라이브러리에서 재사용 할 수있게합니다 (항상 프로그래밍에 좋습니다). 또한 편리한 구문을 제공하여 필수로 보이게합니다.
Haskell에는 이미 연산자 오버로딩 [1]이 있습니다. Java 또는 C #에서 인터페이스를 사용하는 방식과 유사한 방식으로 유형 클래스를 사용하지만 Haskell은 + && 및>와 같은 영숫자가 아닌 토큰도 중위 식별자로 허용합니다. "세미콜론 오버로딩"[2]을 의미하는 경우에는 연산자 오버로딩뿐입니다. 흑 마법처럼 들리며 "세미콜론에 과부하"를 요구하는 것처럼 들리지만 (진취적인 Perl 해커들이이 아이디어를 받아들이고 있는 그림을 그린다) 요점은 순전히 기능적인 코드가 명시 적 시퀀싱을 요구하거나 허용하지 않기 때문에 모나드 없이는 세미콜론이 없다는 것입니다.
이 모든 것이 필요한 것보다 훨씬 더 복잡하게 들립니다. sigfpe의 기사는 꽤 멋지지만 Haskell을 사용하여 설명하기 위해 Haskell을 이해하고 Monads를 Grok Haskell로 이해하는 닭고기와 계란 문제를 깨뜨리지 못합니다.
[1] 이것은 모나드와는 별개의 문제이지만 모나드는 Haskell의 연산자 오버로딩 기능을 사용합니다.
[2] 모나 딕 동작을 연결하는 연산자가 >> = ( "bind"로 발음 됨)이지만 중괄호와 세미콜론 및 / 또는 들여 쓰기와 줄 바꿈을 사용할 수있는 구문 설탕 ( "do")이 있기 때문에 이것은 지나치게 단순화 된 것입니다.
최근에 모나드를 다른 방식으로 생각하고 있습니다. 나는 그것들을 수학적 방식으로 실행 순서 를 추상화하는 것으로 생각하고 있어 새로운 종류의 다형성을 가능하게합니다.
명령형 언어를 사용하고 일부 표현식을 순서대로 작성하면 코드는 항상 그 순서대로 정확하게 실행됩니다.
그리고 간단한 경우에, 모나드를 사용할 때도 똑같이 느껴집니다. 순서대로 발생하는 표현식 목록을 정의합니다. 사용하는 모나드에 따라 코드가 순서대로 (IO 모나드에서와 같이) 여러 항목에 대해 동시에 (List 모나드에서와 같이) 병렬로 실행될 수 있다는 점을 제외하고는 도중에 중단 될 수 있습니다 (Maybe 모나드에서와 같이). , (재개 모나드에서와 같이) 나중에 재개하기 위해 중간에 일시 중지하거나 (트랜잭션 모나드에서와 같이) 되감기하고 처음부터 시작하거나 (로직 모나드에서와 같이) 다른 옵션을 시도하기 위해 도중에 되 감을 수 있습니다. .
그리고 모나드는 다형성이기 때문에 필요에 따라 다른 모나드에서 동일한 코드를 실행할 수 있습니다.
또한 경우에 따라 모나드 변환기와 함께 모나드를 결합하여 동시에 여러 기능을 얻을 수 있습니다.
나는 아직 모나드를 처음 접했지만 읽을 수있는 정말 좋은 링크를 공유 할 것이라고 생각했습니다. (사진 포함 !!) : http://www.matusiak.eu/numerodix/blog/2012/3/11/ 평신도를위한 monads / (제휴 없음)
기본적으로이 기사에서 얻은 따뜻하고 모호한 개념은 모나드는 기본적으로 서로 다른 기능이 구성 가능한 방식으로 작동 할 수있게 해주는 어댑터라는 개념이었습니다. 유형 등. 따라서 BIND 기능은 이러한 어댑터를 만들 때 사과에 사과를, 오렌지에 오렌지를 유지하는 역할을합니다. LIFT 기능은 "낮은 수준"기능을 가져와 BIND 기능과 함께 작동하고 구성 가능하도록 "업그레이드"하는 역할을합니다.
나는 그것이 옳았기를 바라며, 더 중요한 것은 기사가 모나드에 대해 유효한 견해를 가지기를 바랍니다. 이 기사는 모나드에 대해 더 많이 배우고 자하는 나의 욕구를 자극하는 데 도움이되었습니다.
위의 훌륭한 답변 외에도 개념을 JavaScript 라이브러리 jQuery (및 DOM을 조작하기 위해 "메서드 체이닝"을 사용하는 방법)와 관련하여 모나드를 설명하는 다음 기사 (Patrick Thomson 작성)에 대한 링크를 제공하겠습니다. : jQuery는 모나드입니다.
jQuery를 문서 자체는 아마 더 잘 알고있는 "빌더 패턴"에 대한 용어는 "모나드"하지만 회담을 참조하지 않습니다. 이것은 당신이 그것을 깨닫지도 못한 채 적절한 모나드를 가지고 있다는 사실을 바꾸지 않습니다.
Monads Are Not Metaphors 이지만 Daniel Spiewak이 설명하는 것처럼 일반적인 패턴에서 나오는 실질적으로 유용한 추상화입니다.
모나드는 공통 컨텍스트를 공유하는 계산을 함께 결합하는 방법입니다. 마치 파이프 네트워크를 구축하는 것과 같습니다. 네트워크를 구성 할 때 네트워크를 통해 흐르는 데이터가 없습니다. 그러나 'bind'및 'return'과 함께 모든 비트를 연결하면 다음과 같은 것을 호출 runMyMonad monad data하고 데이터는 파이프를 통해 흐릅니다.
실제로 모나드는 부작용과 호환되지 않는 입력 및 반환 값 (체인 연결 용)을 처리하는 함수 합성 연산자의 사용자 정의 구현입니다.
올바르게 이해했다면 IEnumerable은 모나드에서 파생됩니다. 이것이 C # 세계의 우리들에게 흥미로운 접근 방식이 아닐까요?
그만한 가치는 여기 저에게 도움이 된 튜토리얼에 대한 링크가 있습니다 (아직도 모나드가 무엇인지 이해하지 못했습니다).
- http://osteele.com/archives/2007/12/overloading-semicolon
- http://spbhug.folding-maps.org/wiki/MonadsEn
- http://www.loria.fr/~kow/monads/
거기에 대해 배울 때 가장 도움이 된 두 가지는 다음과 같습니다.
Graham Hutton의 저서 Programming in Haskell 에서 8 장, "Functional Parsers" . 이것은 실제로 모나드를 전혀 언급하지 않지만 장을 통해 작업하고 그 안의 모든 것을 정말로 이해할 수 있다면, 특히 일련의 바인드 작업이 평가되는 방식을 이해한다면 모나드의 내부를 이해할 것입니다. 몇 번의 시도가 필요합니다.
모나드에 관한 모든 것 튜토리얼 . 이것은 그들의 사용에 대한 몇 가지 좋은 예를 제공하며 나는 Appendex의 비유가 나를 위해 일했다고 말해야합니다.
Monoid는 Monoid 및 지원되는 유형에 정의 된 모든 작업이 항상 Monoid 내에서 지원되는 유형을 반환하도록 보장하는 것으로 보입니다. 예 : 모든 숫자 + 모든 숫자 = 숫자, 오류 없음.
나눗셈은 두 개의 분수를 받아들이고 분수를 반환하는데, 이것은 haskell somewhy (이것이 분수가되는 이유)에서 무한대로 0으로 나누기를 정의한 분수를 반환합니다.
어쨌든 모나드는 연산 체인이 예측 가능한 방식으로 작동하는지 확인하는 방법 일 뿐이며, x로 호출 된 Num-> Num의 다른 함수로 구성된 Num-> Num이라고 주장하는 함수는 그렇지 않습니다. 미사일을 발사하십시오.
반면에 미사일을 발사하는 기능이 있다면 미사일을 발사하는 다른 기능으로 구성 할 수 있습니다. 왜냐하면 우리의 의도는 분명하기 때문입니다. 미사일을 발사하고 싶습니다. 이상한 이유로 "Hello World"를 인쇄합니다.
Haskell에서 main은 IO () 또는 IO [()] 유형이며, 그 범위가 이상하고 이에 대해 논의하지 않겠습니다.
메인이 있으면 일련의 작업을 수행하고 싶습니다. 프로그램을 실행하는 이유는 일반적으로 IO를 통해 효과를 생성하기 위해서입니다. 따라서 IO를 수행하기 위해 기본적으로 IO 작업을 함께 연결할 수 있습니다.
내가 "IO를 반환"하지 않는 작업을 시도하면 프로그램은 체인이 흐르지 않는다고 불평하거나 기본적으로 "이것이 우리가하려는 작업-IO 작업과 어떤 관련이 있는가"라고 불평 할 것입니다. 프로그래머는 이탈하지 않고 미사일 발사에 대해 생각하지 않고 생각의 기차를 유지하면서 분류 알고리즘을 생성합니다.
기본적으로 모나드는 "여기에서 숫자를 반환하는이 함수를 알고 있습니다. 실제로 항상 작동하는 것은 아닙니다. 때로는 숫자를 생성 할 수 있으며, 때로는 아무것도 생성하지 않습니다. 마음". 이것을 알면, 모나 딕 액션을 주장하려고하면, 모나 딕 액션은 컴파일 타임 예외로 작동 할 수 있습니다. "야, 이것은 실제로 숫자가 아닙니다. 이것은 숫자 일 수 있습니다.하지만 이것을 가정 할 수는 없습니다. 흐름이 허용되는지 확인합니다. " 이는 예측할 수없는 프로그램 동작을 상당히 방지합니다.
모나드는 순수성이나 통제에 관한 것이 아니라 모든 행동이 예측 가능하고 정의되거나 컴파일되지 않는 범주의 정체성을 유지하는 것 같습니다. 무언가를 할 것으로 예상되면 아무것도 할 수없고, 아무것도하지 않을 것으로 예상되면 (보이는) 무언가를 할 수 없습니다.
내가 모나드에 대해 생각할 수있는 가장 큰 이유는 절차 적 / OOP 코드를 살펴보면 프로그램이 시작되거나 끝나는 위치를 알지 못한다는 것을 알게 될 것입니다. 보는 것은 많은 점프와 많은 수학입니다. , 마법, 미사일. 당신은 그것을 유지할 수 없을 것이고, 가능하다면, 당신이 프로그램의 어떤 부분을 이해하기 전에 전체 프로그램에 대해 마음을 감싸는 데 상당한 시간을 할애 할 것입니다. 왜냐하면이 맥락에서 모듈화는 상호 의존적 인 "섹션"을 기반으로하기 때문입니다. 코드는 효율성 / 상호 관계를 보장하기 위해 가능한 한 관련되도록 최적화됩니다. 모나드는 매우 구체적이고 정의에 따라 잘 정의되어 있으며 프로그램 흐름이 분석이 가능하고 분석하기 어려운 부분을 분리 할 수 있는지 확인합니다. 모나드는 " 또는 우주를 파괴하거나 심지어 시간을 왜곡합니다. 우리는 그것이 무엇인지 알지 못하며 보장 할 수 없습니다. 모나드는 그것이 무엇인지 보증합니다. 매우 강력합니다. 또는 우주를 파괴하거나 심지어 시간을 왜곡합니다. 우리는 그것이 그것이 무엇인지를 알지 못하며 보장 할 수 없습니다. 모나드는 그것이 무엇인지 보증합니다. 매우 강력합니다.
"실제 세계"의 모든 것은 혼동을 방지하는 명확한 관찰 가능한 법칙에 묶여 있다는 점에서 모나드처럼 보입니다. 이것은 우리가 클래스를 만들기 위해이 객체의 모든 연산을 모방해야한다는 것을 의미하지 않습니다. 대신 단순히 "정사각형은 정사각형"이라고 말할 수 있습니다. 정사각형이나 원형도 아닙니다. "정사각형에는 면적이 있습니다. 그 중 하나의 길이에 기존 차원을 곱한 것입니다. 어떤 정사각형을 가지고 있든 2D 공간에서 정사각형이라면 면적은 절대적으로 길이 제곱이 될 수 없습니다. 증명하는 것은 거의 사소한 일입니다. 이것은 매우 강력합니다. 우리는 세상이있는 그대로인지 확인하기 위해 주장 할 필요가 없습니다. 우리는 프로그램이 궤도에서 벗어나는 것을 막기 위해 현실의 함축을 사용합니다.
Im은 틀렸다는 것이 거의 보장되었지만 이것이 누군가를 도울 수 있다고 생각하므로 누군가에게 도움이되기를 바랍니다.
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)
참고 엄격히 말해서 함수형 프로그래밍에서 모나드 의 정의 는 및의 순서로 정의되는 범주 이론 의 모나드 정의와 동일하지 않습니다 . 특정 매핑에서는 동일하지만. 이 프레젠테이션은 매우 좋습니다. http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-categorymapflatten
이 답변은 동기를 부여하는 예제로 시작하여 예제를 통해 작동하며 모나드의 예제를 도출하고 공식적으로 "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것입니다 통과 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 _공백을 채운 후에는 유형을 참조합니다. An M int은 int와 문자열의 쌍입니다. An 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 - 가져
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.
나는 MonadHaskell의 맥락에서 설명하려고 노력할 것 입니다.
함수형 프로그래밍에서는 함수 구성이 중요합니다. 그것은 우리 프로그램이 작고 읽기 쉬운 기능으로 구성 될 수 있도록합니다.
두 가지 기능이 있다고 가정 해 봅시다 : g :: Int -> String및 f :: String -> Bool.
우리는 할 수 있습니다 . 이것은 값 이 어디에 (f . g) x있는 것과 똑같습니다 .f (g x)xInt
한 함수의 결과를 다른 함수에 구성 / 적용 할 때 유형을 일치시키는 것이 중요합니다. 위의 경우에서 반환하는 결과 유형은에서 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 xA는 Maybe Int값. >>=운영자는이 걸릴 수 있습니다 Int은 "아마도-하지-이"문맥 값을, 그리고에 적용 f1. 결과 f1A는, Maybe Bool전체의 결과 일 것이다 >>=동작.
마지막으로 왜 Monad유용할까요? 연산자 Monad를 정의하는 유형 클래스 이기 때문에 및 연산자 를 정의하는 유형 클래스 >>=와 거의 동일 Eq합니다 .==/=
결론적으로, Monad타입 클래스는 >>=컨텍스트에서 값을 기대하지 않는 함수에 컨텍스트의 값 (이러한 모나 딕 값이라고 부름)을 전달할 수 있는 연산자를 정의합니다 . 컨텍스트가 처리됩니다.
여기서 기억해야 할 사항이 있다면 Monad컨텍스트의 값을 포함하는 함수 구성을 허용한다는 것 입니다.
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오른쪽과 왼쪽의 정체성입니다.
Kleisli 트리플
프로그래밍에서 모나드는 모나드 유형 클래스의 인스턴스가있는 펑터 유형 생성자입니다. 정의와 구현에는 몇 가지 동등한 변형이 있으며, 각각 모나드 추상화에 대해 약간 다른 직관을 가지고 있습니다.
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
Functor 계산 에는 유형이 있습니다.
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. Extension 또는 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 화살표를 구성하여 오른쪽 화살표 적용 결과에 왼쪽 화살표를 적용합니다.
모나드 유형 클래스의 인스턴스는 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 :: * -> *
펑터로 해석되고,
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에서 ID 모나드는 정의됩니다.
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 -?
옵션 모나드는 기본 형태의 예외 처리를 다룹니다.
(-? 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 = []
모나드 유형 클래스를 정의 할 때 extension 대신 =<<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
모든 모나드는 펑 터일뿐만 아니라 모든 응용 프로그램은 펑터이고 모든 모나드는 응용 프로그램이기 때문입니다.
명령형 의사 코드 인 목록 모나드 사용
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 표기법과 모나드 이해는 중첩 된 바인드 표현식의 구문 설탕입니다. bind 연산자는 모나 딕 결과의 로컬 이름 바인딩에 사용됩니다.
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와 <|>모노 이드를 형성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 ptrue이면 가드는를 생성 [()]하고의 정의에 >>따라 로컬 상수 함수를 생성합니다.
\ _ -> return p
결과에 적용됩니다 (). false이면 가드는 목록 모나드의 fail( [])를 생성하며 Kleisli 화살표가 적용될 결과를 생성하지 >>않으므로 p건너 뜁니다.
상태
악명 높게 모나드는 상태 저장 계산을 인코딩하는 데 사용됩니다.
상태 프로세서 함수
forall st t. st -> (t, st)
상태를 전환하고 st결과를 산출합니다 t. 상태는 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 (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 는 상태 유형을 고유하게 결정합니다 .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)과 마찬가지로 블록의 마지막 문은 수익률을 나타냅니다. bind 연산자는 "프로그래밍 가능한 세미콜론"이라고도합니다.
구조화 된 명령형 프로그래밍의 반복 제어 구조 프리미티브는 모나드 방식으로 에뮬레이션됩니다.
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 세계 상태 프로세서 모나드는 기능적 표시 및 명령 적 운영 의미론의 순수 하스켈과 실제 세계의 조정입니다. 실제 엄격한 구현과 유사한 유사점 :
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입니다.
펑 터는 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. 펑 터는 C, in 의 "구조"인 형태 정체성과 구성을 보존해야합니다 D.
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)
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
모나드는 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 : 함수형 프로그래밍을위한 모나드
Simon L Peyton Jones, Philip Wadler : 명령형 함수 프로그래밍
Jonathan MD Hill, Keith Clarke : 범주 이론 소개, 범주 이론 모나드 및 함수 프로그래밍과의 관계 ´
Eugenio Moggi : 계산 및 모나드 개념
그런데 왜 그렇게 추상적 인 이론이 프로그래밍에 유용해야할까요?
대답은 간단합니다. 컴퓨터 과학자로서 우리는 추상화 를 중요하게 생각 합니다 ! 소프트웨어 구성 요소에 대한 인터페이스를 설계 할 때 구현에 대해 가능한 한 적게 표시하기를 원합니다 . 우리는 구현을 많은 대안, 동일한 '개념'의 다른 많은 '인스턴스'로 대체 할 수 있기를 원합니다. 많은 프로그램 라이브러리에 대한 일반 인터페이스를 설계 할 때 선택한 인터페이스가 다양한 구현을 갖는 것이 훨씬 더 중요합니다. 그것은 우리가 매우, 그것이 가치 모나드 개념의 보편성입니다 때문에 카테고리 이론의 개념 프로그래밍을위한 매우 유용한 너무 추상적이다.
따라서 아래에 제시된 모나드의 일반화가 범주 이론과 밀접한 관련이 있다는 것은 놀라운 일이 아닙니다. 그러나 우리는 우리의 목적이 매우 실용적이라고 강조합니다. '범주 이론을 구현하는 것'이 아니라 결합 자 라이브러리를 구조화하는보다 일반적인 방법을 찾는 것입니다. 수학자들이 이미 우리를 위해 많은 작업을 수행 한 것은 단순히 우리의 행운입니다!
에서 화살표로 일반화 모나드 존 휴즈에 의해
세계가 필요로하는 것은 또 다른 모나드 블로그 게시물이지만, 이것이 야생에서 기존 모나드를 식별하는 데 유용하다고 생각합니다.
위는 Sierpinski 삼각형이라는 프랙탈로, 제가 기억할 수있는 유일한 프랙탈입니다. 프랙털은 위의 삼각형과 같은 자기 유사 구조로, 부분이 전체와 유사합니다 (이 경우 정확히 부모 삼각형의 절반 크기).
모나드는 프랙탈입니다. 모나 딕 데이터 구조가 주어지면 그 값을 구성하여 데이터 구조의 다른 값을 형성 할 수 있습니다. 이것이 프로그래밍에 유용한 이유이며 많은 상황에서 발생하는 이유입니다.
http://code.google.com/p/monad-tutorial/ 은이 질문을 정확히 해결하기 위해 진행중인 작업입니다.
모나드는 상태가 변경되는 객체를 캡슐화하는 데 사용되는 것입니다. 수정 가능한 상태 (예 : Haskell)를 허용하지 않는 언어에서 가장 자주 발생합니다.
예를 들어 파일 I / O가 있습니다.
파일 I / O에 모나드를 사용하여 변경 상태 특성을 모나드를 사용하는 코드로만 격리 할 수 있습니다. Monad 내부의 코드는 Monad 외부 세계의 변화하는 상태를 효과적으로 무시할 수 있습니다. 따라서 프로그램의 전반적인 효과를 추론하기가 훨씬 쉽습니다.
아래 " {| 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}
연료 소모량은 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
그러나 그들은 실제로 무엇에 대해 이야기합니까? 글쎄, 그것은 모나드에 달려 있습니다. 초록으로 만 말하는 것은 사용이 제한적입니다. 이해를 구체화하려면 특정 모나드에 대한 경험이 있어야합니다.
예를 들어, 데이터 유형 Maybe
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}
따라서 Maybe 모나드는 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최종 출력에 동일한 유형의 컨텍스트가 있음을 알지 않으면 추출하지 않습니다.
There are other monads which are used to represent different contexts. Here's some characterizations of a few more. The IO monad doesn't actually have an a, but it knows a guy and will get that a for you. The State st monad has a secret stash of st that it will pass to f under the table, even though f just came asking for an a. The Reader r monad is similar to State st, although it only lets f look at r.
이 모든 것의 요점은 자체적으로 Monad로 선언 된 모든 유형의 데이터가 모나드에서 값을 추출하는 것과 관련된 일종의 컨텍스트를 선언한다는 것입니다. 이 모든 것의 큰 이득? 글쎄, 그것은 어떤 종류의 맥락으로 계산을하기에 충분히 쉽다. 그러나 여러 컨텍스트가 포함 된 계산을 함께 묶으면 지저분해질 수 있습니다. 모나드 작업은 프로그래머가 할 필요가 없도록 컨텍스트의 상호 작용을 해결합니다.
은 참고의 사용은 >>=멀리에서 자율성의 일부를 복용하여 혼란을 완화 f. 즉, Nothing예를 들어 위의 경우 f에는 더 이상의 경우 수행 할 작업을 결정할 수 없습니다 Nothing. 그것은에서 인코딩 된 >>=. 이것은 트레이드 오프입니다. 것이 필요하다고하면 f결정의 경우에 무엇을 할 Nothing다음 f에서 함수 있었어야 Maybe a에 Maybe b. 이 경우 Maybe모나드가되는 것은 관련이 없습니다.
그러나 때로는 데이터 유형이 생성자를 내 보내지 않으며 (IO를보고) 광고 된 값으로 작업하려는 경우 선택의 여지가 거의 없지만 모나 딕 인터페이스로 작업 할 수 있습니다.
참고 URL : https://stackoverflow.com/questions/2366/can-anyone-explain-monads
'Program Club' 카테고리의 다른 글
| Python을 사용한 Quicksort (0) | 2020.10.10 |
|---|---|
| .NET Reflector에 대한 "무료"대안이 있습니까? (0) | 2020.10.10 |
| 고정 헤더에 맞게 조정하기 위해 html 앵커 오프셋 (0) | 2020.10.10 |
| MSBuild.exe를 사용하여 cmd 행으로 ASP.NET MVC 4 프로젝트를 "게시" (0) | 2020.10.09 |
| 키로 Go 맵 값 정렬 (0) | 2020.10.09 |
