首先:抱歉这个暧昧的问题标题...... 第二:我是完整的初学者......
我有一个非常简单的数据类型叫做Task。任务有价格,数量和单位。单位可以是几天或几小时。
我想计算任务列表的总金额和总价。对于价格没有问题,我将每个价格乘以给定任务的金额,然后加起来。但是我被卡住了。
我无法轻松折叠小时和日期列表。假设我有1小时和1天的任务,两个都有1个,但总数应该在一个单位。我选择了'小时'作为基本单位。
所以回顾一下,这是我的类型:
-- data --
-- the task is parsed from json, but I would like the `unit` type to be
-- custom like `Hour | Day` instead of String. But that is an aeson issue?
data Task = {
amount :: Double,
price :: Double,
unit :: String
}
-- functions --
taskPrice :: Task -> Double
taskPrice t = fromIntegral (price t) * amount t
calcPrice :: [Task] -> Double
calcPrice [] = 0
calcPrice [x] = taskPrice x
calcPrice (x:xs) = foldl (\acc x -> acc + (taskPrice x)) (taskPrice x) xs
-- this version will add days and hours like they are equal...
calcInvoiceHours :: [Task] -> Double
calcInvoiceHours [] = 0
calcInvoiceHours [x] = amount x
calcInvoiceHours (x:xs) = foldl (\acc x -> acc + (amount x)) (amount x) xs
每个任务知道它自己的类型,但模式匹配中的切换案例不是我想的方式...在一些天真的伪代码中我会写:< / p>
calcInvoiceHours :: [Task] -> Double
calcInvoiceHours [] = 0
calcInvoiceHours [x -> unit x === Hour] = amount x
calcInvoiceHours [x -> unit x === Day] = (amount x) * 8 -- work 8 hours a day
calcInvoiceHours (x:xs) = foldl (\acc x -> acc + (amount x)) (amount x)
我知道这是错误的,但我不知道该怎么做。在我走到这里之前,我可能正在跑步,但这似乎是一项非常简单的任务(没有双关语)。
谢谢!
==更新==
我找到了办法!但如果这不是好方法,我会很高兴听到我如何改进!另外我还在寻找解析字符串的时间&#39;小时&#39;和&#39;天&#39;一个具体的客户类型小时|来自json的一天。
taskHours :: Task -> Double
taskHours (Task _ amount _ unit)
| unit == "hours" = amount
| otherwise = amount * 8
calcInvoiceHours :: [Task] -> Double
calcInvoiceHours [] = 0
calcInvoiceHours [x] = taskHours x
calcInvoiceHours (x:xs) = foldl (\acc x -> acc + (taskHours x)) (taskHours x) xs
答案 0 :(得分:4)
你真的不需要明确的弃牌。您只需要map
和sum
的组合。
inHours :: Task -> Double
inHours (Task amt _ "hours") = amt
inHours (Task amt _ "days") = 8 * amt
taskPrice :: Task -> Double
taskPrice t = (price t) * (inHours t)
calcPrice :: [Task] -> Double
calcPrice = sum . (map taskPrice)
calcInvoiceHours :: [Task] -> Double
calcInvoiceHours = sum . (map inHours)
答案 1 :(得分:1)
您已将 time 的处理作为数据模型的主要隐含部分。我将timepans建模为一个独立的抽象数据类型,具有计算新时间跨度和投影时间跨度的各种组件的功能。这允许您以对您使用TimeSpan
类型的代码不透明的方式在您选择的规范单元(我选择“刻度”)中执行计算。
-- Make the type abstract, so users can't directly manipulate ticks,
-- by keeping the TimeSpan constructor private to this module
newtype TimeSpan = TimeSpan Integer
-- you could pack these two into a Monoid instance if you like, or use GeneralizedNewtypeDeriving and get it without writing any code
zero :: TimeSpan
zero = TimeSpan 0
plus :: TimeSpan -> TimeSpan -> TimeSpan
plus (TimeSpan x) (TimeSpan y) = TimeSpan (x + y)
ticksPerHour :: Fractional a => a
ticksPerHour = 10000 * 1000 * 60 * 60 -- 10,000 ticks per millisecond
fromHours :: Float -> TimeSpan
fromHours = TimeSpan . hoursToTicks
where hoursToTicks x = round (x * ticksPerHour)
toHours :: TimeSpan -> Float
toHours (TimeSpan x) = ticksToHours x
where ticksToHours x = fromIntegral x / ticksPerHour
-- similar functions to measure the timespan in days, etc
这就是你在面向对象语言中的表现。 C#'s TimeSpan
是一个struct
(不要让我开始讨论C#结构在数据抽象方面有多糟糕),内部计算一个私有数量的刻度,并使用公共方法进行转换TimeSpan
进入各种其他测量方案,如小时或天。它也是或多或少what you'll find in Haskell's Data.Time.Clock
模块。
现在,您可以直接在代码中使用抽象TimeSpan
类型,委托给TimeSpan
库,以独立于单位的方式进行添加和减法。
data Task = Task {
taskLength :: TimeSpan,
taskPrice :: Money -- omitted: a similar treatment to build an abstract Money type
}
totalTime :: [Task] -> TimeSpan
totalTime = foldl' plus zero . map taskLength
totalTimeHours :: [Task] -> Float
totalTimeHours = toHours . totalTime