【问题标题】:How to efficiently instance Eq for a type class如何有效地为类型类实例化 Eq
【发布时间】:2020-11-26 20:18:44
【问题描述】:

我做了以下类来在不同的温度类型之间进行转换:

data Temp = Kelvin Float | Celsius Float |Fahrenheit Float deriving Show

conversionKelvin:: Temp -> Temp
conversionKelvin (Celsius x) = Kelvin (x + 273.15)
conversionKelvin (Fahrenheit x) = Kelvin((x - 32) * 5/9 + 273.15)
conversionKelvin (Kelvin x) = Kelvin x

conversionCelsius:: Temp -> Temp
conversionCelsius (Kelvin x) = Celsius (x - 273.15)
conversionCelsius (Fahrenheit x) = Celsius((x - 32) * 5/9)
conversionCelsius (Celsius x) = Celsius x

conversionFahrenheit:: Temp -> Temp
conversionFahrenheit (Celsius x) = Fahrenheit (x * 9/5 + 32)
conversionFahrenheit (Kelvin x) = Fahrenheit((x - 273.15)*9/5 + 32)
conversionFahrenheit (Fahrenheit x) = Fahrenheit x

到目前为止一切都很好,但是我想实现实例 Eq 和 Ord。我考虑将每种类型转换为摄氏度,然后查看哪个更大,但我无法通过编译器。 有什么帮助吗?

编辑:这是我实例化 Eq 的尝试:

instance Eq Temp where
    a == b = conversionCelsius(a) == conversionCelsius(b)

它可以编译,但它使 haskell 进入某种循环(不打印输出)

【问题讨论】:

  • 您是如何实现instance Eq 的?请注意,浮点计算通常会出现舍入错误,因此如果两个值彼此接近,则它们可能会“意外”相等,反之亦然。
  • 还需要将Kelvin转换成Kelvin
  • @WillemVanOnsem ,记录和更新
  • 避免舍入问题的一种方法是将固定精度的Float 替换为任意精度的精确Rational(来自Data.Ratio),然后使用fromRational 转换为固定精度精确到最后。否则,例如,将温度从摄氏转换为华氏并返回不一定会产生与(==) 相等的值:let { x = 1 :: Rational; } in (x, ((x * 9/5 + 32.0) - 32.0) * 5/9) 产生完全相同的值(1 % 1, 1 % 1),但使用Float / Double 会产生不等值 (1.0,0.9999996) / (1.0,0.9999999999999984).
  • 考虑将每个温度存储在同一单位(可能是开尔文)中,并且仅为显示目的与其他单位进行转换。您可能会使用 温度而不是创建或显示它们所需的温度,如果一切都立即归一化为开尔文,EqOrd 是微不足道且可推导的。

标签: haskell


【解决方案1】:

这会导致无限递归

instance Eq Temp where
    a == b = conversionCelsius(a) == conversionCelsius(b)

这是因为a == b 的比较在两个Temps 之间,而conversionCelsius(a) == conversionCelsius(b) 的比较再次 在两个Temps 之间,所以我们永远递归。

要停止递归,您至少需要有一个基本情况,其中Temps 之间的比较是通过比较Floats 来完成的。以下是一种可能的解决方案。

instance Eq Temp where
    Celsius x == Celsius y = x == y
    a         == b         = conversionCelsius a == conversionCelsius b

这将直接比较摄氏温度,比较它们的Float 值。其他温度在递归之前被转换为摄氏度。在我们递归之后,我们会立即找到base case,所以递归结束。

【讨论】:

    【解决方案2】:

    我认为您首先需要将Kelvin 转换为Kelvin 等等:

    conversionKelvin:: Temp -> Temp
    conversionKelvin (Celsius x) = Kelvin (x + 273.15)
    conversionKelvin (Fahrenheit x) = Kelvin((x - 32) * 5/9 + 273.15)
    conversionKelvin k@(Kelvin _) = k

    至于Eq 实例,您确实可以先将两个温度转换为相同的单位(无论该单位是什么),然后检查它们是否相等:

    instance Eq Temp where
        a == b | ~(Kelvin ka) <- conversionKelvin a, ~(Kelvin kb) <- conversionKelvin b = ka == kb

    话虽如此,使用Floats 的计算通常会导致舍入错误。因此,这意味着相同的两个Temps可以被认为是不同的,而稍微不同的两个Temps可以被认为是相同的。

    你也可以实现一个函数toKelvin :: Temp -&gt; Float,因为很清楚结果的单位是什么:

    toKelvin :: Temp -> Float
    toKelvin (Celsius x) = x + 273.15
    toKelvin (Fahrenheit x) = (x - 32) * 5/9 + 273.15
    toKelvin (Kelvin k) = k

    然后使用:

    instance Eq Temp where
        a == b = toKelvin a == toKelvin kb

    【讨论】:

      【解决方案3】:

      如何为您的表示选择一个正常形式,派生Eq 实例

      type    Temp :: Type
      newtype Temp = Celsius Float
        deriving
        newtype Eq
      

      然后为不同的单位创建pattern synonyms

      pattern Kelvin :: Float -> Temp
      pattern Kelvin kelvin <- Celsius (celsiusToKelvin -> kelvin)
        where Kelvin kelvin =  Celsius (kelvinToCelsius kelvin)
      
      pattern Fahrenheit :: Float -> Temp
      pattern Fahrenheit fahrenheit <- Celsius (celsiusToFahrenheit -> fahrenheit)
        where Fahrenheit fahrenheit =  Celsius (fahrenheitToCelsius fahrenheit)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-06-29
        • 2018-11-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-29
        相关资源
        最近更新 更多