これは、高次抽象構文として知られています。
最初の解決策: Haskellのラムダを使用します。データ型は次のようになります。
data Prop
= Not Prop
| And Prop Prop
| Or Prop Prop
| Impl Prop Prop
| Equiv Prop Prop
| Equals Obj Obj
| ForAll (Obj -> Prop)
| Exists (Obj -> Prop)
deriving (Eq, Ord)
data Obj
= Num Integer
| Add Obj Obj
| Mul Obj Obj
deriving (Eq, Ord)
次のように式を書くことができます。
ForAll (\x -> Exists (\y -> Equals (Add x y) (Mul x y))))
これについては、 MonadReaderの記事で詳しく説明されています。強くお勧めします。
2番目の解決策:
次のような文字列を使用します
data Prop
= Not Prop
| And Prop Prop
| Or Prop Prop
| Impl Prop Prop
| Equiv Prop Prop
| Equals Obj Obj
| ForAll String Prop
| Exists String Prop
deriving (Eq, Ord)
data Obj
= Num Integer
| Var String
| Add Obj Obj
| Mul Obj Obj
deriving (Eq, Ord)
次に、次のような式を書くことができます
ForAll "x" (Exists "y" (Equals (Add (Var "x") (Var "y")))
(Mul (Var "x") (Var "y"))))))
利点は、数式を簡単に表示できることです(関数を表示するのは難しいObj -> Prop
です)。欠点は、衝突(〜alpha変換)と置換(〜beta変換)で名前を変更する必要があることです。どちらのソリューションでも、2つのデータ型の代わりにGADTを使用できます。
data FOL a where
True :: FOL Bool
False :: FOL Bool
Not :: FOL Bool -> FOL Bool
And :: FOL Bool -> FOL Bool -> FOL Bool
...
-- first solution
Exists :: (FOL Integer -> FOL Bool) -> FOL Bool
ForAll :: (FOL Integer -> FOL Bool) -> FOL Bool
-- second solution
Exists :: String -> FOL Bool -> FOL Bool
ForAll :: String -> FOL Bool -> FOL Bool
Var :: String -> FOL Integer
-- operations in the universe
Num :: Integer -> FOL Integer
Add :: FOL Integer -> FOL Integer -> FOL Integer
...
3番目の解決策:数字を使用して変数がバインドされている場所を表します。低いほど深いことを意味します。たとえば、ForAll(Exists(Equals(Num 0)(Num 1)))では、最初の変数はExistsにバインドされ、2番目はForAllにバインドされます。これはdeBruijn数字として知られています。私は数ではありません-私は自由変数です。