問題は、可能な型が存在しないことevalVar
です:
evalVar :: String -> [PPair] -> Expr ?
?
であるとは言えませんa
。なぜなら、戻り値が の任意の値に対して機能すると主張しているためですa
。ただし、できることは、「Expr
未知の型を持つ an」を独自のデータ型にラップすることです。
data SomeExpr where
SomeExpr :: Expr a -> SomeExpr
または、同等に、RankNTypes
ではなくGADTs
:
data SomeExpr = forall a. SomeExpr (Expr a)
これは、存在量化と呼ばれます。PPair
次に、次を使用して書き換えることができますSomeExpr
。
data PPair = PPair String SomeExpr
そしてevalVar
うまくいきます:
evalVar k (PPair kk v : xs)
| k == kk = v
| otherwise = evalVar k xs
(もちろん、[(String,SomeExpr)]
代わりに a と標準lookup
関数を使用することもできます。)
ただし、一般的に、このように式を完全に Haskell レベルで型付けしたままにしようとするのは、おそらく無駄なことです。Agdaのような依存型の言語では問題はありませんが、おそらく Haskell ではすぐに実行できない何かに遭遇したり、必要なコンパイル時の安全性が得られないところまで物事を弱体化させたりすることになるでしょう。失われます。
もちろん、決してうまくいかないというわけではありません。型付き言語は、GADT の動機となった例の 1 つです。しかし、それは思い通りにはいかないかもしれませんし、ポリモーフィズムのような自明ではない型システム機能が言語にある場合は、おそらく問題に遭遇するでしょう。
本当に入力を維持したい場合は、文字列よりも豊富な構造を使用して変数に名前を付けます。次のように、Var a
型を明示的に運ぶ型を持っています。
data PPair where
PPair :: Var a -> Expr a -> PPair
evalVar :: Var a -> [PPair] -> Maybe (Expr a)
これと同様のことを実現する良い方法は、vaultパッケージを使用することです。Key
とからST
を構築し、異種コンテナとしてIO
使用できます。Vault
これは基本的Map
に、キーが対応する値の型を保持する のようなものです。具体的には、Var a
asを定義して、代わりにKey (Expr a)
a を使用することをお勧めします。(完全な開示: 私は Vault パッケージに取り組んできました。)Vault
[PPair]
もちろん、変数名をKey
値にマップする必要がありますが、解析直後にすべての を作成しKey
、文字列の代わりにそれらを持ち歩くことができます。(ただし、この戦略で a から対応する変数名に移動するのは少し手間Var
がかかります。存在要素のリストを使用して実行できますが、解決策が長すぎてこの回答に入れることができません。)
(ちなみに、通常の型のように、GADT を使用してデータ コンストラクターに複数の引数を指定できますdata PPair where PPair :: String -> Expr a -> PPair
。)