【问题标题】:How to compare non 'Eq' type in Haskell?如何比较 Haskell 中的非“Eq”类型?
【发布时间】:2021-03-15 06:34:57
【问题描述】:

加载以下模块时出现错误:

(Eq Planet)没有因使用“==”而产生的实例 • 在表达式中:planet == Mercury 在模式保护的一个 stmt 中 “ageOn”的方程:

如何检查行星是否等于某个行星?

module SpaceAge (Planet(..), ageOn) where

data Planet = Mercury
        | Venus
        | Earth
        | Mars
        | Jupiter
        | Saturn
        | Uranus
        | Neptune

mercury :: Float
mercury = 0.2408467

venus :: Float
venus = 0.61519726

earth :: Float
earth = 1.0

mars :: Float
mars = 1.8808158

jupiter :: Float
jupiter = 11.862615

saturn :: Float
saturn = 29.447498

uranus :: Float
uranus = 84.016846

neptune :: Float
neptune = 164.79132

ageOn :: Planet -> Float -> Float
ageOn planet seconds
   | planet == Mercury = seconds * mercury
   | planet == Venus = seconds * venus
   | planet == Earth = seconds * earth
   | planet == Mars = seconds * mars
   | planet == Jupiter = seconds * jupiter
   | planet == Saturn = seconds * saturn
   | planet == Uranus = seconds * uranus
   | planet == Neptune = seconds * neptune

【问题讨论】:

  • 使用模式匹配。或者您可以随时将deriving Eq 添加到数据类型定义中

标签: haskell functional-programming


【解决方案1】:

您可以使用模式匹配

ageOn :: Planet -> Float -> Float
ageOn Mercury seconds = seconds * mercury
ageOn Venus seconds = seconds * venus
ageOn Earth seconds = seconds * earth
ageOn Mars seconds = seconds * mars
ageOn Jupiter seconds = seconds * jupiter
ageOn Saturn seconds = seconds * saturn
ageOn Uranus seconds = seconds * uranus
ageOn Neptune seconds = seconds * neptune

但您也可以将Planet 设为Eq 类型类的实例。如果你只是想检查两个 Planets 是否相同,如果你使用相同的数据构造函数,你可以让 Haskell 用 deriving 子句实现它:

data Planet
  = Mercury
  | Venus
  | Earth
  | Mars
  | Jupiter
  | Saturn
  | Uranus
  | Neptune
  deriving Eq

【讨论】:

  • 模式匹配是“更干净”的方式,即有经验的 Haskell 编码人员会使用它而不是派生子句,对吧?
  • @timtam:这两者是不等价的。您可以创建Eq 的实例,例如MercuryVenus 相等,而对于模式匹配,您可以将其与数据构造函数匹配,并且您可以“解包”参数(这里没有参数)。
  • @timtam 用模式匹配实现你的ageOn 确实更干净。您通常想要拥有Eq 实例,因为它在其他情况下可能很有用(任意示例:groupelem 对列表元素有Eq 约束)。
  • @timtam FWIW 我与 duplode 有不同的看法:我会 100% 派生 Eq -- 以及 OrdEnum。然后我会用myPlanetMap = M.fromList (zip [Mercury .. Neptune] [mercury, venus, earth, {- ... -}, neptune]) 之类的东西创建一个Map Planet Double,然后写ageOn planet seconds = (myPlanetMap ! planet) * seconds
  • @timtam 在编写一个通过检查不同情况来处理已知类型的函数时,有经验的Haskellers 通常会使用模式匹配。 Eq 检查通常由不知道它们正在处理的类型的泛型函数使用(因此它们无法在静态编译的一组案例上进行模式匹配,但可能需要检查它们有权访问的其他值至)。这正是@DanielWagner 的建议需要Eq(和Ord)的原因;它重用了Map 的通用功能,适用于具有Ord 实例的任何类型,而不是特定于您的Planet
【解决方案2】:
ageOn planet seconds = seconds * case planet of
  Mercury -> mercury
  Venus -> venus
  Earth -> earth
  Mars -> mars
  ...

或者更好的是,排除查找因素。

yearLength :: Planet -> Float
yearLength = \case
  Mercury -> mercury
  Venus -> venus
  ...

ageOn planet seconds = yearLength planet * seconds

同时,我认为您的逻辑可能有错误。如果我活了 1 个地球年,那么我活了超过 1 个水星年。因此,根据您的功能的确切含义,您可能想要定义

ageOn planet earthYears = earthYears / yearLength planet

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多