Haskell中如何检查Fractional类型实例是否为零值?
如何在不添加Eq约束的情况下实现Fractional类型的安全除法?
问题描述
我有一个被限定为Fractional类型的值,想检查它是否为零值,以此实现如下签名的安全除法:
safe_div :: (Fractional q) => q -> q -> Maybe q
我查了Hoogle上Fractional的最小定义,没找到可用方法。
补充说明:我希望避免修改q的约束,但模式匹配零值时似乎隐式需要Eq约束。比如我写了这样的定义:
safe_div :: (Fractional q) => q -> q -> Result q safe_div a 0 = Err ["division by zero"] safe_div a b = Ok (a / b)
触发了如下错误:
- Could not deduce (Eq q) arising from the literal `0'
from the context: Fractional q
bound by the type signature for:
safe_div :: forall q. Fractional q => q -> q -> Result q
at AST2.hs:11:1-48
Possible fix:
add (Eq q) to the context of
the type signature for:
safe_div :: forall q. Fractional q => q -> q -> Result q- In the pattern: 0
In an equation for `safe_div':
safe_div a 0 = Err ["division by zero"]
解答
首先得明确:Fractional类型类本身不带相等检查的能力,所以不管是直接写0做模式匹配,还是用==判断是不是零,都得额外加Eq q的约束。要是你不想加这个约束,有几个思路,但最实用的还是直接添加约束:
1. 绕弯用运算判断(本质仍依赖Eq)
Fractional类型都支持倒数recip和乘法,理论上非零的数b满足b * recip b == 1。但要判断这个结果是不是1,还是得用Eq,只是换了个方式用Eq,而且浮点数还可能有精度坑,不推荐。
2. 直接添加Eq约束(最靠谱的常规做法)
Haskell里实际用到的Fractional类型,比如Float、Double、Rational,全都实现了Eq。所以加个Eq q完全不麻烦,改完的代码是这样的:
safe_div :: (Fractional q, Eq q) => q -> q -> Result q safe_div a 0 = Err ["division by zero"] safe_div a b = Ok (a / b)
如果是用Maybe的版本:
safe_div :: (Fractional q, Eq q) => q -> q -> Maybe q safe_div _ 0 = Nothing safe_div a b = Just (a / b)
3. 自定义带零检查的类型类(进阶折腾玩法)
真要是非得脱离Eq,那你可以自己搞个继承Fractional的类型类,里面加个判断零的方法:
class Fractional q => ZeroCheckable q where isZero :: q -> Bool -- 给常用类型手动实现实例 instance ZeroCheckable Double where isZero x = x == 0.0 instance ZeroCheckable Rational where isZero = (== 0) -- 用新类型类实现安全除法 safe_div :: (ZeroCheckable q) => q -> q -> Result q safe_div a b | isZero b = Err ["division by zero"] | otherwise = Ok (a / b)
但这个方法要给每个用到的Fractional类型手动写实例,太折腾,一般没必要。
总结
直接添加Eq q约束是最省心也最符合Haskell常规写法的选择,几乎不会有什么额外负担。
内容的提问来源于stack exchange,提问作者Nikolai Savulkin
相关产品推荐
相关产品推荐

