目前我正在学习Haskell,在宾夕法尼亚大学的Haskell课程中学习FP。在其中一个作业中,我必须定义以下类型类来实现表达式求值计算器:
class Expr a where
mul :: a -> a -> a
add :: a -> a -> a
lit :: Integer -> a
class HasVars a where
var :: String -> a
以及一个模仿数学表达式的数据类型,它可以包含整数的加法、乘法,也可以在表达式中保存变量。
data VarExprT = VarLit Integer
| VarAdd VarExprT VarExprT
| VarMul VarExprT VarExprT
| Var String
deriving (Show, Eq)
instance HasVars VarExprT where
var = Var
instance Expr VarExprT where
lit = VarLit
add = VarAdd
mul = VarMul
现在,为了模拟在一个带有变量的表达式中的加法、乘法操作,我必须创建上述类型类的示例,如下所示:
instance HasVars (M.Map String Integer -> Maybe Integer) where
var str = \mMap -> M.lookup str mMap
instance Expr (M.Map String Integer -> Maybe Integer) where
lit x = \mMap -> Just x
add f1 f2 = \mMap -> if isNothing (f1 mMap) || isNothing (f2 mMap)
then
Nothing
else
Just (fromJust (f1 mMap) + fromJust (f2 mMap))
mul f1 f2 = \mMap -> if isNothing (f1 mMap) || isNothing (f2 mMap)
then
Nothing
else
Just (fromJust (f1 mMap) * fromJust (f2 mMap))
为了实际计算表达式,提供了以下函数:
withVars :: [(String, Integer)] -> (M.Map String Integer -> Maybe Integer)-> Maybe Integer
withVars vs exp = exp $ M.fromList vs
因此,在ghci中使用上述函数看起来如下所示:
*Calc> withVars [("x", 6)] $ add (lit 3) (mul (lit 6) (var "x"))
Just 39
*Calc> withVars [("x", 6)] $ add (lit 3) (var "y")
Nothing
因此,我的查询如下:
在Haskell的一个普通表达式中,表达式只在需要的时候才求值,这对我来说仍然不是很直观,但是我有点明白了。但是在上面的第一个表达式中,内部求值是如何对条件中的表达式工作的呢?
因为据我所知,第一个add
正在发生,因此将检查if
条件。并且必须将条件求值到True
或完全求值到False
的点。但是在||
的第二个表达式中,它将尝试计算对应于f2 mMap
的mul
表达式。现在mul
中又出现了一个if
条件,所以我很困惑,因为在表达式求值过程中,会出现反复出现的if
条件。
PS:M.Map等是因为import qualified Data.Map as M
1条答案
按热度按时间pod7payv1#
||
的定义是惰性的,并且仅在需要时才计算第二个参数,即当第一个参数为假时。您可以在GHCi中进行测试,观察以下内容是否不会触发错误。
在您的示例中,
isNothing (f1 mMap) || isNothing (f2 mMap)
将首先计算isNothing (f1 mMap)
,如果这是真的,则将跳过对isNothing (f2 mMap)
的计算。注意,这本质上与布尔运算符
||
的“短路”语义相同,后者在C、C++、Java和许多其他语言中很常见。在这里,对f() || g()
求值不会调用g
,除非f()
求值为false。关于风格的小插曲
你不应该使用
fromJust
--这是一个分部函数,如果你忘记检查Nothing
,可能会导致程序崩溃。在你的代码中,你会检查它,但这不是推荐的风格。您的代码遭受“布尔盲”:您有两个
Maybe Integer
值,而不是直接测试它们,而是首先将它们转换为布尔值,从而丢失了宝贵的信息(里面的整数)。由于在测试中丢失了这些信息,因此您需要求助于fromJust
这样的危险工具来恢复在测试中丢失的信息。这里通常的做法是避免布尔值,避免
if
,并使用模式匹配直接测试Maybe Integer
值。(有一些库函数可以缩短这一点,但这是无关紧要的)。
上面的代码根本没有使用布尔值。它也只在需要的时候才计算
f2 mMap
,也就是说,只有当f1 mMap
到达(_ , Nothing) -> Nothing
行时,也就是说,只有当f1 mMap
不是Nothing
时。这提供了与前面使用布尔值和||
的代码相同的懒惰语义。