모나드를 보는 다른 방법


29

하스켈을 배우는 동안 모나드가 무엇인지, 하스켈에서 모나드가 중요한 이유를 설명하려는 많은 튜토리얼에 직면했습니다. 그들 각각은 비유를 사용했기 때문에 의미를 이해하기가 더 쉬울 것입니다. 하루가 끝나면 모나드가 무엇인지에 대한 3 가지 견해로 끝납니다.

보기 1 : 모나드를 레이블로

때로는 모나드가 특정 유형의 레이블이라고 생각합니다. 예를 들어 다음과 같은 유형의 함수가 있습니다.

myfunction :: IO Int

myfunction은 수행 될 때마다 Int 값을 생성하는 함수입니다. 결과 유형은 Int가 아니라 IO Int입니다. 따라서 IO는 Int 값의 레이블로, Int 값은 IO 작업이 수행 된 프로세스의 결과임을 사용자에게 알립니다.

결과적으로이 Int 값은 IO가있는 프로세스에서 나온 값으로 표시되었으므로이 값은 "더티"입니다. 당신의 프로세스는 더 이상 순수하지 않습니다.

보기 2 : 모나드는 불쾌한 일이 발생할 수있는 개인 공간으로 간주됩니다.

모든 프로세스가 순수하고 엄격한 시스템에서는 때로는 부작용이 필요합니다. 따라서 모나드는 불쾌한 부작용을 일으킬 수있는 작은 공간입니다. 이 공간에서 당신은 순수한 세상을 탈출하고, 불순종하고, 당신의 과정을 만들고, 가치를 가지고 돌아올 수 있습니다.

보기 3 : 범주 이론에서와 같은 모나드

이것은 내가 완전히 이해하지 못하는 견해입니다. 모나드는 같은 범주 또는 하위 범주에 대한 기능입니다. 예를 들어 Int 값과 하위 범주 IO Int가 있으며 이는 IO 프로세스 후에 생성 된 Int 값입니다.

이 견해가 맞습니까? 어느 것이 더 정확합니까?


5
# 2는 모나드가 일반적이 아닙니다. 실제로, 그것은 IO로 거의 제한되어 있으며 유용한 견해 는 아닙니다 (cf. Monad가 아닌 것 ). 또한, "엄격한"은 일반적으로 Haskell 소유 하지 않는 속성 (즉, 엄격한 평가)을 지칭 하기 위해 사용 됩니다. 그건 그렇고, 모나드는 그것을 바꾸지 않습니다 (다시 말하면 모나드가 아닌 것을보십시오).

3
기술적으로 세 번째 것만 맞습니다. Monad는 endofunctor입니다. 특별한 작업에 대한 정의는 승격 및 바인딩입니다. 모나드는 수없이 많습니다-리스트 모나드는 모나드 뒤에 직관을 얻는 완벽한 예입니다. readS 시설이 더 좋습니다. 놀랍게도 모나드는 순수한 기능 언어로 암시 적으로 상태를 스레드하는 도구로 사용할 수 있습니다. 이것은 모나드의 정의 속성이 아닙니다. 우연의 일치로, 상태 스레딩은 그 용어로 구현 될 수 있습니다. IO에도 동일하게 적용됩니다.
permeakra

Common Lisp에는 언어의 일부로 자체 컴파일러가 있습니다. 하스켈에는 모나드가 있습니다.
Will Ness

답변:


33

보기 # 1 및 # 2는 일반적으로 올바르지 않습니다.

  1. 모든 종류의 데이터 유형은 * -> *레이블로 작동 할 수 있으며 모나드는 그 이상입니다.
  2. ( IO모나드를 제외하고) 모나드 내의 계산은 부정확하지 않습니다. 그것들은 단순히 부작용이있는 것으로 인식하는 계산을 나타내지 만 순수합니다.

이 두 가지 오해는 IO모나드에 중점을 둔 것인데, 실제로는 약간 특별합니다.

가능한 경우 카테고리 이론에 들어 가지 않고 # 3에 대해 조금 자세히 설명하려고합니다.


표준 계산

함수형 프로그래밍 언어의 모든 계산은 소스 유형과 대상 유형이있는 함수로 볼 수 있습니다 f :: a -> b. 함수에 인수가 둘 이상 있으면 카레 를 사용하여 인수를 하나의 인수 함수로 변환 할 수 있습니다 ( Haskell Wiki 참조 ). 우리가 값이 있다면 x :: a(0 인수 함수를), 우리는의 인수를 취하는 함수로 변환 할 수 있습니다 단위 유형을 : (\_ -> x) :: () -> a.

.연산자를 사용하여 이러한 기능을 구성하여 더 복잡한 프로그램을 더 간단한 프로그램으로 만들 수 있습니다 . 예를 들어, 가지고 f :: a -> b있고 g :: b -> c우리가 얻는 다면 g . f :: a -> c. 이것은 변환 된 값에도 적용됩니다. 우리가 x :: a그것을 가지고 표현으로 변환하면, 우리는 얻습니다 f . ((\_ -> x) :: () -> a) :: () -> b.

이 표현에는 다음과 같은 매우 중요한 속성이 있습니다.

  • 우리는 매우 특별한 기능, 즉 각 유형에 대한 항등 함수 id :: a -> a를 가지고 a있습니다. 그것은 인 식별 요소 에 관하여 .: f모두에 동일 f . id하고 id . f.
  • 함수 구성 연산자 .연관되어 있습니다.

모나 딕 계산

특정 계산 범주를 선택하여 사용하고 그 결과에 단일 반환 값 이상의 것이 포함되어 있다고 가정합니다. 우리는 "더 많은 것"이 무엇을 의미하는지 지정하고 싶지 않으며 가능한 한 일반적인 것을 유지하고 싶습니다. 유형 - 가장 일반적인 방법은 표현하기 위해 "뭔가 더"유형 기능으로 대표되는 m종류의 * -> *(즉, 또 다른 한 형식 변환). 따라서 우리가 작업하고자하는 각 계산 범주에 대해 몇 가지 type 함수가 m :: * -> *있습니다. (하스켈에서, m이고 [], IO, Maybe, 등) 및 분류 의지 유형의 모든 기능을 포함 a -> m b.

이제 기본 사례에서와 같은 방식으로 이러한 범주의 함수를 사용하려고합니다. 우리는 이러한 기능을 구성 할 수 있기를 원하며, 구성이 연관되기를 원하며 정체성을 원합니다. 우리는 필요합니다 :

  • (현실을 부르 자 연산자를하려면 <=<그 기능을 구성하는) f :: a -> m bg :: b -> m c같은 무언가로 g <=< f :: a -> m c. 그리고 그것은 연관 적이어야합니다.
  • 각 유형에 대해 일부 신원 기능을 갖기 위해 그것을 호출합시다 return. 우리는 또한 원하는 f <=< return과 동일 f과 동일 return <=< f.

모든이 m :: * -> *있는 우리는 기능을 가지고 return<=<불리며 모나드 . 기본 경우와 같이 더 간단한 계산을 통해 복잡한 계산을 만들 수 있지만 이제는 반환 값 유형이로 변환됩니다 m.

(사실, 난 약간 용어 학대 범주를 여기에. 우리는 우리가이 법을 따르는 알고 후에 만 카테고리 우리의 건설 호출 할 수있는 범주 이론의 의미에서.)

하스켈의 모나드

하스켈 (그리고 다른 기능 언어)에서 우리는 대부분없는 유형의 기능, 가치와 함께 작동 () -> a. 따라서 <=<각 모나드에 대해 정의하는 대신 함수를 정의합니다 (>>=) :: m a -> (a -> m b) -> m b. 그러한 대안 적 정의는 동등하며, 우리는를 >>=사용하여 표현 <=<하거나 그 반대로 표현할 수 있습니다 (운동으로 시도하거나 출처를보십시오 ). 원칙은 현재 명확하지 않지만 동일하게 유지됩니다. 결과는 항상 유형 m a이며 유형의 함수를 구성합니다 a -> m b.

각 우리가 만드는 모나드를 들어, 우리는 것을 확인하는 것을 잊지합니다 return<=<연관성 및 왼쪽 / 오른쪽 정체성 : 우리가 필요한 특성을 가지고있다. 표현 사용 return하고 >>=그들은이라고 모나드 법 .

예-목록

우리가하기로 선택 m하면 [], 우리는 유형의 함수 범주를 얻는다 a -> [b]. 이러한 함수는 비 결정적 계산을 나타내며 그 결과는 하나 이상의 값일 수 있지만 값은 없습니다. 이것은 소위 list monad를 발생 시킵니다. 조성물 f :: a -> [b]g :: b -> [c]다음 방법 : g <=< f :: a -> [c]수단은 모든 가능한 유형의 결과를 계산은 [b]적용 g그들 각각에, 그리고 하나의리스트에있는 모든 결과를 수집한다. 하스켈로 표현

return :: a -> [a]
return x = [x]
(<=<) :: (b -> [c]) -> (a -> [b]) -> (a -> [c])
g (<=<) f  = concat . map g . f

또는 사용 >>=

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

이 예제에서 리턴 유형은 [a]type의 값을 포함하지 않았을 수 있습니다 a. 실제로, 반환 유형에 이러한 값이 있어야한다는 모나드에 대한 요구 사항은 없습니다. 일부 모나드는 항상 (와 같 IO거나 State) 있지만 일부는 []나 같지 않습니다 Maybe.

IO 모나드

앞서 언급했듯이 IO모나드는 다소 특별합니다. 유형 값은 프로그램 환경과 상호 작용하여 구성된 유형 값을IO a 의미 합니다. 따라서 다른 모나드와 달리 순수한 구성을 사용하여 유형의 값을 설명 할 수는 없습니다 . 다음 은 환경과 상호 작용하는 계산을 구별하는 태그 또는 레이블입니다. 이것은 뷰 # 1과 # 2가 올바른 유일한 경우입니다.aIO aIO

위해 IO모나드 :

  • 구성 f :: a -> IO bg :: b -> IO c의미 : f환경과 상호 작용하는 계산 g 값을 사용하여 계산하고 환경과 상호 작용하는 결과를 계산합니다.
  • returnIO"태그"를 값에 추가하기 만하면됩니다 (환경을 그대로 유지하여 결과를 "계산"합니다).
  • 모나드 법칙 (연관성, 동일성)은 컴파일러에 의해 보장됩니다.

몇 가지 참고 사항 :

  1. 모나드 계산은 항상 결과 유형을 가지므로 모나드 m a에서 "탈출"하는 방법은 없습니다 IO. 의미는 다음과 같습니다. 일단 계산이 환경과 상호 작용하면 그렇지 않은 계산을 구성 할 수 없습니다.
  2. 함수형 프로그래머가 순수한 방법으로 무언가를 만드는 방법을 모른다면, ( 마지막 수단으로) IO모나드 내에서 상태 저장 계산을 통해 작업을 프로그래밍 할 수 있습니다 . 이것이 IO종종 프로그래머의 sin bin 이라고 불리는 이유 입니다 .
  3. 불완전한 세계 (기능 프로그래밍의 의미에서)에서 값을 읽으면 사용자의 입력 소비와 같이 환경도 변경 될 수 있습니다. 따라서 같은 함수 getChar의 결과 유형은이어야합니다 IO something.

3
좋은 대답입니다. IO언어 관점에서 특별한 의미가 없다는 것을 분명히하고 싶습니다 . 그것은 특별 하지 않으며 다른 코드와 같이 작동합니다. 런타임 라이브러리 구현 만 특별합니다. 또한 특수 목적으로 탈출하는 방법이 있습니다 ( unsafePerformIO). 사람들이 종종 IO특수 언어 요소 또는 선언적 태그로 생각하기 때문에 이것이 중요하다고 생각합니다 . 그렇지 않습니다.
usr

2
@usr 좋은 지적. 나는 것을 추가 할 것 unsafePerformIO가 정말 안전하지 않은 만 전문가에 의해 사용되어야한다. 예를 들어 coerce :: a -> b두 가지 유형을 변환하고 대부분의 경우 프로그램을 중단 시키는 함수 를 만들 수 있습니다 . 참고 이 예제를 - 당신이로도 기능을 변환 할 수 있습니다 Int
페트르 Pudlák

또 다른 "특별한 마법"모나드는 ST인데, 이것은 모나드 내에서만 가능하다고 생각 될 때 읽고 쓸 수있는 메모리에 대한 참조를 선언 한 다음 호출하여 결과를 추출 할 수 있습니다runST :: (forall s. GHC.ST.ST s a) -> a
sara

5

보기 1 : 모나드를 레이블로

"따라서이 Int 값은 IO가있는 프로세스에서 나온 값으로 표시되었으므로이 값은"더티 "입니다."

"IO Int"는 일반적으로 Int 값이 아닙니다 ( "return 3"과 같은 경우도 있음). Int 값을 출력하는 절차입니다. 이 "프로 시저"를 다르게 실행하면 다른 Int 값을 얻을 수 있습니다.

모나드 m은 내장 된 (구체적인) "프로그래밍 언어"입니다.이 언어 내에서 "프로 시저"를 정의 할 수 있습니다. ma 유형의 모나드 값은이 "프로그래밍 언어"에서 a 유형의 값을 출력하는 절차입니다.

예를 들면 다음과 같습니다.

foo :: IO Int

Int 유형의 값을 출력하는 일부 프로 시저입니다.

그때:

bar :: IO (Int, Int)
bar = do
  a <- foo
  b <- foo
  return (a,b)

두 개의 Ints를 출력하는 절차입니다.

이러한 모든 "언어"는 몇 가지 작업을 지원합니다.

  • 두 개의 프로 시저 (ma 및 mb)는 "연결"될 수 있습니다. 첫 번째 프로 시저와 두 번째 프로 시저로 구성된 더 큰 프로 시저 (ma >> mb)를 작성할 수 있습니다.

  • 무엇보다 첫 번째 출력 (a)가 두 번째 출력에 영향을 줄 수 있습니까 (ma >> = \ a-> ...);

  • 프로 시저 (return x)는 일정한 값 (x)을 산출 할 수 있습니다.

다른 임베디드 프로그래밍 언어는 다음과 같이 지원하는 종류가 다릅니다.

  • 임의의 값을 산출;
  • "포킹"([] 모나드);
  • 예외 (throw / catch) (Either e monad);
  • 명시 적 연속 / callcc 지원;
  • 다른 "에이전트"에게 메시지를 전송 / 수신;
  • 변수를 작성, 설정 및 읽습니다 (이 프로그래밍 언어의 로컬 변수) (ST 모나드).

1

모나드 타입과 모나드 클래스를 혼동하지 마십시오.

모나 딕 타입 (즉, 모나드 클래스의 인스턴스 인 타입)은 특정 문제를 해결합니다 (원칙적으로 각 모나 딕 타입은 다른 것을 해결합니다) : 상태, 랜덤, 어쩌면 IO. 그것들은 모두 문맥이있는 타입입니다 ( "라벨"이라고 부르는 것이지만 모나드가되는 것은 아닙니다).

이들 모두를 위해 "선택된 체인 작업"이 필요합니다 (한 작업은 이전 결과에 따라 다름). 다음은 모나드 클래스를 플레이하는 것입니다 : 여러분의 타입 (주어진 문제 해결)이 모나드 클래스의 인스턴스가되고 체인 문제가 해결되도록하십시오.

모나드 클래스는 무엇을 해결합니까?를 참조하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.