問題
良い答えを得るには、まず具体的な質問が必要です。ゼロまたは別の自然数Z
の後継である自然数を考えてみましょう。S n
n
data Nat = Z | S Nat
zero = Z
one = S Z
two = S $ S Z
Haskell では、次のような無限の再帰的データ構造を書くこともできます。
infinity = S infinity
Nat
偶数でない限り、infinity
偶数か奇数かを判断できます。
even :: Nat -> Bool
even Z = True
even (S n) = odd n
odd :: Nat -> Bool
odd Z = False
odd (S n) = even n
有限Nat
の s の場合、even
はTrue
またはFalse
であり、even infinity
はundefined
です。これは問題ありませんが、2 つの数値のどちらかが であるかどうかを確認するにはどうすればよいeven
でしょうか。素朴な実装を書くことができます:
eitherEven :: Nat -> Nat -> Bool
eitherEven x y = even x || even y
これは、最初の引数が有限である場合はいつでも非常にうまく機能します。以下、even
は任意の偶数、odd
は任意の奇数です。
eitherEven even even == True
eitherEven even odd == True
eitherEven even infinity == True
eitherEven odd even == True
eitherEven odd odd == False
eitherEven odd infinity == undefined
True
しかし、第一引数が無限大の場合、第二引数が無限大の場合でも戻りません。Even
eitherEven infinity even == undefined -- this should be True
eitherEven infinity odd == undefined
eitherEven infinity infinity == undefined
簡単な解決策
この問題の簡単な解決策は、最初の引数のテストと 2 番目の引数のテストを交互に行うことです。関数を再帰的に呼び出すときは、引数を交換して、2 つの引数のどちらをテストするかを切り替えます。
eitherEven :: Nat -> Nat -> Bool
eitherEven Z _ = True
eitherEven (S Z) y = even y
eitherEven (S (S x)) y = eitherEven y x
これには、最初の引数が有限でない場合でも、目的の出力があります。
> eitherEven infinity two
True
引数が対称的に扱われないより複雑な問題については、Bool
フラグを渡して各ステップで反転させることができます。一般に、作業中の場所を追跡するために、任意のステート マシンを使用できます。
3 つの数値のいずれかが偶数かどうかをテストしたいときに何をすべきかをすぐには解決できないため、この解決策はあまり満足のいくものではありません。そのためには、新しい関数を作成する必要があります。
anyEven3 :: Nat -> Nat -> Nat -> Bool
anyEven3 Z _ _ = True
anyEven3 (S Z) y z = eitherEven y z
anyEven3 (S (S x)) y z = anyEven3 y z x
再試行する前に両方をx
試したいので、最後に置きます。行列を作っています。キューの最初のものが結果 を生成することを証明できれば、完了です。キュー内の最初のもので結果が生成されないことを証明できれば、それをキューから削除し、より小さな入力セットで機能するバージョンを使用します。キューの最初のものの結果を決定できない場合は、最後に置きます。同じパターンは、引数を 2 つ取る場合や、引数を1 つしか取る場合にも見られます。y
z
x
True
eitherEven
even
anyEven :: [Nat] -> Bool
anyEven = go []
where
go [] [] = False
go r [] = go [] (reverse r)
go r ( Z :_ ) = True
go r ( S Z :ns) = go r ns
go r ((S (S x)):ns) = go (x:r) ns