IO 모나드가 기술적으로 올바르지 않습니까?


12

하스켈 위키에는 다음과 같은 IO 모나드의 조건부 사용 예제가 있습니다 (여기 참조) .

when :: Bool -> IO () -> IO ()
when condition action world =
    if condition
      then action world
      else ((), world)

이 예에서의 정의 참고 IO a촬영을 할 수있는 RealWorld -> (a, RealWorld)모든 것을 더 이해할 수 있도록.

이 스 니펫은 조건 적으로 IO 모나드에서 작업을 실행합니다. 이제 가정 condition이며 False, 작업이 action실행해서는 안됩니다. 게으른 시맨틱을 사용하면 실제로 그럴 것입니다. 그러나 Haskell은 기술적으로 엄격하지 않다고 언급 합니다 . 이것은 예를 들어 컴파일러가 action world다른 스레드에서 선제 적으로 실행될 수 있고 나중에 필요하지 않다는 것을 발견했을 때 그 계산을 버릴 수 있음을 의미합니다. 그러나 그 시점까지 부작용이 이미 발생했을 것입니다.

이제 전체 프로그램이 끝났을 때 부작용이 전파되는 방식으로 IO 모나드를 구현할 수 있으며, 어떤 부작용을 실행해야하는지 정확히 알고 있습니다. 그러나 중간 부작용을 분명히 가지고있는 Haskell에서 무한 프로그램을 작성할 수 있기 때문에 이것은 사실이 아닙니다.

이것은 IO 모나드가 기술적으로 잘못되었음을 의미합니까, 아니면이를 방지하는 다른 방법이 있습니까?


컴퓨터 과학에 오신 것을 환영합니다 ! 귀하의 질문은 주제와 관련이 없습니다. 우리는 프로그래밍 질문이 아닌 컴퓨터 과학 질문을 다룹니다 ( FAQ 참조 ). 귀하의 질문이 스택 오버플 로에 관한 주제 일 수 있습니다 .
dkaeae

2
제 생각에 이것은 컴퓨터 과학 문제입니다. 실제 프로그래밍 문제가 아니라 Haskell의 이론적 의미론을 다루기 때문입니다.
Lasse

4
프로그래밍 언어 이론에 익숙하지는 않지만이 질문에 대한 주제는 여기에 있습니다. 여기서 '잘못된'의미가 무엇인지 명확하게 설명하면 도움이 될 수 있습니다. IO 모나드는 어떤 속성이 없어야한다고 생각합니까?
이산 도마뱀

1
이 프로그램은 제대로 입력되지 않았습니다. 실제로 무엇을 쓰려고했는지 잘 모르겠습니다. 의 정의 when는 입력 가능하지만 선언 한 유형이 없으므로이 특정 코드가 흥미로운 이유를 알 수 없습니다.
Gilles 'SO- 악마 그만해

2
이 프로그램은 바로 위에 링크 된 Haskell-wiki 페이지에서 그대로 사용됩니다. 실제로 입력하지 않습니다. IO 내부를 더 읽기 쉽게하기 위해로 IO a정의 된 가정하에 작성 되었기 때문입니다 RealWorld -> (a, RealWorld).
Lasse

답변:


12

이것은 IO모나드 의 제안 된 "통역"입니다 . 이 "통역"을 진지하게 받아들이려면 "RealWorld"를 진지하게 고려해야합니다. action world추론 적으로 평가 되는지 action, 부작용이 없는지, 영향이있을 경우, 그 영향이 발생한 우주의 새로운 상태 (예 : 네트워크 패킷이 전송 된)를 반환함으로써 그 영향을 처리 하는지 여부 는 관련 이 없습니다. 그러나 함수의 결과 ((),world)는 우주의 새로운 상태입니다 world. 우리는 측면에서 추측 적으로 평가했을 수있는 새로운 우주를 사용하지 않습니다. 우주의 상태는 world입니다.

당신은 아마 그것을 심각하게 받아들이는 데 어려움을 겪을 것입니다. 이것이 가장 피상적으로 역설적이고 무의미한 방법은 여러 가지가 있습니다. 동시성은 특히이 관점에서 명백하지 않거나 미쳤습니다.

"잠깐만 요."라고 말합니다. " RealWorld는 단지 '토큰'입니다. 실제로 전체 우주의 상태가 아닙니다 ." 자,이 "통역"은 아무 것도 설명하지 않습니다. 그럼에도 불구하고, 구현 세부 사항 으로서 , 이것이 GHC 모델 IO입니다. 1 그러나 이것은 실제로 부작용이있는 마법의 "기능"을 가지고 있으며이 모델은 그 의미에 대한 지침을 제공하지 않음을 의미합니다. 그리고 이러한 기능에는 실제로 부작용이 있기 때문에 제기하는 관심사는 완전히 핵심입니다. GHC 확인을 위해 노력해야 RealWorld하며 이러한 특수 기능은 프로그램의 의도 된 동작을 변경하는 방식으로 최적화되지 않습니다.

개인적으로 (아마도 지금까지 명백한 것처럼),이 "세계를 지나친"모델은 IO교육적 도구로서 쓸모없고 혼란 스럽다고 생각합니다. (구현에 도움이 되든 모르겠지만, GHC의 경우 역사적인 유물에 가깝다고 생각합니다.)

한 가지 대안은 IO응답 핸들러가있는 설명 요청으로 보는 것입니다. 이를 수행하는 몇 가지 방법이 있습니다. 아마도 가장 접근하기 쉬운 것은 무료 모나드 구성을 사용하는 것입니다. 특히 다음을 사용할 수 있습니다.

data IO a = Return a | Request OSRequest (OSResponse -> IO a)

이를보다 정교하게하고 더 나은 속성을 가지려면 여러 가지 방법이 있지만 이것은 이미 개선 된 것입니다. 이해하기 위해 현실의 본질에 대한 깊은 철학적 가정이 필요하지 않습니다. 모든 상태는 값을 반환하는 것만 IO큼 사소한 프로그램 Return이거나 응답 처리기가있는 운영 체제에 대한 요청입니다. OSRequest다음과 같이 될 수 있습니다.

data OSRequest = OpenFile FilePath | PutStr String | ...

마찬가지로 OSResponse다음과 같은 것일 수 있습니다.

data OSResponse = Errno Int | OpenSucceeded Handle | ...

(수행 할 수있는 개선 사항 중 하나는 요청 OpenSucceeded에서 얻을 수없는 것을 알 수 있도록보다 안전한 유형을 만드는 것 PutStr입니다.)이 모델 IO은 일부 시스템에서 해석되는 요청을 설명하는 모델입니다 ( "실제" IO모나드의 경우 Haskell 런타임 자체), 그리고 아마도 해당 시스템이 응답을 제공 한 핸들러를 호출합니다. 물론 이것은 요청이 어떻게 PutStr "hello world"처리되어야 하는지를 알려주지 않지만 척하지도 않습니다. 이것이 다른 시스템에 위임되고 있음을 명시합니다. 이 모델도 꽤 정확합니다. 최신 OS의 모든 사용자 프로그램은 무엇이든하기 위해 OS에 요청해야합니다.

이 모델은 올바른 직관을 제공합니다. 예를 들어, 많은 초보자는 <-연산자와 같은 것을 "unwrapping" IO으로 보거나 (안타깝게도 강화 된)보기를 IO String"포함하는"컨테이너 String(및 컨테이너 를 <-가져 오는 컨테이너)라고합니다 . 이 요청-응답 관점은 이러한 관점을 분명히 잘못합니다. 안에 파일 핸들이 없습니다 OpenFile "foo" (\r -> ...). 이것을 강조하는 일반적인 비유는 케이크 레시피 안에 케이크가 없다는 것입니다.

이 모델은 동시성에서도 쉽게 작동합니다. 우리는 OSRequestlike에 대한 생성자를 쉽게 가질 수 Fork :: (OSResponse -> IO ()) -> OSRequest있으며 런타임은이 추가 핸들러에 의해 생성 된 요청을 일반 핸들러와 인터리브 할 수 있지만 좋아합니다. 약간의 영리함을 통해이 (또는 관련 기술)을 사용하여 "우리는 OS에 요청하고 상황이 발생한다"고 말하는 대신 동시성과 같은 것을 실제로 직접 모델링 할 수 있습니다. 이것이 IOSpec라이브러리가 작동하는 방식입니다.

1 Hugs는 연속 형 구현을 사용 IO했는데, 명시적인 데이터 형식이 아닌 불투명 한 함수를 사용하지만 필자가 설명한 것과 거의 유사합니다. HBC는 또한 이전 요청-응답 스트림 기반 IO에 계층화 된 연속 기반 구현을 사용했습니다. NHC (따라서 YHC)를 사용 썽크, 즉이 약 IO a = () -> a는하지만 ()불렸다 World하지만 국가 통과하고 있지 않습니다. JHC와 UHC는 기본적으로 GHC와 동일한 접근 방식을 사용했습니다.


깨달은 답변에 감사드립니다. 정말 도움이되었습니다. IO를 구현하는 데 시간이 걸렸지 만 더 직관적이라는 데 동의합니다. 이 구현이 RealWorld 구현과 같은 부작용 순서와 관련된 잠재적 문제로 고통받지 않는다고 주장하고 있습니까? 나는 즉시 어떤 문제도 볼 수 없지만 그것이 존재하지 않는다는 것도 분명하지 않습니다.
Lasse

한 의견 : OpenFile "foo" (\r -> ...)실제로 있어야 Request (OpenFile "foo") (\r -> ...)합니까?
Lasse

@Lasse Yep,와 함께 있었을 것입니다 Request. 첫 번째 질문에 답하기 위해, 이것은 IO평가 값 (모듈 바닥)에 영향을받지 않습니다. 이것은 비활성 값이기 때문입니다. 모든 부작용 (있는 경우)은이 값을 해석하는 것입니다. 이 when예 에서는 action평가 된 경우 중요 Request (PutStr "foo") (...)하지 않습니다. 어쨌든 이러한 요청을 해석하는 것에 제공하지 않는 값일뿐 입니다. 소스 코드와 같습니다. 간절히 또는 게으르게 줄이더라도 상관 없습니다. 통역사에게 줄 때까지 아무 일도 일어나지 않습니다.
Derek Elkins가 SE를

아 그래. 이것은 정말 영리한 정의입니다. 처음에는 모든 프로그램이 실행을 마치면 모든 부작용이 반드시 발생해야한다고 생각했습니다. 해석하기 전에 데이터 구조를 작성해야하기 때문입니다. 그러나 요청에 연속이 포함되어 있기 때문에 Request부작용을보기 위해서는 맨 처음의 데이터 만 작성하면됩니다 . 연속성을 평가할 때 후속 부작용이 발생할 수 있습니다. 영리한!
Lasse
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.