Tôi hiện đang làm việc với một trình thông dịch đơn giản cho ngôn ngữ lập trình và tôi có kiểu dữ liệu như thế này:
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
Và tôi có nhiều chức năng làm những việc đơn giản như:
-- 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
Nhưng trong mỗi hàm này, tôi phải lặp lại phần gọi mã theo cách đệ quy chỉ với một thay đổi nhỏ đối với một phần của hàm. Có cách nào để làm điều này rộng rãi hơn không? Tôi thà không phải sao chép và dán phần này:
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
Và chỉ thay đổi một trường hợp duy nhất mỗi lần vì có vẻ không hiệu quả để sao chép mã như thế này.
Giải pháp duy nhất tôi có thể đưa ra là có một hàm gọi hàm đầu tiên trên toàn bộ cấu trúc dữ liệu và sau đó đệ quy về kết quả như sau:
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
Nhưng tôi cảm thấy có lẽ nên có một cách đơn giản hơn để làm điều này rồi. Tui bỏ lỡ điều gì vậy?
Add :: Expr -> Expr -> Expr
thay vì Add :: [Expr] -> Expr
, và loại bỏ Sub
hoàn toàn.
recurseAfter
đang ana
ngụy trang. Bạn có thể muốn xem xét biến thái và recursion-schemes
. Điều đó đang được nói, tôi nghĩ rằng giải pháp cuối cùng của bạn là ngắn nhất có thể. Chuyển sang biến thái chính thức recursion-schemes
sẽ không tiết kiệm nhiều.