私は現在、プログラミング言語の簡単なインタープリターに取り組んでおり、次のようなデータ型があります。
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
そして、私は次のような単純なことを行う多くの関数を持っています:
-- Substitute a value for a variable
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = go
where
go (Variable x)
| x == name = Number newValue
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
-- Replace subtraction with a constant with addition by a negative number
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = go
where
go (Sub x (Number y)) =
Add [go x, Number (-y)]
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
しかし、これらの各関数では、関数の一部を少し変更するだけで、コードを再帰的に呼び出す部分を繰り返す必要があります。これをより一般的に行うための既存の方法はありますか?この部分をコピーして貼り付ける必要はありません。
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
そして、このようにコードを複製するのは非効率的と思われるため、毎回1つのケースを変更するだけです。
私が思いつくことができる唯一の解決策は、最初にデータ構造全体に対して関数を呼び出し、次に次のように結果に対して再帰的に呼び出す関数を持つことです:
recurseAfter :: (Expr -> Expr) -> Expr -> Expr
recurseAfter f x =
case f x of
Add xs ->
Add $ map (recurseAfter f) xs
Sub x y ->
Sub (recurseAfter f x) (recurseAfter f y)
other -> other
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue =
recurseAfter $ \case
Variable x
| x == name -> Number newValue
other -> other
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd =
recurseAfter $ \case
Sub x (Number y) ->
Add [x, Number (-y)]
other -> other
しかし、私はおそらくこれを行うためのもっと簡単な方法があるはずだと感じています。何か不足していますか?
Add :: Expr -> Expr -> Expr
代わりに定義しAdd :: [Expr] -> Expr
、Sub
完全に取り除きます。
recurseAfter
がana
変装していると思います。アナモルフィズムとを見てみたいかもしれませんrecursion-schemes
。そうは言っても、あなたの最終的な解決策は可能な限り短いと思います。公式のrecursion-schemes
アナモルフィズムに切り替えても、それほどの節約にはなりません。