Экземпляр уравнения для (==) переопределения - PullRequest
0 голосов
/ 11 июня 2019

У меня есть следующая декларация данных для представления температуры:

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

-- Functions for conversion between temperatures
kelvToCels :: Temp -> Temp
kelvToCels (Kelvin k) = Celsius (k-273.15)

kelvToFahr :: Temp -> Temp
kelvToFahr (Kelvin k) = Fahrenheit (((9/5)*(k-273.15))+32)

celsToKelv :: Temp -> Temp
celsToKelv (Celsius c) = Kelvin (c+273.15)

celsToFahr :: Temp -> Temp
celsToFahr (Celsius c) = Fahrenheit (((9/5)*c)+32)

fahrToKelv :: Temp -> Temp
fahrToKelv (Fahrenheit f) = Kelvin ((5/9)*(f-32)+273.15) 

fahrToCels :: Temp -> Temp
fahrToCels (Fahrenheit f) = Celsius ((f-32)/(9/5))

Я хочу иметь возможность сравнивать температуры, такие, что

> (Celsius 100) == (Fahrenheit 212.0) оценивается как истина.

Вот мои попытки:

instance Eq Temp where
   Celsius c == Fahrenheit f = 
    (celsToFahr c) == f

Результат: ошибка ghci, потому что c и f на RHS - это Float, а не Temps, поэтому здесь есть 'fix':

instance Eq Temp where
   Celsius c == Fahrenheit f = 
    (celsToFahr (Celsius c)) == (Fahrenheit f)

Компилируется без ошибок, однако (Celsius 100) == (Fahrenheit 212.0) выдает исключение: неисчерпывающие шаблоны в функции ==

Я также хотел бы сделать экземпляр Ord, чтобы переопределить compare аналогичным образом.

Я зашел в тупик и не могу найти примеров, похожих на мой, поэтому любой совет очень важен. Заранее спасибо.

Ответы [ 2 ]

11 голосов
/ 11 июня 2019

Я рекомендую вам никогда написать неполное совпадение с образцом. Думая о том, что это означает для ваших xToY функций, это означает, что они должны быть в состоянии обрабатывать любые входные данные - и поэтому их имена должны измениться на toY.

Я также представляю гарантию, что мы знаем, какой конструктор используется, возвращая Float (который явно не может быть помечен неправильным конструктором), а не Temp (который мог). Итак:

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

Аналогично для toCelsius и toFahrenheit. Если бы вы действительно хотели, вы могли бы отдельно написать что-то вроде

normalizeKelvin :: Temp -> Temp
normalizeKelvin = Kelvin . toKelvin

но будет ли это разумным или нет, во многом зависит от того, как вы планируете использовать этот код.

Учитывая это, теперь мы можем написать экземпляр Eq, который не является рекурсивным, просто выбрав одну из шкал в качестве естественной и преобразовав в нее *. Итак:

instance Eq Temp where
    t == t' = toKelvin t == toKelvin t'

Обратите внимание, что здесь мы отправляем из экземпляра Temp в экземпляр Float Eq, когда мы вызываем (==), в отличие от вашего кода, который отправлялся из экземпляра Temp обратно на другой вызов к Temp экземпляр Eq.

* Если вы параноидально настроены на округление, вы можете сначала проверить, нужно ли вообще преобразование. Итак:

instance Eq Temp where
    Fahrenheit f == Fahrenheit f' = f == f'
    Celsius c == Celsius c' = c == c'
    t == t' = toKelvin t == toKelvin t'
0 голосов
/ 12 июня 2019

Я бы порекомендовал вам избегать наличия трех разных возможных представлений температуры.Это просто приводит к большому количеству ветвлений и преобразований во время выполнения.Имеет смысл иметь выделенный тип для температур, и имеет смысл оставить шкалу, использовавшую частную реализацию, но придерживаться одного соглашения упрощает вещи.

module Physics.Quantities.Temperature (Temperature) where
newtype Temp = KelvinTemp { getKelvinTemperature :: Double }
  deriving (Eq, Ord)

Обратите внимание, что я не экспортируюспецифичный для Кельвина конструктор, поэтому для любого, кто использует этот тип, не может иметь значение, какая шкала температуры используется.А поскольку внутреннее представление является фиксированным, компилятор может самостоятельно определять экземпляры Eq и Ord.

Теперь все в порядке, очевидно, вам все равно нужно будет на самом деле получить материалсделано , поэтому вам понадобятся средства доступа.Одним из способов является простое считывание в этом масштабе, например

toCelsius :: Temp -> Double
toCelsius (KelvinTemp tK) = tK - waterTriplePointInK

Но это будет односторонний процесс, не позволяющий создавать значения температуры снова.Элегантный способ достижения этого - использовать двунаправленные функции - изоморфизмы.Наиболее популярным представлением является библиотека из объектива :

import Control.Lens

kelvin :: Iso' Temp Double
kelvin = iso getKelvinTemperature KelvinTemp

celsius :: Iso' Temp Double
celsius = iso (\(Temp tK) -> tK - waterTriplePointInK)
              (\tC -> Temp $ tC + waterTriplePointInK)
 where waterTriplePointInK = 273.15

fahrenheit :: Iso' Temp Double
fahrenheit = iso (\(Temp tK) -> (tK - fahrZeroInK)/fahrScaleFact)
                 (\tF -> Temp $ tF*fahrScaleFact + fahrZeroInK)
 where fahrZeroInK = 255.372
       fahrScaleFact = 5/9

Теперь вы можете делать такие вещи, как

*Main> let tBoil :: Temp; tBoil = 100^.from celsius
*Main> tBoil^.fahrenheit
212.00039999999993

*Main> 37^.from celsius.fahrenheit
98.60039999999992

*Main> 4000^.from kelvin.celsius
3726.85

Если вы действительно хотите иметь разные представлениядля других масштабов есть еще один подход, который более типизирован и позволит избежать ветвления во время выполнения:

{-# LANGUAGE DataKinds, KindSignatures, MultiParamTypeClasses #-}

data TemperatureScale = KelvinSc | CelsiusSc | FahrenheitSc

newtype     KelvinTemperature = Kelvin     {getKelvinTemperature    ::Double}
newtype    CelsiusTemperature = Celsius    {getCelsiusTemperature   ::Double}
newtype FahrenheitTemperature = Fahrenheit {getFahrenheitTemperature::Double}

type family Temperature (sc :: TemperatureScale) where
  Temperature 'KelvinSc     = KelvinTemperature
  Temperature 'CelsiusSc    = CelsiusTemperature
  Temperature 'FahrenheitSc = FahrenheitTemperature

class ConvTemperature t t' where
  convTemperature :: Temperature t -> Temperature t'

instance ConvTemperature KelvinSc  KelvinSc        where convTemperature = id
instance ConvTemperature CelsiusSc CelsiusSc       where convTemperature = id
instance ConvTemperature FahrenheitSc FahrenheitSc where convTemperature = id
instance ConvTemperature KelvinSc FahrenheitSc where
  ...
...

Если вы действительно серьезно относитесь к этому, посмотрите пакет unit ,который делает все это и многое другое.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...