gau*_*ing 0 haskell functional-programming lazy-evaluation
目前,我正在尝试通过宾夕法尼亚大学的 FP in Haskell 课程来学习 Haskell。在其中一项作业中,我必须定义以下类型类来实现表达式求值计算器:
class Expr a where
mul :: a -> a -> a
add :: a -> a -> a
lit :: Integer -> a
class HasVars a where
var :: String -> a
Run Code Online (Sandbox Code Playgroud)
一种模仿数学表达式的数据类型,可以包含整数、加法、乘法,还可以在表达式中保存变量。
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
Run Code Online (Sandbox Code Playgroud)
现在,为了模拟表达式中变量的加法、乘法操作,我必须创建上述类型类的实例,如下所示:
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))
Run Code Online (Sandbox Code Playgroud)
为了实际评估表达式,提供了以下函数:
withVars :: [(String, Integer)] -> (M.Map String Integer -> Maybe Integer)-> Maybe Integer
withVars vs exp = exp $ M.fromList vs
Run Code Online (Sandbox Code Playgroud)
因此,在 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
Run Code Online (Sandbox Code Playgroud)
所以我的查询如下:
在 Haskell 的普通表达式中,表达式仅在需要时才被求值,这对我来说仍然不是很直观,但我有点明白了。但是在上面的第一个表达式中,条件中的表达式的内部评估将如何工作?
因为据我了解,首先add正在发生,因此if将检查条件。并且条件必须被评估到达到True或完全评估到的程度False。但在 的第二个表达式中,||它将尝试计算对应于 的mul表达式。现在又出现了一个条件。因此,由于表达式求值过程中出现反复出现的条件,我对求值究竟如何进行感到困惑。(mul (lit 6) (var "x")) mMapf2 mMapifmulif
PS:M.Map等是因为import qualified Data.Map as M
的定义||是惰性的,并且仅在需要时(即第一个参数为 false 时)才计算第二个参数。
您可以在 GHCi 中测试它,观察以下内容不会触发错误。
> True || error "ouch!"
True
Run Code Online (Sandbox Code Playgroud)
在您的情况下,isNothing (f1 mMap) || isNothing (f2 mMap)将首先评估isNothing (f1 mMap),如果这是真的,它将跳过 的评估isNothing (f2 mMap)。
||请注意,这本质上与 C、C++、Java 和许多其他语言中常见的布尔运算符的“短路”语义相同。在那里,除非评估结果为 false,否则评估f() || g()不会调用。gf()
你不应该使用fromJust-- 这是一个部分函数,如果你忘记检查Nothing. 在您的代码中,您确实会对此进行检查,但这不是推荐的样式。
您的代码患有“布尔盲性”:您有两个Maybe Integer值,而不是直接测试它们,而是首先将它们转换为布尔值,从而丢失了宝贵的信息(内部的整数)。由于您在测试中丢失了该信息,因此您需要诉诸危险的工具,例如fromJust恢复在测试中丢失的信息。
这里的常见做法是避免布尔值,避免,并使用模式匹配直接if测试值。Maybe Integer
add f1 f2 = \mMap -> case (f1 mMap, f2 mMap) of
(Nothing, _ ) -> Nothing
(_ , Nothing) -> Nothing
(Just i1, Just i2) -> Just (i1 + i2)
Run Code Online (Sandbox Code Playgroud)
(有库函数可以缩短它,但这无关紧要)。
上面的代码根本没有使用布尔值。它还将f2 mMap仅在需要时进行评估,即仅在(_ , Nothing) -> Nothing到达该行时进行评估,即仅在f1 mMap不是 时进行评估Nothing。这提供了与之前使用布尔值和 的代码相同的惰性语义||。
| 归档时间: |
|
| 查看次数: |
219 次 |
| 最近记录: |