Đây là chính xác những gì Reader monad dành cho:
The Reader đơn nguyên (còn gọi là đơn nguyên Môi trường). Trình bày tính toán , có thể đọc giá trị từ môi trường được chia sẻ, chuyển các giá trị từ hàm này sang hàm khác và thực thi các phép tính phụ trong môi trường đã sửa đổi .
Như ghi chú Sjoerd, các đơn nguyên cho quyền lực hơn ở đây hơn bạn cần, nhưng bạn vẫn có thể sử dụng đơn nguyên Reader cho vấn đề này mà không cần quá nhiều như gõ do
:
import qualified Data.Map as M
import Control.Applicative ((<$>), (<*>))
import Control.Monad.Reader
data Expr = Const Bool
| Var Char
| Not Expr
| And Expr Expr
| Or Expr Expr
| Xor Expr Expr
Chỉ cần đặt loại môi trường của bạn làm đối số đầu tiên cho hàm tạo kiểu Reader
và loại kết quả ban đầu của bạn làm loại thứ hai.
eval' :: Expr -> Reader (M.Map Char Bool) Bool
Thay vì chỉ c
như giá trị của vụ án Const
, sử dụng return
để nâng nó vào đơn nguyên:
eval' (Const c) = return c
Khi bạn cần môi trường cho nhìn lên giá trị của một biến, sử dụng ask
. Sử dụng do
ký hiệu, bạn có thể viết các trường hợp Var
như thế này:
eval' (Var v) = do values <- ask
return (M.findWithDefault False v values)
Tôi nghĩ rằng nó đẹp hơn, tuy nhiên, để sử dụng fmap
aka <$>
:
eval' (Var v) = M.findWithDefault False v <$> ask
Tương tự, unary not
có thể được fmap
PED trên kết quả của đệ quy:
eval' (Not x) = not <$> eval' x
Fina lly, các Applicative thể hiện của Reader làm cho các trường hợp nhị phân dễ chịu:
eval' (And a b) = (&&) <$> eval' a <*> eval' b
eval' (Or a b) = (||) <$> eval' a <*> eval' b
eval' (Xor a b) = (/=) <$> eval' a <*> eval' b
Sau đó, để cho nó tất cả bắt đầu, đây là một helper để tạo ra môi trường ban đầu và chạy các tính toán:
eval :: Expr -> [(Char,Bool)] -> Bool
eval exp env = runReader (eval' exp) (M.fromList env)
Không chỉ đơn giản này, nó còn có thể hiệu quả hơn. Tôi tin rằng nó được gọi là chuyển đổi đối số tĩnh. –