gpt4 book ai didi

Haskell:减少样板文件

转载 作者:行者123 更新时间:2023-12-05 08:52:47 24 4
gpt4 key购买 nike

在这样的代码中减少重复次数的公认方法是什么?

newtype Fahrenheit = Fahrenheit Double deriving (Eq)
newtype Celsius = Celsius Double deriving (Eq)
newtype Kelvin = Kelvin Double deriving (Eq)
newtype Rankine = Rankine Double deriving (Eq)
newtype Reaumure = Reaumure Double deriving (Eq)
newtype Romer = Romer Double deriving (Eq)
newtype Delisle = Delisle Double deriving (Eq)
newtype Newton = Newton Double deriving (Eq)

instance Show Fahrenheit where
show (Fahrenheit f) = show f ++ " °F"

instance Show Celsius where
show (Celsius c) = show c ++ " °C"

instance Show Kelvin where
show (Kelvin k) = show k ++ " K"

instance Show Rankine where
show (Rankine r) = show r ++ " °R"

instance Show Reaumure where
show (Reaumure r) = show r ++ " °Ré"

instance Show Romer where
show (Romer r) = show r ++ " °Rø"

instance Show Delisle where
show (Delisle d) = show d ++ " °De"

instance Show Newton where
show (Newton n) = show n ++ " N°"

class Temperature a where
increaseTemp :: a -> Double -> a
decreaseTemp :: a -> Double -> a
toFahrenheit :: a -> Fahrenheit
toCelsius :: a -> Celsius
toKelvin :: a -> Kelvin
toRankine :: a -> Rankine
toReaumure :: a -> Reaumure
toRomer :: a -> Romer
toDelisle :: a -> Delisle
toNewton :: a -> Newton

instance Temperature Fahrenheit where
increaseTemp (Fahrenheit f) n = if n < 0 then error "negative val" else Fahrenheit $ f + n
decreaseTemp (Fahrenheit f) n = if n < 0 then error "negative val" else Fahrenheit $ f - n
toFahrenheit = id
toCelsius (Fahrenheit f) = Celsius $ (f - 32) * 5 / 9
toKelvin (Fahrenheit f) = Kelvin $ (f - 32) * 5 / 9 + 273.15
toRankine (Fahrenheit f) = Rankine $ f + 458.67
toReaumure (Fahrenheit f) = Reaumure $ (f - 32) * 4 / 9
toRomer (Fahrenheit f) = Romer $ (f - 32) * 7 / 24 + 7.5
toDelisle (Fahrenheit f) = Delisle $ (212 - f) * 5 / 6
toNewton (Fahrenheit f) = Newton $ (f - 32) * 11 / 60

instance Temperature Celsius where
increaseTemp (Celsius c) n = if n < 0 then error "negative val" else Celsius $ c + n
decreaseTemp (Celsius c) n = if n < 0 then error "negative val" else Celsius $ c - n
toFahrenheit (Celsius c) = Fahrenheit $ c * 9 / 5 + 32
toCelsius = id
toKelvin (Celsius c) = Kelvin $ c + 273.15
toRankine (Celsius c) = Rankine $ c * 9/5 + 491.67
toReaumure (Celsius c) = Reaumure $ c * 4 / 5
toRomer (Celsius c) = Romer $ c * 21 / 40 + 7.5
toDelisle (Celsius c) = Delisle $ (100 - c) * 3 / 2
toNewton (Celsius c) = Newton $ c * 33 / 100

instance Temperature Kelvin where
increaseTemp (Kelvin k) n = if n < 0 then error "negative val" else Kelvin $ k + n
decreaseTemp (Kelvin k) n = if n < 0 then error "negative val" else Kelvin $ k - n
toFahrenheit (Kelvin k) = Fahrenheit $ (k - 273.15) * 9 / 5 + 32
toCelsius (Kelvin k) = Celsius $ k - 273.15
toKelvin = id
toRankine (Kelvin k) = Rankine $ k * 9 / 5
toReaumure (Kelvin k) = Reaumure $ (k - 273.15) * 4 / 5
toRomer (Kelvin k) = Romer $ (k - 273.15) * 21 / 40 + 7.5
toDelisle (Kelvin k) = Delisle $ (373.15 - k) * 3 / 2
toNewton (Kelvin k) = Newton $ (k - 273.15) * 33 / 100

-- rest of the instances omitted.

此外,在 Class 定义中,有一种方法可以将输入变量的类型限制为其中一个 Unit。即 toCelsius::a -> Celsius,是否可以做些什么来限制 a 的值?或者它只适用于声明了实例的类型这一事实暗示了这一点。

最佳答案

主要问题似乎是单位转换,您可以使用 DataKinds 和一堆其他看起来可怕的语言扩展(仅适用于 3 个单位,但您应该能够很容易地概括这一点):

{-# LANGUAGE DataKinds,
KindSignatures,
RankNTypes,
ScopedTypeVariables,
AllowAmbiguousTypes,
TypeApplications #-}
data TemperatureUnit = Fahrenheit | Celsius | Kelvin
newtype Temperature (u :: TemperatureUnit) = Temperature Double deriving Eq

class Unit (u :: TemperatureUnit) where
unit :: TemperatureUnit

instance Unit Fahrenheit where unit = Fahrenheit
instance Unit Celsius where unit = Celsius
instance Unit Kelvin where unit = Kelvin

instance Show TemperatureUnit where
show Celsius = "°C"
show Fahrenheit = "°F"
show Kelvin = "K"

instance forall u. Unit u => Show (Temperature u) where
show (Temperature t) = show t ++ " " ++ show (unit @u)

convertTemperature :: forall u1 u2. (Unit u1, Unit u2) => Temperature u1 -> Temperature u2
convertTemperature (Temperature t) = Temperature . fromKelvin (unit @u2) $ toKelvin (unit @u1) where
toKelvin Celsius = t + 273.15
toKelvin Kelvin = t
toKelvin Fahrenheit = (t - 32) * 5/9 + 273.15
fromKelvin Celsius k = k - 273.15
fromKelvin Kelvin k = k
fromKelvin Fahrenheit k = (k - 273.15) * 9/5 + 32

然后你可以像这样使用它:

-- the explicit type signatures here are only there to resolve
-- ambiguities; In more realistic code you'd not need them as often
main = do
let (t1 :: Temperature Celsius) = Temperature 10.0
(t2 :: Temperature Fahrenheit) = Temperature 10.0
putStrLn $ show t1 ++ " = " ++ show (convertTemperature t1 :: Temperature Fahrenheit)
-- => 10.0 °C = 50.0 °F
putStrLn $ show t2 ++ " = " ++ show (convertTemperature t2 :: Temperature Celsius)
-- => 10.0 °F = -12.222222222222221 °C

Try it online!

这里的技巧是 DataKinds 允许我们将常规数据类型提升到类型级别,并将它们的数据构造器提升到类型级别(我理解这在现代版本中并没有什么不同) GHC 了?抱歉,我自己对这个问题有点犹豫)。然后我们只定义一个辅助类来获取单元的数据版本,这样我们就可以基于它进行调度。这使我们能够像您尝试使用所有新类型包装器那样做,除了更少的新类型包装器(以及更少的实例声明和更少的命名函数)。

当然,另一件事是不同单位转换之间的组合爆炸式增长 - 您可以接受并手动编写所有 n^2 公式,或者您可以尝试概括它(根据@chepner 的评论,温度单位可能是可能的,但我不确定你可能想要在它们之间转换的各种事物是否可能)。这种方法无法解决该固有问题,但它确实消除了您使用 newtype-per-unit 方法产生的一些句法噪音。

您的 increaseTempdecreaseTemp 函数可以作为单个函数 offsetTemperature 实现,同时允许负数。尽管我认为让它们采用与第二个参数相同的单位而不只是 Double 更有意义:

offsetTemperature :: Temperature u -> Temperature u -> Temperature u
offsetTemperature (Temperature t) (Temperature offset) = Temperature (t + offset)

PS:Temperature 可能Eq 的一个实例 - 浮点相等性是出了名的不稳定(可预测,但可能不会做你想做的事)。我只是把它放在这里,因为它在你的例子中。

关于Haskell:减少样板文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55924593/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com