Haskell布尔值定义如何避免无限递归?求F#适配思路
问题描述
多年前了解到Haskell的布尔表达式比较机制,希望在F#项目中复用类似逻辑,但不清楚实现原理。看到的Haskell代码如下:
x /= y = not (x == y) x == y = not (x /= y)
这段逻辑看似通顺,但疑惑为何不会陷入无限递归?
另外,项目中存在已花费和未花费的硬币,希望仿照布尔值的方式处理——硬币未花费即未被标记为已花费,已花费即未被标记为未花费。
为什么不会触发无限递归?
这是Haskell类型类(Typeclass)的默认实现特性。Eq类型类同时定义了==(相等)和/=(不等)两个方法,上述代码是给这两个方法提供了互为反向的默认实现。
实际使用时,对于任意具体类型(比如Bool、Int),必须至少直接实现其中一个方法:要么手动定义==的具体比较逻辑,要么手动定义/=的具体比较逻辑,另一个方法会自动通过默认实现推导出来。如果两个方法都不手动实现,编译器会直接报错。
举个实际例子:GHC内部对Bool类型直接实现了==的具体逻辑(比如True == True = True,True == False = False),这时/=就会通过not (x == y)计算,完全不会触发递归;如果某个类型先手动实现/=,那==就会用not (x /= y)推导,同样不存在递归问题。
F#中硬币状态的实现思路
你的硬币状态是典型的互斥二元状态,可以参考Haskell的思路设计,既避免重复逻辑,又不会出现递归问题:
1. 定义状态类型
先创建一个区分已花费/未花费的联合类型:
type CoinStatus = | Spent | Unspent
2. 实现互反的状态判断函数
选择其中一个判断作为基础实现,另一个通过取反推导:
// 基础实现:判断硬币是否已花费 let isSpent (status: CoinStatus) = match status with | Spent -> true | Unspent -> false // 基于基础实现推导:判断硬币是否未花费 let isUnspent status = not (isSpent status)
你也可以反过来,先实现isUnspent再推导isSpent,核心是保证其中一个是直接匹配类型的具体逻辑,另一个仅做取反调用,完全不会有递归风险。
如果需要给状态添加相等/不等比较,也可以利用F#的结构相等特性(联合类型默认支持),再定义不等判断:
// 利用F#内置结构相等实现相等判断 let (==) (a: CoinStatus) (b: CoinStatus) = a = b // 基于相等判断推导不等判断 let (!=) a b = not (a == b)
这里的==是直接调用F#内置的相等逻辑,不存在递归定义,同样不会有无限递归问题。
内容的提问来源于stack exchange,提问作者lontivero

