[英]Emacs: how to evaluate Haskell expressions in the comment section within the source file
[英]How to evaluate expressions in Haskell
我了解如何创建和评估简单的数据类型Expr。 例如这样:
data Expr = Lit Int | Add Expr Expr | Sub Expr Expr | [...]
eval :: Expr -> Int
eval (Lit x) = x
eval (Add x y) = eval x + eval y
eval (Sub x y) = eval x - eval y
所以这是我的问题:如何将变量添加到此Expr类型,应该针对其指定值进行评估? 它应该如下所示:
data Expr = Var Char | Lit Int | Add Expr Expr [...]
type Assignment = Char -> Int
eval :: Expr -> Assignment -> Int
我现在如何为(Var Char)和(Add Expr Expr)执行我的eval功能? 我想我已经找到了最简单的,如何为Lit做这件事。
eval (Lit x) _ = x
对于(Var Char),我尝试了很多,但是我无法从作业中获得一个Int ..想想它会像这样工作:
eval (Var x) (varname number) = number
您需要将赋值函数应用于变量名称以获取Int:
eval (Var x) f = f x
这是因为f :: Char -> Int
和x:: Char
,所以你可以只用fx
来获取Int。 令人满意的是,这将适用于变量名称的集合。
ass :: Assignment
ass 'a' = 1
ass 'b' = 2
意思是
eval ((Add (Var 'a') (Var 'b')) ass
= eval (Var 'a') ass + eval (Var 'b') ass
= ass 'a' + ass 'b'
= 1 + 2
= 3
eval
调用 你需要继续传递赋值函数,直到得到整数:
eval (Add x y) f = eval x f + eval y f
如果允许更改类型,那么将赋值函数放在第一位且数据放在第二位似乎更合乎逻辑:
eval :: Assignment -> Expr -> Int
eval f (Var x) = f x
eval f (Add x y) = eval f x + eval f y
...但我想你可以把它看作一个带有变量变量的常量表达式(感觉势在必行),而不是一系列表达式中的一组常量值(感觉就像参考透明度)。
好吧,如果你把你的环境建模为
type Env = Char -> Int
那么你拥有的就是
eval (Var c) env = env c
但这不是真的“正确”。 首先,未绑定变量会发生什么? 所以也许更准确的类型
type Env = Char -> Maybe Int
emptyEnv = const Nothing
现在我们可以看到变量是否未绑定
eval (Var c) env = maybe handleUnboundCase id (env c)
现在我们可以使用handleUnboundCase
来做一些事情,比如指定一个默认值,炸毁程序,或让猴子爬出你的耳朵。
最后要问的问题是“变量如何约束?”。 如果您在Haskell中查找“let”语句,那么我们可以使用称为HOAS(高阶抽象语法)的技巧。
data Exp = ... | Let Exp (Exp -> Exp)
HOAS位是(Exp - > Exp)。 基本上我们使用Haskell的名称绑定来实现我们的语言。 现在来评估我们做的let
表达式
eval (Let val body) = body val
这让我们通过依靠Haskell解析变量名来躲避Var
和Assignment
。
此样式中的let语句示例可能是
Let 1 $ \x -> x + x
-- let x = 1 in x + x
这里最大的缺点是建模可变性是一种巨大的痛苦,但依赖于Assignment
类型与具体地图的情况就是这种情况。
我建议改用Data.Map
Map
。 你可以实现类似的东西
import Data.Map (Map)
import qualified Data.Map as M -- A lot of conflicts with Prelude
-- Used to map operations through Maybe
import Control.Monad (liftM2)
data Expr
= Var Char
| Lit Int
| Add Expr Expr
| Sub Expr Expr
| Mul Expr Expr
deriving (Eq, Show, Read)
type Assignment = Map Char Int
eval :: Expr -> Assignment -> Maybe Int
eval (Lit x) _ = Just x
eval (Add x y) vars = liftM2 (+) (eval x vars) (eval y vars)
eval (Sub x y) vars = liftM2 (-) (eval x vars) (eval y vars)
eval (Mul x y) vars = liftM2 (*) (eval x vars) (eval y vars)
eval (Var x) vars = M.lookup x vars
但这看起来很笨重,每次添加操作时我们都必须继续使用liftM2 op
操作。 让我们用一些助手清理一下吧
(|+|), (|-|), (|*|) :: (Monad m, Num a) => m a -> m a -> m a
(|+|) = liftM2 (+)
(|-|) = liftM2 (-)
(|*|) = liftM2 (*)
infixl 6 |+|, |-|
infixl 7 |*|
eval :: Expr -> Assignment -> Maybe Int
eval (Lit x) _ = return x -- Use generic return instead of explicit Just
eval (Add x y) vars = eval x vars |+| eval y vars
eval (Sub x y) vars = eval x vars |-| eval y vars
eval (Mul x y) vars = eval x vars |*| eval y vars
eval (Var x) vars = M.lookup x vars
这是一个更好的,但我们仍然需要绕过各地的vars
,这对我来说是丑陋的。 相反,我们可以使用mtl包中的ReaderT
monad。 ReaderT
monad(和非变换器Reader
)是一个非常简单的monad,它公开了一个函数ask
,它返回你在运行时传入的值,你可以做的就是“读取”这个值,并且通常用于使用静态配置运行应用程序。 在这种情况下,我们的“配置”是一个Assignment
。
这就是liftM2
运营商真正派上用场的地方
-- This is a long type signature, let's make an alias
type ExprM a = ReaderT Assignment Maybe a
-- Eval still has the same signature
eval :: Expr -> Assignment -> Maybe Int
eval expr vars = runReaderT (evalM expr) vars
-- evalM is essentially our old eval function
evalM :: Expr -> ExprM Int
evalM (Lit x) = return x
evalM (Add x y) = evalM x |+| evalM y
evalM (Sub x y) = evalM x |-| evalM y
evalM (Mul x y) = evalM x |*| evalM y
evalM (Var x) = do
vars <- ask -- Get the static "configuration" that is our list of vars
lift $ M.lookup x vars
-- or just
-- evalM (Var x) = ask >>= lift . M.lookup x
我们真正改变的唯一一件事就是每当遇到Var x
时我们都要做一些额外的事情,我们删除了vars
参数。 我认为这使得evalM
非常优雅,因为我们只在需要时访问Assignment
,而且我们甚至不必担心失败,它完全由Monad
实例为Maybe
。 在整个算法中没有一行错误处理逻辑,但如果Assignment
不存在变量名,它将优雅地返回Nothing
。
另外,考虑以后是否要切换到Double
并添加除法,但是您还要返回错误代码,以便确定是否存在除以0错误或查找错误。 而不是Maybe Double
,你可以使用Either ErrorCode Double
data ErrorCode
= VarUndefinedError
| DivideByZeroError
deriving (Eq, Show, Read)
然后你可以把这个模块写成
data Expr
= Var Char
| Lit Double
| Add Expr Expr
| Sub Expr Expr
| Mul Expr Expr
| Div Expr Expr
deriving (Eq, Show, Read)
type Assignment = Map Char Double
data ErrorCode
= VarUndefinedError
| DivideByZeroError
deriving (Eq, Show, Read)
type ExprM a = ReaderT Assignment (Either ErrorCode) a
eval :: Expr -> Assignment -> Either ErrorCode Double
eval expr vars = runReaderT (evalM expr) vars
throw :: ErrorCode -> ExprM a
throw = lift . Left
evalM :: Expr -> ExprM Double
evalM (Lit x) = return x
evalM (Add x y) = evalM x |+| evalM y
evalM (Sub x y) = evalM x |-| evalM y
evalM (Mul x y) = evalM x |*| evalM y
evalM (Div x y) = do
x' <- evalM x
y' <- evalM y
if y' == 0
then throw DivideByZeroError
else return $ x' / y'
evalM (Var x) = do
vars <- ask
maybe (throw VarUndefinedError) return $ M.lookup x vars
现在我们确实有明确的错误处理,但它并不坏,我们已经能够使用maybe
来避免显式匹配Just
和Nothing
。
这比你真正需要解决这个问题的信息要多得多,我只想提出一个替代解决方案,它使用Maybe
和Either
ReaderT
属性来提供简单的错误处理,并使用ReaderT
来清除传递Assignment
参数的噪音到处都是。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.