2016-07-20 4 views
0

Zunächst: sorry für die mehrdeutig Frage Titel ... Zweitens: Ich bin ein komplette Anfänger ...Funktionsmuster Spiel auf Daten Argument abhängig

Ich habe einen sehr einfachen Datentyp namens Aufgabe. Eine Aufgabe hatte einen Preis, eine Menge und eine Einheit. Die Einheit kann Tage oder Stunden umfassen.

Ich möchte den Gesamtbetrag und Gesamtpreis für eine Liste von Aufgaben berechnen. Für den Preis gibt es kein Problem, ich multipliziere jeden Preis mit dem Betrag für die gegebene Aufgabe und es summiert sich. Aber für die Menge bin ich fest.

Ich kann nicht einfach eine Liste von Stunden und Tagen falten. Sagen wir, ich habe eine Aufgabe mit 1 Stunde und eine mit 1 Tag, beide haben eine Menge von 1, aber die Summe sollte in einer Einheit sein. Ich habe 'Stunde' als Basiseinheit gewählt.

So zu rekapitulieren, das ist meine Art:

-- 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 

Jede Aufgabe kennt seine eigene Art, sondern ein Schaltergehäuse innerhalb des Musterabgleich ist nicht die Art, wie ich denke, zu gehen ... In einigen naiver Pseudo-Code würde ich schreiben:

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) 

ich weiß dies falsch ist, aber ich weiß nicht, wie es zu tun. Ich könnte laufen, bevor ich hier laufen kann, aber es scheint so eine einfache Aufgabe (kein Wortspiel beabsichtigt).

Danke!

== == UPDATE

fand ich einen Weg! Aber wenn das nicht der richtige Weg ist, würde ich mich freuen zu hören, wie ich mich verbessern kann! Außerdem suche ich noch die Stunden und Tage der Strings zu einem konkreten Kunden-Typ zu analysieren Tag von 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

Sind Sie mit [sum types] (https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/sum-types)? –

+0

Nein, ich bereite gerade die Dokumentation vor, danke! Ich könnte es mit dem Update oben und auch mit dem Vorschlag unten arbeiten lassen. –

+0

Vielleicht wäre diese Frage besser für [Code Review] (http://codereview.stackexchange.com/) geeignet? –

Antwort

4

Sie brauchen hier keine explizite Abkürzung. Sie brauchen nur eine Kombination von map und 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) 
+0

Vielen Dank! Das macht den Code schon viel einfacher zu lesen! –

+0

Die Klammern auf der rechten Seite Ihrer Funktionen sind unnötig, BTW. Zum Beispiel "taskPrice t = Preis t * inHour t" –

+0

Ich neige dazu, Klammern zu verwenden, meistens aus Gewohnheit, wenn ich mich nicht mehr genau erinnern konnte, wie Currying und Funktionsanwendung mit Infix-Operatoren interagierten. – chepner

1

Sie haben die Behandlung von Zeit als weitgehend implizite Teil des Datenmodells verlassen. Ich würde Zeitspannen als in sich geschlossenen abstrakten Datentyp modellieren, mit Funktionen zum Berechnen neuer Zeitspannen und zum Projizieren verschiedener Komponenten der Zeitspanne. Dies ermöglicht Ihnen, Berechnungen in einer kanonischen Einheit Ihrer Wahl (ich habe "Ticks" gewählt) in einer Weise durchzuführen, die für Code undurchsichtig ist, der den Typ TimeSpan verwendet.

-- 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 

Dies ist nur, wie man es in einer objektorientierten Sprache tun würde. C#'s TimeSpan ist ein struct (nicht auf wie schlecht C# Strukturen bei Datenabstraktion sind), die intern eine private Anzahl von Ticks mit öffentlichen Methoden, um eine TimeSpan in verschiedene andere Messschemata wie Stunden oder Tage zu konvertieren beginnt. Es ist auch mehr oder weniger what you'll find in Haskell's Data.Time.Clock Modul.

Jetzt können Sie den abstrakten TimeSpan-Typ direkt in Ihrem Code verwenden und an Ihre TimeSpan-Bibliothek delegieren, um das Hinzufügen und Subtrahieren in Einheiten unabhängig durchzuführen.

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 
+0

Vielen Dank für Ihre Antwort! Dies ist ein bisschen viel für mich zu der Zeit zu erfassen, aber ich werde es im Kopf behalten, während in der Haskell Denkweise/Syntax wachsen –