Haskell中Applicative函子下惰性求值失效的原因与解决方法
Applicative中逻辑与(&&)的短路行为疑惑解答
问题原因
核心差异在于Applicative实例的求值规则和Haskell惰性求值的作用边界:
- 直接执行
(&&) False undefined时,Haskell的惰性求值会优先处理(&&)的第一个参数:由于False已经满足逻辑与的短路条件,函数直接返回False,完全不会去触碰第二个参数undefined,自然不会抛出异常。 - 但用Applicative组合时,
(&&) <$> Just False会生成Just (False &&)(即部分应用的函数),接下来执行<*> undefined时,Maybe的Applicative实例定义要求先对<*>的右操作数求值——也就是先尝试解析undefined,这一步直接触发了异常,根本没机会进入False && _的短路逻辑。
能否避免此问题?
当然可以。只要改变求值逻辑,让后续操作的执行依赖于前一步的结果,而非无条件地预先求值所有参数,就能避免触发undefined异常。
实现短路行为的方法
方法1:用Monad绑定替代Applicative
Monad的>>=操作天生支持根据前一个结果决定后续行为,完美适配短路需求:
Just False >>= \x -> return (x && undefined)
这段代码会直接返回Just False——因为当x为False时,x && undefined不会去求值undefined,且>>=仅在第一个参数为Just时才执行后续函数,完全符合短路逻辑。
方法2:封装专用的短路逻辑函数
针对逻辑与场景,可以写一个专门的辅助函数,利用模式匹配实现短路:
andApplicative :: Maybe Bool -> Maybe Bool -> Maybe Bool andApplicative Nothing _ = Nothing andApplicative _ Nothing = Nothing andApplicative (Just False) _ = Just False -- 直接短路返回 andApplicative (Just True) mb = fmap (True &&) mb
调用andApplicative (Just False) undefined时,会直接匹配到第三个模式,返回Just False,不会去求值第二个参数。
方法3:自定义支持短路的Applicative实例
如果需要在Applicative层面通用支持短路,可以自定义一个类似Maybe的新类型,重写<*>操作以识别可短路的部分应用函数:
newtype ShortCircuitMaybe a = ShortCircuitMaybe (Maybe a) deriving (Show) instance Functor ShortCircuitMaybe where fmap f (ShortCircuitMaybe m) = ShortCircuitMaybe (fmap f m) instance Applicative ShortCircuitMaybe where pure = ShortCircuitMaybe . Just ShortCircuitMaybe Nothing <*> _ = ShortCircuitMaybe Nothing ShortCircuitMaybe (Just f) <*> sm@(ShortCircuitMaybe mx) = -- 检查当前部分应用函数是否是False && _的形式 case f False of False -> ShortCircuitMaybe (Just False) -- 短路返回 _ -> ShortCircuitMaybe (f <$> mx)
这个实例会在左边的部分应用函数对False返回False时(也就是&&的部分应用),直接返回结果,跳过右操作数的求值。
内容的提问来源于stack exchange,提问作者Leonid
相关产品推荐
相关产品推荐

