TimeDuration类型可以是Semigroup和Monoid的实例吗?

时间:2018-06-23 21:01:12

标签: haskell

为了找到一种快速简便的方法来添加不同的时间段,我想到可以将它们声明为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

3 个答案:

答案 0 :(得分:3)

是的,很好。基本上等于

{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}

import Data.AdditiveGroup
import GHC.Generics

newtype TimeDuration = TimeDuration {getDurationInSeconds :: Integer}
  deriving (Generic, AdditiveGroup)

...带有用于特殊情况TimeDuration 60TimeDuration 180 ... TimeDuration 3600等的额外构造函数(它们在您的构造中实际上是多余的;您最好将简单的构造器-功能可以完成相同的工作)。

AdditiveGroup typeclass是一个特殊的monoid类型类,它避免了可能是 multiplicat monoid 的任何混淆–实际上并没有任何意义,因为在维度方面, time × time time 不匹配,但是对于像IntDouble这样的无量纲数字类型,乘法等分线只是和加法一样明智。

由于这些实例仍然具有明确的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 2S 7200在道德上是相同的,并且它们之间的区别仅是表示问题(参见show . read的严格意义上讲, id,因为它可以规范格式)。那么,如果不打算将HM用于任何相关的区分(请参阅其他答案),那么问题就变成了。

答案 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年呢?时间不总是一秒,时间就很难。