11

だから私は何か斬新なことをしようとしていますが (私はそう思います)、Haskell 型レベルのプログラミングの経験が十分ではなく、それを自分で解決することができません。

実行するいくつかの効果を記述した無料のモナドを持っています (それがあなたのやり方なら AST です)。期待される効果の説明に対してそれを解釈したいと思います。

これまでの私のコードは次のとおりです::

{-# LANGUAGE DeriveFunctor, FlexibleInstances, GADTs, FlexibleContexts #-}
import Control.Monad.Free -- from package 'free'

data DSL next
    = Prompt String (String -> next)
    | Display String next
    deriving (Show, Functor)

prompt p = liftF (Prompt p id)
display o = liftF (Display o ())

-- |Just to make sure my stuff works interactively
runIO :: (Free DSL a) -> IO a
runIO (Free (Prompt p cont)) = do
    putStr p
    line <- getLine
    runIO (cont line)
runIO (Free (Display o cont)) = do putStrLn o; runIO cont
runIO (Pure x) = return x

それが「コア」コードです。プログラムの例を次に示します。

greet :: (Free DSL ())
greet = do
    name <- prompt "Enter your name: "
    let greeting = "Why hello there, " ++ name ++ "."
    display greeting
    friendName <- prompt "And what is your friend's name? "
    display ("It's good to meet you too, " ++ friendName ++ ".")

このプログラムをテストするために、次runTest :: Free DSL a -> _ -> Maybe aのようにプログラムと「期待される効果」の仕様を漠然と取るfunction を使用したいと思います。

expect = (
    (Prompt' "Enter your name:", "radix"),
    (Display' "Why hello there, radix.", ()),
    (Prompt' "And what is your friend's name?", "Bob"),
    (Display' "It's good to meet you too, Bob.", ()))

そして、プログラムが実行する各効果をリスト内の次の項目と照合することにより、プログラムを解釈しexpectます。次に、関連付けられた値 (各ペアの 2 番目の項目) が、その効果の結果としてプログラムに返されます。すべての効果が一致する場合、プログラムの最終結果は として返されますJust。何かが一致しない場合は、Nothing返される必要があります (後で、有益なエラー メッセージを返すようにこれを拡張します)。

もちろん、このタプルは役に立ちません。その型は非常に巨大なものであり、ジェネリック関数をexpect書き込めないからです。runTest私が抱えている主な問題は、どのようなプログラムに対してもどのようなシーケンスでも機能する関数を記述できるように、この予想される意図のシーケンスをどのように表現すべきかということですFree DSL a

  1. Haskell のさまざまな高度な型レベルの機能については漠然と認識していますが、どの機能を使用する必要があるかについてはまだ経験がありません。
  2. シーケンスに HList などを使用する必要がありexpectedますか?

調べるためのヒントは大歓迎です。

4

1 に答える 1

11

プログラムのテストは、何らかの結果を生成Free f aするプログラムの単なるインタープリターです。Free f a -> rr

あなたが探しているのは、プログラムの結果が期待どおりであることを主張するプログラムのインタープリターを構築する簡単な方法です。インタープリターの各ステップは、プログラムから命令をアンラップするか、Free f何らかのエラーを説明します。彼らはタイプを持っています

Free DSL a -> Either String (Free DSL a)
|                    |       ^ the remaining program after this step
|                    ^ a descriptive error
^ the remaining program before this step

のコンストラクターごとにテストを行いますDSLprompt'は特定の値を持つ を期待Promptし、応答値を関数に提供して次の処理を見つけます。

prompt' :: String -> String -> Free DSL a -> Either String (Free DSL a)
prompt' expected response f =
    case f of
        Free (Prompt p cont) | p == expected -> return (cont response)
        otherwise                            -> Left $ "Expected (Prompt " ++ show expected ++ " ...) but got " ++ abbreviate f

abbreviate :: Free DSL a -> String
abbreviate (Free (Prompt  p _)) = "(Free (Prompt "  ++ show p ++ " ...))"
abbreviate (Free (Display p _)) = "(Free (Display " ++ show p ++ " ...))"
abbreviate (Pure _)             = "(Pure ...)"

display'Display特定の値を持つ を期待します。

display' :: String -> Free DSL a -> Either String (Free DSL a)
display' expected f =
    case f of
        Free (Display p next) | p == expected -> return next
        otherwise                             -> Left $ "Expected (Display " ++ show expected ++ " ...) but got " ++ abbreviate f

pure'Pure特定の値を持つ を期待します

pure' :: (Eq a, Show a) => a -> Free DSL a -> Either String ()
pure' expected f = 
    case f of
        Pure a | a == expected -> return ()
        otherwise              -> Left $ "Expected " ++ abbreviate' (Pure expected) ++ " but got " ++ abbreviate' f

abbreviate' :: Show a => Free DSL a -> String
abbreviate' (Pure a) = "(Pure " ++ showsPrec 10 a ")"
abbreviate' f        = abbreviate f

prompt'とを使用display'すると、 のスタイルでインタープリターを簡単に構築できますexpect

expect :: Free DSL a -> Either String (Free DSL a)
expect f = return f >>=
           prompt' "Enter your name:" "radix" >>=
           display' "Why hello there, radix." >>=
           prompt' "And what is your friend's name?" "Bob" >>=
           display' "It's good to meet you too, Bob."

このテストの実行

main = either putStrLn (putStrLn . const "Passed") $ expect greet

失敗に終わる

Expected (Prompt "Enter your name:" ...) but got (Free (Prompt "Enter your name: " ...))

プロンプトの最後にスペースがあることを期待するようにテストを変更したら、

expect :: Free DSL a -> Either String (Free DSL a)
expect f = return f >>=
           prompt' "Enter your name: " "radix" >>=
           display' "Why hello there, radix." >>=
           prompt' "And what is your friend's name? " "Bob" >>=
           display' "It's good to meet you too, Bob."

実行すると、

Passed
于 2015-09-20T17:48:58.267 に答える