하스켈에서 유형 안전 질문 - 답변 흐름을 만들려고합니다. QnA를 FSM과 비슷한 유향 그래프로 모델링했습니다.타입 안전 흐름 (상태 머신)
data Node s a s' = Node {
question :: Question a,
process :: s -> a -> s'
}
s
a
가 질문에 대한 응답이며 s'
출력 상태, 입력 상태이다
그래프의 각 노드는 문제를 나타낸다. 노드는 입력 상태 s
에 의존합니다. 즉, 응답을 처리하기 위해 이전에 특정 상태에 있어야합니다.
Question a
은 a
유형의 답을 생성하는 간단한 질문/답변을 나타냅니다. si
처음 s1
을 생성하는 노드를 통해 s1
다음 Node2
로 끝나는 모든 경로를 통과해야에 의존하는 경우 내 말은 형태 보증으로
, 예를 들어, 노드 Node2 :: si -> a -> s2
을 부여. s1 == si
인 경우 Node2
의 모든 선행 작업은 s1
을 생성해야합니다.
예제
문의 글이 : 온라인 쇼핑 웹 사이트에서, 우리는 사용자의 신체 크기와 좋아하는 색상을 요청해야합니다.
e1
: 사용자에게 크기를 알고 있는지 묻습니다. 그렇다면e2
으로 이동하십시오. 그렇지 않은 경우e3
e2
으로 이동하십시오 : 사용자의 크기를 묻고 으로 가서 색상을 문의하십시오.e3
: (사용자는 크기를 알지 못함) 사용자의 무게를 묻고e4
으로 가십시오.e4
: (e3
후) 사용자의 높이를 요청하고 자신의 크기를 계산하고ef.
ef
로 이동 : 사용자의 마음에 드는 색상을 요청하고Final
결과에 흐름을 마무리합니다. 내 모델에서
는 Edge
들 서로 Node
의 연결 :
data Edge s sf where
Edge :: EdgeId -> Node s a s' -> (s' -> a -> Edge s' sf) -> Edge s sf
Final :: EdgeId -> Node s a s' -> (s' -> a -> sf) -> Edge s sf
sf
여기 즉, 문의 글의 최종 결과입니다 (Bool, Size, Color)
.
각 순간의 QnA 상태는 튜플 : (s, EdgeId)
으로 나타낼 수 있습니다. 이 상태는 직렬화 가능하며 우리는이 상태를 아는 것만으로 QnA를 계속할 수 있어야합니다.
saveState :: (Show s) => (s, Edge s sf) -> String
saveState (s, Edge eid n _) = show (s, eid)
getEdge :: EdgeId -> Edge s sf
getEdge = undefined --TODO
respond :: s -> Edge s sf -> Input -> Either sf (s', Edge s' sf)
respond s (Edge ...) input = Right (s', Edge ...)
respond s (Final ...) input = Left s' -- Final state
-- state = serialized (s, EdgeId)
-- input = user's answer to the current question
main' :: String -> Input -> Either sf (s', Edge s' sf)
main' state input =
let (s, eid) = read state :: ((), EdgeId) --TODO
edge = getEdge eid
in respond s input edge
전체 코드 : 나 가장자리가 형 안전 유지하는
{-# LANGUAGE GADTs, RankNTypes, TupleSections #-}
type Input = String
type Prompt = String
type Color = String
type Size = Int
type Weight = Int
type Height = Int
data Question a = Question {
prompt :: Prompt,
answer :: Input -> a
}
-- some questions
doYouKnowYourSizeQ :: Question Bool
doYouKnowYourSizeQ = Question "Do you know your size?" read
whatIsYourSizeQ :: Question Size
whatIsYourSizeQ = Question "What is your size?" read
whatIsYourWeightQ :: Question Weight
whatIsYourWeightQ = Question "What is your weight?" read
whatIsYourHeightQ :: Question Height
whatIsYourHeightQ = Question "What is your height?" read
whatIsYourFavColorQ :: Question Color
whatIsYourFavColorQ = Question "What is your fav color?" id
-- Node and Edge
data Node s a s' = Node {
question :: Question a,
process :: s -> a -> s'
}
data Edge s sf where
Edge :: EdgeId -> Node s a s' -> (s' -> a -> Edge s' sf) -> Edge s sf
Final :: EdgeId -> Node s a s' -> (s' -> a -> sf) -> Edge s sf
data EdgeId = E1 | E2 | E3 | E4 | Ef deriving (Read, Show)
-- nodes
n1 :: Node() Bool Bool
n1 = Node doYouKnowYourSizeQ (const id)
n2 :: Node Bool Size (Bool, Size)
n2 = Node whatIsYourSizeQ (,)
n3 :: Node Bool Weight (Bool, Weight)
n3 = Node whatIsYourWeightQ (,)
n4 :: Node (Bool, Weight) Height (Bool, Size)
n4 = Node whatIsYourHeightQ (\ (b, w) h -> (b, w * h))
n5 :: Node (Bool, Size) Color (Bool, Size, Color)
n5 = Node whatIsYourFavColorQ (\ (b, i) c -> (b, i, c))
-- type-safe edges
e1 = Edge E1 n1 (const $ \ b -> if b then e2 else e3)
e2 = Edge E2 n2 (const $ const ef)
e3 = Edge E3 n3 (const $ const e4)
e4 = Edge E4 n4 (const $ const ef)
ef = Final Ef n5 const
ask :: Edge s sf -> Prompt
ask (Edge _ n _) = prompt $ question n
ask (Final _ n _) = prompt $ question n
respond :: s -> Edge s sf -> Input -> Either sf (s', Edge s' sf)
respond s (Edge _ n f) i =
let a = (answer $ question n) i
s' = process n s a
n' = f s' a
in Right undefined --TODO n'
respond s (Final _ n f) i =
let a = (answer $ question n) i
s' = process n s a
in Left undefined --TODO s'
-- User Interaction:
saveState :: (Show s) => (s, Edge s sf) -> String
saveState (s, Edge eid n _) = show (s, eid)
getEdge :: EdgeId -> Edge s sf
getEdge = undefined --TODO
-- state = serialized (s, EdgeId) (where getEdge :: EdgeId -> Edge s sf)
-- input = user's answer to the current question
main' :: String -> Input -> Either sf (s', Edge s' sf)
main' state input =
let (s, eid) = undefined -- read state --TODO
edge = getEdge eid
in respond s edge input
그것은 중요합니다.예를 들어, e2
을 e3
에 잘못 연결하면 형식 오류가 발생합니다 : e2 = Edge E2 n2 (const $ const ef)
은 괜찮습니다. e2 = Edge E2 n2 (const $ const e3)
은 오류 여야합니다.
나는 --TOOD
내 질문을 나타낼 :
가장자리 유형 안전,
Edge s sf
후 어떻게getEdge :: EdgeId -> Edge s sf
함수를 만들 수있는 입력 형 변수 (s
)가 있어야 유지에 대한 나의 기준을 감안할 때? 는
내가 현재 상태
s
와 현재의 가장자리Edge s sf
주어진respond
함수를 만들 수있는 방법, 최종 상태 (현재의 가장자리Final
경우) 또는 다음 주와 다음 가장자리(s', Edge s' sf)
중 하나를 반환합니다 ?
Node s a s'
및 Edge s sf
의 내 디자인은 단순히 잘못 될 수 있습니다. 나는 그것에 충실 할 필요가 없다.
데이터 유형에 직렬화 할 수없는 임의의 함수 유형이 포함되어 있으므로 여기에서 원하는 인터페이스를 얻을 수는 없습니다. 'saveState'는 그래프 자체를 직렬화하지 않아도 쓸모가 없습니다. 첫 번째 단계는 실제로 모델링하고자하는 것을 식별하는 것입니다. '가장자리'함수에서 사용하는 유일한 함수는 상수 함수와'if'이며, 일반적인 유스 케이스 인 경우이를 대표하면 아마도 모델링이 될 것입니다 꽤 쉬운. 추가 유형 안전성 제약 조건을 가진 '그래프'(노드 및 모서리)를 실제로 모델링하려면이 작업이 더욱 어렵습니다. – user2407038
나는 일반적인 해결책을 찾고있다. 나는 현재 상태''''와''최신 대답''에 따라 다음 서브 그래프를 선택하는 더 복잡한'가장자리'를 상상할 수 있습니다. 실재 인'Edge'는 데이터베이스 연결 등을 사용하고 하위 그래프를'IO (Edge s 'sf)'로 리턴 할 수도 있습니다. – homam
그래프에서 '이동'할 노드를 '선택'하지 않습니다. 각 노드는 간단히 노드 세트에 연결됩니다. 노드의 * 값 의미 *는 전환 할 값과 전환 자체가 그래프 구조의 일부가 아닙니다. 대신 노드와 가장자리에 해석 할 항목으로 레이블이 지정된 그래프가 있습니다 (귀하의 도메인에서) '상태'와 '전환'이되도록하십시오. 즉당신의 * edge *는'e1 = Edge n1 [n2, n3]'입니다. 그러나 당신의 * 가장자리 라벨은 함수'\ b -> if b ...'입니다. *이 그래프의 * 모양 *은 레이블은 할 수 없습니다. – user2407038