GADTの方向性が間違っていると思います。その理由を理解するために、最初にの型なしバージョンExprとその評価者(質問の最初の部分)を見ていきます。
Expr作成できるタイプの値は次のとおりです。
expr1 = Con (IntValue 42)
expr2 = Con (BoolValue True)
expr3 = Con (BoolValue False)
これまでのところ、とても良いです:expr1整数定数42、expr2およびexpr3ブール定数Trueとを表しFalseます。Expr最も外側のコンストラクターとしての型のすべての値は、Con基本的に次のようになります。
Andコンストラクターをミックスに追加すると、物事が面白くなり始めます。
expr4 = And (Con (BoolValue True)) (Con (BoolValue True))
expr5 = And (Con (BoolValue True)) (And (Con (BoolValue False)) (Con (BoolValue False)))
expr6 = And (Con (BoolValue True)) (Con (IntValue 42))
expr4そしてexpr5大丈夫です。それらはそれぞれ式True && TrueとTrue && (False && False)を表します。True私たちは彼らがとに評価することを期待しますFalseが、それについてはすぐに詳しく説明します。しかし、expr6怪しげに見えます。それはTrue && 42意味をなさない表現を表しています(少なくともHaskellでは!)。
これまで見てきた式は、6番を除いて、すべて値があります。整数値は42で、expr1残りはブール値です( s 2から5の場合は、、、)。ご覧のとおり、値は整数またはブール値のいずれかであるため、タイプの値として表すことができます。TrueFalseTrueFalseexprValue
Exprを取り、そのを返すエバリュエーターを書くことができますValue。つまり、エバリュエーターは定数式の値を返す必要があり、式が論理'および'の場合、ブール値である必要がある構成式の値の論理'および'を返す必要があります-できませんand整数とブール値、または2つの整数の論理積を取ります。コードでは、これは次のように変換されます
est :: Expr -> Value -- takes an Expr and returns its Value
est (Con value) = value -- the value of a constant expression is the wrapped value
est (And e1 e2) = let value1 = est e1 -- the value of the first operand
value2 = est e2 -- the value of the second operand
in logicalAndValue value1 value2
logicalAndValue :: Value -> Value -> Value
logicalAndValue (BoolValue b1) (BoolValue b2) = BoolValue (b1 && b2)
logicalAndValue (BoolValue b1) (IntValue i2) = error "Can't take the logical 'and' of a boolean and an integer"
logicalAndValue (IntValue i1) (BoolValue b2) = error "Can't take the logical 'and' of an integer and a boolean"
logicalAndValue (IntValue i1) (IntValue i2) = error "Can't take the logical 'and' of two integers"
これは、最初ののより冗長なバージョンであり、est2つの評価された式の論理'および'を別の関数にスピンオフし、わずかに有益なエラーメッセージを表示する操作が含まれています。
さて、うまくいけば、これはあなたの最初の質問に答えます!問題は、Expr値がブール値または整数値のいずれかを持つ可能性があり、その型を「見る」ことができなくなるという事実に要約されます。したがって、Andこれが意味をなさない2つの式を一緒にすることができます。
これを解決する1つの方法は、Expr2つの新しい式タイプに分割することです。1つは整数値を持ち、もう1つはブール値を持ちます。これは次のようになります(expr上記で使用したのと同等のものも示します)。
data IntExpr = ConInt Int
expr1 :: IntExpr
expr1 = ConInt 42
data BoolExpr = ConBool Bool
| AndBool BoolExpr BoolExpr
expr2 :: BoolExpr
expr2 = ConBool True
expr3 = ConBool False
expr4 = AndBool (ConBool True) (ConBool True)
expr5 = AndBool (ConBool True) (AndBool (ConBool False) (ConBool False))
注目すべき2つの点があります。Value型を削除しましたが、同等のものを作成することができexpr6なくなりました。これは、その明らかな変換AndBool (ConBool True) (ConInt 42)がコンパイラによって拒否されるためです(これを試す価値があるかもしれません)。型エラーの:ConInt 42型IntExprであり、それをの2番目の引数として使用することはできませんAndBool。
評価者には、整数式用とブール式用の2つのバージョンも必要です。それらを書きましょう!値がIntExpr必要であり、 sに評価される必要があります。IntBoolExprBool
evalInt :: IntExpr -> Int
evalInt (ConInt n) = n
evalBool :: BoolExpr -> Bool
evalBool (ConBool b) = b
evalBool (AndBool e1 e2) = let v1 = evalBool e1 -- v1 is a Bool
v2 = evalBool e2 -- v2 as well
in v1 && v2
ご想像のとおり、これは、より多くのタイプの式(Char、lists、Doubleなど)を追加したり、2つの数値を追加したり、「if」式を作成したり、タイプが指定されていない変数を作成したりするなど、式を組み合わせる方法を追加すると、面倒になります。あらかじめ...
これがGADTの出番です!評価の可能な結果タイプ(IntExprおよびそれBoolExpr以上)ごとに個別のタイプの式を作成する代わりに、評価するタイプで式タイプ自体に「タグ付け」します。したがって、typeの値を評価した結果がであり、aが。を与えることを確認します。実際には、(上記の関数のように)実行時にタイプチェックを実行するのではなく、コンパイラにタイプチェックを実行させます。Expr IntIntExpr BoolBoollogicalAndValue
今のところ、式を作成する方法は2つだけです。定数式を作成する方法と、2つのブール値を一緒に'および'-する方法です。最初の方法は次のように機能します。があればInt、それをに変換し、をExpr IntにBool変換Expr Boolします。したがって、「makeconstantexpression」コンストラクターの型アノテーションは次のようになります。
Con :: v -> Expr v
2番目のコンストラクターはブール値を表す2つの式を取り(したがって、これらの2つの式はタイプですExpr Bool)、ブール値を持つ別の式を返します。つまり、このコンストラクターのタイプは次のとおりです。
And :: Expr Bool -> Expr Bool -> Expr Bool
ピースをまとめると、次のExprタイプが得られます。
data Expr e where
Con :: v -> Expr v
And :: Expr Bool -> Expr Bool -> Expr Bool
いくつかの値の例:
expr1 :: Expr Int
expr1 = Con 42
expr2 :: Expr Bool
expr2 = Con True
expr3 :: Expr Bool
expr3 = Con False
expr4 :: Expr Bool
expr4 = And (Con True) (Con True)
expr5 :: Expr
expr5 = And (Con True) (And (Con False) (Con False))
繰り返しになりますが、に相当するものexpr6はタイプチェッカーを通過しません。これはですがAnd (Con True) (Con 42)、でCon 42あるExpr Intため、の引数として使用することはできません。これは。Andである必要がありますExpr Bool。
これで、評価者は本当に簡単になります。(覚えExpr eておいてください、これはタイプの値を持つ式であることを意味しeます)それは。を返しますe。定数式の値は定数自体であり、論理式の値はオペランドの値の「および」であり、これらの値はBoolsであると確信しています。これは与える:
est :: Expr e -> e
est (Con v) = v
est (And e1 e2) = let b1 = est e1 -- this will be a Bool, since e1 is an Expr Bool
b2 = est e2 -- likewise
in b1 && b2
指定したGADTは、型指定Exprされていないのと直接同等であり、それでも、などの「不正な」値を作成できますAnd (Con (BoolValue True)) (Con (IntValue 42))。
'Value'型を取り除くことにより、式の型が何であるかをより正確に述べることができます。「式の型は整数またはブール値ですが、まだわかりません」と言って式を評価するときに問題が発生するのではなく、式の値の型を最初から知っていることを確認します。そして、意味をなさない方法でそれらを組み合わせないこと。
これまでに達成できたことを願っています。さまざまなレベルのすべてのタイプ、値、および式が混乱する可能性があります。Expr-そして、タイプとエバリュエーターを自分で拡張して少し実験できるようになります。
簡単に試すことができるのは、文字列または文字定数を使用して2つの整数値を加算する式を作成するか、ブール型の最初の型と同じものの2番目と3番目の3つの引数を取る'if-then-else'式を作成することです。タイプ(ただし、そのタイプは、、、Intなど)。BoolChar