为了找到一种快速简便的方法来添加不同的时间段,我想到可以将它们声明为Semigroup和Monoid的实例。以下实例是否有效?
data TimeDuration = S Int | M Int | H Int deriving (Show, Eq)
instance Semigroup TimeDuration where
S n1 <> S n2 = S (n1 + n2)
M n1 <> M n2 = S (60 * (n1 + n2))
H n1 <> H n2 = S (3600 * (n1 + n2))
S n1 <> M n2 = S (n1 + 60 * n2)
S n1 <> H n2 = S (n1 + 3600 * n2)
M n1 <> S n2 = S (60 * n1 + n2)
M n1 <> H n2 = S (60 * n1 + 3600 * n2)
H n1 <> S n2 = S (3600 * n1 + n2)
H n1 <> M n2 = S (3600 * n1 + 60 * n2)
instance Monoid TimeDuration where
mempty = S 0
示例:mconcat [S 1,M 2,M 3,S 2,H 1] == S 3903
用户 leftaroundabout 要求我提供更有意义的示例。因此,这是一个新的实现,我希望通过一些示例可以更好地显示操作结果(<>)
的各种可能instance Semigroup TimeDuration where
S n1 <> S n2 = S (n1 + n2)
M n1 <> M n2 = M (n1 + n2)
H n1 <> H n2 = H (n1 + n2)
S n1 <> M n2 = S (n1 + 60 * n2)
M n1 <> S n2 = S n2 <> M n1
S n1 <> H n2 = S (n1 + 3600 * n2)
H n1 <> S n2 = S n2 <> H n1
M n1 <> H n2 = M (n1 + 60 * n2)
H n1 <> M n2 = M n1 <> H n1
instance Monoid TimeDuration where
mempty = S 0
mconcat [] = S 0
mconcat xs = foldr1 (\y acc -> y <> acc) xs
-- ex. mconcat [M 2, M 3] == M 5
-- ex. mconcat [H 2, H 3] == H 5
-- ex. mconcat [M 2, M 3, S 1] == S 301
-- ex. mconcat [H 2, H 3, M 1] == M 301
-- ex. mconcat [H 2, H 3, S 1] == S 18001
答案 0 :(得分:3)
是的,很好。基本上等于
{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}
import Data.AdditiveGroup
import GHC.Generics
newtype TimeDuration = TimeDuration {getDurationInSeconds :: Integer}
deriving (Generic, AdditiveGroup)
...带有用于特殊情况TimeDuration 60
,TimeDuration 180
... TimeDuration 3600
等的额外构造函数(它们在您的构造中实际上是多余的;您最好将简单的构造器-功能可以完成相同的工作)。
AdditiveGroup
typeclass是一个特殊的monoid类型类,它避免了可能是 multiplicat monoid 的任何混淆–实际上并没有任何意义,因为在维度方面, time × time 与 time 不匹配,但是对于像Int
或Double
这样的无量纲数字类型,乘法等分线只是和加法一样明智。
由于这些实例仍然具有明确的AdditiveGroup
实例,因此只能导出AdditiveGroup TimeDuration
实例。也可以用手写的方式
instance AdditiveGroup TimeDuration where
zeroV = TimeDuration 0
TimeDuration δt₀ ^+^ TimeDuration δt₁ = TimeDuration $ δt₀ + δt₁
关于AdditiveGroup
的一件特别有趣的事:它是VectorSpace
的前身,它为您提供了确实有意义的乘法,即
instance VectorSpace TimeDuration where
type Scalar TimeDuration = Integer
factor *^ TimeDuration δt = TimeDuration $ factor * δt
答案 1 :(得分:2)
如果我们要为具有法律的类编写实例,则通常应首先进行健全性检查,即实例是否紧随其后。对于Semigroup
,有关联律...
(x <> y) <> z = x <> (y <> z)
...,而Monoid
添加了身份法则:
mempty <> x = x
x <> mempty = x
您的实例只要遵循S
值,它就会遵循法律(归结为加秒)。但是,一旦其他构造函数发挥作用,身份定律就会被破坏,例如:
mempty <> H 2 = S 7200
我们可能会争辩说H 2
和S 7200
在道德上是相同的,并且它们之间的区别仅是表示问题(参见show . read
的严格意义上讲, id
,因为它可以规范格式)。那么,如果不打算将H
和M
用于任何相关的区分(请参阅其他答案),那么问题就变成了。
答案 2 :(得分:1)
这看起来还可以,但为什么不这样做呢?
newtype TimeDuration = Seconds (Sum Integer) deriving (Show, Eq, Monoid, Semigroup)
我认为您需要扩展以通过新类型派生半群和monoid,并且需要从Sum
导入Data.Foldable
您可以使用其余功能:
seconds = Seconds
minutes = seconds . (*60)
hours = minutes . (*60)
您可能想要的另一件事是:
data TimDuration = Duration { seconds :: Int, minutes :: Int, hours :: Integer }
并定义一个函数对此进行标准化。但是,有一个问题要证明,实际上增加持续时间和时间很困难:增加一分钟与增加60秒是否一样?有人会这样想,但是如果说时间是23:59:00且最后一分钟有a秒呢?在这种情况下,如果您增加60秒,则会到达23:59:60。如果您增加一分钟,第二天您应该获得23:59:60或00:00:00吗?在这种情况下,我认为说一分钟是60秒是明智的。但是一天或一个月要花多长时间?如果在时钟更改的前一天的12:00之前增加一天,会发生什么情况。您应该第二天12:00还是13:00/11:00?如果在1月29日增加一个月,是2月28日还是3月1日?那leap年呢?时间不总是一秒,时间就很难。