2012-02-01 4 views
6

4 비트 마이크로 프로세서를 에뮬레이트하고 있습니다. 레지스터, 메모리 및 실행중인 출력 (페치 실행 사이클 카운터가있는 경우 보너스 포인트)을 추적해야합니다. 나는 모나드가 없어도 이것을 할 수 있었지만, 많은 것을 한 번에 지나치게 지나치게 지저분하게 느꼈다. 또한 함수 정의는 길고 읽기가 어렵다.haskell에서 서로 다른 수준의 상호 작용

나는 이것을 모나드와 함께 시도했다. 나는 분리 된 모든 주 구성 요소를 하나의 유형으로 취급하려고 시도했지만, 그 때문에 가치를 만들어야하는 문제가 생겼습니다.

State Program() -- Represents the state of the processor after a single iteration of the fetch execute cycle 

의미가있는 유일한 유형이었습니다. 그러나 그 시점에서 왜 귀찮게합니까? 내 복합 형에서 문자열을 당겨 I 실행 출력을 필요로한다는 사실을 제외하고, 좋은 일을 값

State Program' String 

로 처리하여 그것을 깨는 시도했다. 내가 뭘 했든간에 나는 동시에 끈과 상태 모두를 견딜 수 없었다.

이제 모나드 변압기에 맞서려고 노력하고 있습니다. 나는 모든 다른 수준의 국가들을 분리해야만하는 것처럼 보인다. 하지만 내 머리가 빨리 폭발하고있다.

StateT Registers (StateT Memory (State Output)) a = 
StateT (registers -> (StateT Memory (State Output)) (a,registers)) 

StateT Registers (StateT Memory (State Output)) a = 
StateT (registers -> (Memory -> (Output -> (((a,Registers),Memory),Output)))) 

아직 FEcycle 카운터에 넣지 않았습니다!

질문 :

  1. 오전 나는 곧 정상 궤도에?
  2. 모나드 변압기를 당겨보고있는 것처럼 "실행 출력"을 상태로 처리하는 것을 중지하고 IO 모나드로 손바닥을 그릴 수 있습니까? 그것은 멋지게 될 것입니다. 대신에 인쇄 할 수 있습니다.
  3. 얼마나 많은 레이어로 상태를 구분해야합니까? 두 개의 서로 다른 레이어를 볼 수는 있지만 서로 밀접하게 관련되어 있습니다 (메모리와 레지스터는 모두 메모리와 레지스터의 상태에 따라 다릅니다). 나는 그들을 하나의 상태로 유지하거나 그들을 분리하여 쌓아 두어야합니까? 어떤 방법으로 가장 가독성이 좋은 코드가 생성됩니까?
+1

아마도 "출력 실행 중"은 Writer 모나드를 사용하여 가장 잘 표현 될 수 있습니다 (http://monads.haskell.cz/html/writermonad.html 참조). –

답변

9

여러 상태의 모나드를 서로 겹쳐 놓는 것은 나쁜 생각입니다. 각 상태에 도달하기 위해서는 lift 묶음을 작성해야합니다.이 상태는 스택 아래의 레이어 수로 나타납니다. 왝! 사실, mtl 라이브러리는 일반적으로 드문 경우를 제외하고 스택의 각 "종류"에 대해 하나의 모나드 변환기로 사용하도록 설계되었습니다.

대신에 StateT Program IO()을 제안합니다. 상태에 대한 인터페이스는 동일하므로 간단히 liftIO을 사용하여 IO에서 출력을 수행 할 수 있습니다. 물론 값 유형은 ()이지만 그 점은 무엇이 문제입니까? 관련 가치가 없습니다. 최상위 레벨 에뮬레이터에서 돌아올 수 있습니다. 물론 에뮬레이터의 부분 인과 같이 크기가 작고 재사용 가능한 구성 요소가있을 수 있으며 관련 결과 유형을 갖게됩니다. (실제로는 get이 이러한 구성 요소 중 하나입니다.) 최상위 수준에서 의미있는 반환 값을 갖는 것이 잘못되었습니다.

국가의 각 부분에 편리하게 액세스 할 수있는 한, 원하는 것은 렌즈입니다. this Stack Overflow answer은 훌륭한 소개입니다. 그들은 당신이 당신의 상태의 독립된 부분에 간단하고 쉽게 접근하고 수정할 수있게합니다. 예를 들어 data-lens 구현을 사용하면 과 같은 것을 쉽게 쓸 수 있습니다. regA 또는 stack %= drop 2으로 스택의 처음 두 요소를 제거합니다.

물론, 그것은 본질적으로 전역 변수의 집합의 필수 돌연변이로 코드를 돌고 있지만, 실제로 장점, 즉 당신이 모방하고있는 CPU를 기반으로 정확히 패러다임이기 때문에. 그리고 data-lens-template 패키지 , 당신은 하나의 라인에 기록 정의에서 이러한 렌즈를 파생시킬 수 있습니다.

+0

그건 대단한거야. 템플릿 haskell을 배울 수있는 시간. regA + = 1과 같은 기능 정의를 뒷받침하는 기능을 쉽게 찾을 수 있습니까? 그것이 훌륭하고 읽기 쉽고 (매우 긴급한) 의도를 가장 명확하게 표현하기 때문에 기능적 코드처럼 보이지 않습니다. – TheIronKnuckle

+1

data-lens-template을 사용하기 위해서는 Template Haskell을 배울 필요가 없습니다. 그냥 파일의 상단에'{- # LANGUAGE TemplateHaskell # -}'을 붙이고'Program'의 정의 아래에있는'makeLenses [ " ''Program]'을 붙이면됩니다. '(+ =)'의 정의에 관해서는, 확실히; 그것들은'StateT'를위한 핵심 렌즈 API의 간단한 래퍼 일뿐입니다; [출처] (http://hackage.haskell.org/packages/archive/data-lens/2.0.2/doc/html/src/Data-Lens-Strict.html)는 Hackage 문서에서 링크되어 있습니다. – ehird

+2

사소한 불안정한 - 계층화 된 상태의 모나드는 나쁜 생각입니다. 이것들은 상태를 분할 할 수 있기 때문에 일부 클라이언트가 읽기 - 쓰기 액세스가 아닌 특정 상태의 계층에 대한 읽기 액세스 만 허용하도록 제한 할 수 있습니다.이 옵션은 "보안 의식이있는"코드에 사용됩니다. 그렇지 않으면 좋은 대답. –

2

레지스터와 메모리를 나타내는 데이터 형식을 생성하는 것이 작업을 수행하는 간단한 방법 :

data Register = ... 
data Memory = ... 
data Machine = Machine [Register] Memory 

그런 다음 레지스터/메모리를 업데이트 몇 가지 기능을 가지고 있습니다. 이제 유형에 따라 상태에 대한 이런 종류의 출력을 사용
type Simulation = State Machine Output 

지금 각 작업은 형태가 될 수있다 :

여기
operation previous = do machine <- get 
         (result, newMachine) <- operate on machine 
         put newMachine 
         return result 

previous 기계의 이전 출력됩니다. 그것을 결과에도 통합 할 수 있습니다.

따라서 Machine 유형은 시스템의 상태를 나타냅니다. 당신은 이전 작업의 결과물을 그것으로 통과시키고 있습니다.

더 복잡한 방법은 state threads (Control.Monad.ST)을 사용하는 것입니다. 이것들은 외부에서 순수성을 보증하면서 함수 내에서 변경 가능한 참조와 배열을 사용할 수 있도록합니다.