函数模式匹配取决于数据参数

时间:2016-07-20 18:13:14

标签: haskell

首先:抱歉这个暧昧的问题标题...... 第二:我是完整的初学者......

我有一个非常简单的数据类型叫做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

2 个答案:

答案 0 :(得分:4)

你真的不需要明确的弃牌。您只需要mapsum的组合。

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