2
data Foo = Bar1
         | Bar2 Foo Foo
         | Bar3 Foo
         | Bar4 Foo Foo Foo

ここで、誰かがFooツリーを作成し、Foo 値の引数が有効かどうかを確認したいとします。コンストラクターの引数に関する規則は次のとおりです。

  • Bar2 Bar1 Foo
  • Bar3 (Bar2|Bar3|Bar4)
  • Bar4 Bar1 Bar1 (Bar1|Bar4)

私は値のコンストラクターを知っており、直接の引数のみをチェックしたいだけで、再帰的なものは何もありません。このような:

bar2 Bar1 Bar1      = True
bar2 Bar1 (Bar2 {}) = True
bar2 Bar1 (Bar3 _)  = True
bar2 Bar1 (Bar4 {}) = True
bar2 _ _ = False

たとえば、Bar4 の場合も同様です。

bar4 Bar1 Bar1 Bar1      = True
bar4 Bar1 Bar1 (Bar4 {}) = True
bar4 _ _ _ = False

これらの条件を最も簡潔に表現するにはどうすればよいでしょうか? すべての組み合わせをリストすることは、場合によっては少し多すぎます。私の知る限り、パターン マッチングの "OR" 構文は存在しません。

アップデート

私はダニエルのソリューションを採用し、これに到達しました:

data Foo = Bar1
         | Bar2 Foo Foo
         | Bar3 Foo
         | Bar4 Foo Foo Foo
         deriving (Data, Typeable)

bar2 a b = a `isOf` [Bar1] && b `isOf` [Bar1,Bar2{},Bar3{},Bar4{}]
bar4 a b c = [a,b] `areOf` [Bar1] && c `isOf` [Bar1,Bar4{}]

isOf l r = toConstr l `elem` map toConstr r
areOf l r = all (`isOf` r) l

これについて私が気に入っているのは、派生句を追加する以外はデータ型を変更する必要がなく、読み取り可能であることです。もちろん、欠点は、これらが動的チェックであることです。私の場合、アサートのようなチェックでプログラミング エラーを検出するだけなので、これで問題ありません。

4

2 に答える 2

5

型システムレベルでそれをチェックする唯一の方法は、それをいくつかのデータ型に分割することだと思います。何かのようなもの:

data Foo = Foo1 Bar1 | Foo2 Bar2 | Foo3 Bar3 | Foo4 Bar4
data Bar1 = Bar1
data Bar2 = Bar2a Bar1 Foo
data Bar3 = Bar3a Bar2 | Bar3b Bar3 | Bar3c Bar4
data Bar4 = Bar4a Bar1 Bar1 Bar1 | Bar4b Bar1 Bar1 Bar4

一部の型変数を特定のコンストラクターに制限することはできないため、そのための新しいデータ型を作成する必要があります。

欠点は、入力するコンストラクター/パターンがはるかに多いことですが、いくつかのヘルパー関数を使用することで、少なくとも部分的に解決できます。


編集:おそらく別の解決策は、GADTを使用してコンストラクターにタグを付けるためにファントムタイプを使用することです。

{-# LANGUAGE GADTs #-}

data FBar1
data FBar2
data FBar3
data FBar4

data Foo a where
    Bar1 :: Foo FBar1
    Bar2 :: Foo FBar1 -> Foo b -> Foo FBar2
    Bar3a :: Foo FBar2 -> Foo FBar3
    Bar3b :: Foo FBar3 -> Foo FBar3
    Bar3c :: Foo FBar4 -> Foo FBar3
    Bar4a :: Foo FBar1 -> Foo FBar1 -> Foo FBar4
    Bar4b :: Foo FBar1 -> Foo FBar4 -> Foo FBar4

この解決策が解決するよりも多くの問題を引き起こさないかどうかはわかりません。たとえば、次のような関数を作成することはできません。

construct :: Int -> FooAny X
construct 0 = Bar1
construct 1 = Bar2 Bar1 Bar1

Xとの両方FBar1である必要があるためFBar2そのための実存主義が必要です。たとえば、次のようにラップします。

data FooAny where
    FooAny :: Foo a -> FooAny

construct :: Int -> FooAny
construct 0 = FooAny $ Bar1
construct 1 = FooAny $ Bar2 Bar1 Bar1
于 2012-08-17T16:22:19.507 に答える
2

投稿された優れた静的チェックソリューションがあります。これは、動的チェック ソリューションの提案です。重要なアイデアは、パターン マッチングを避け、代わりにコンパクトなコードを記述するためのすべてのツールを使用することです。これを行うにはいくつかの選択肢があります。2つお話しします。isBarX1 つ目は、コンストラクターごとに関数を記述することです。

isBar1 (Bar1 {}) = True
isBar1 _ = False

-- ...

isBar4 (Bar4 {}) = True
isBar4 _ = False

valid (Bar1)       = True
valid (Bar2 a b)   = isBar1 a
valid (Bar3 a)     = not (isBar1 a)
valid (Bar4 a b c) = isBar1 a && isBar1 b && (isBar1 c || isBar4 c)

もう 1 つのオプションは、どのコンストラクターが使用されたかを示すデータを返す関数を作成することです。たとえば、 のようなカスタム型data Constructor = CBar1 | CBar2 | CBar3 | CBar4、または以下で行うように、 のような少しハックなものを作成しIntます。

constr (Bar1 {}) = 1
constr (Bar2 {}) = 2
constr (Bar3 {}) = 3
constr (Bar4 {}) = 4

valid (Bar1)       = True
valid (Bar2 a b)   = constr a == 1
valid (Bar3 a)     = constr a /= 1
valid (Bar4 a b c) = constr a == 1 && constr b == 1 && constr c `elem` [1,4]
于 2012-08-17T18:27:44.347 に答える