2015-09-10 19 views
9

Das Problem ist einfach, ich möchte einige Reisekosten berechnen, die beide Ausgaben in DKK und JPY enthalten. So habe ich eine nette Art und Weise gefunden Währung zu modellieren, damit ich bin in der Lage zu konvertieren hin und her:Wie kategorisieren Sie über Maßeinheiten?

[<Measure>] type JPY 
[<Measure>] type DKK 
type CurrencyRate<[<Measure>]'u, [<Measure>]'v> = 
    { Rate: decimal<'u/'v>; Date: System.DateTime} 

let sep10 = System.DateTime(2015,9,10) 
let DKK_TO_JPY : CurrencyRate<JPY,DKK> = 
    { Rate = (1773.65m<JPY>/100m<DKK>); Date = sep10} 
let JPY_TO_DKK : CurrencyRate<DKK,JPY> = 
    { Rate = (5.36m<DKK>/100.0m<JPY>); Date=sep10 } 

Ich gehe Kosten als Satzart

type Expense<[<Measure>] 'a> = { 
    name: string 
    quantity: int 
    amount: decimal<'a> 
} 

und ich hier ein Beispiel haben zu modellieren Liste der Ausgaben:

let travel_expenses = [ 
    { name = "flight tickets"; quantity = 1; amount = 5000m<DKK> } 
    { name = "shinkansen ->"; quantity = 1; amount = 10000m<JPY> } 
    { name = "shinkansen <-"; quantity = 1; amount = 10000m<JPY> } 
] 

und das ist, wo die Show aufhört ... F # nicht, dass die Liste nicht mag, und Beschwerden, die alle von der Liste DKK sein sollten, macht Sinn -Welche natürlich.

Dann dachte ich, dass es muss eine intelligente Art und Weise sein, eine diskriminierte Vereinigung meiner Einheiten von Maßnahmen, um sie in einer Kategorie zu setzen, und dann mit ich versuchte:

[<Measure>] type Currency = JPY | DKK 

Aber dies ist nicht möglich, und Ergebnisse in The kind of the type specified by its attributes does not match the kind implied by its definition.

Die Lösung, die ich bisher gefunden habe, ist sehr überflüssig, und ich denke, dass es die Maßeinheit ziemlich sinnlos macht.

type Money = 
    | DKK of decimal<DKK> 
    | JPY of decimal<JPY> 
type Expense = { 
    name: string 
    quantity: int 
    amount: Money 
} 
let travel_expenses = [ 
    { name = "flight tickets"; quantity = 1; amount = DKK(5000m<DKK>) } 
    { name = "shinkansen ->"; quantity = 1; amount = JPY(10000m<JPY>) } 
    { name = "shinkansen <-"; quantity = 1; amount = JPY(10000m<JPY>) } 
] 

Gibt es eine gute Möglichkeit, mit diesen Einheiten von Maßnahmen als Kategorien zu arbeiten? wie zum Beispiel

[<Measure>] Length = Meter | Feet 
[<Measure>] Currency = JPY | DKK | USD 

oder soll ich mein Problem umbauen und vielleicht nicht Maßeinheiten benutzen?

+1

zuerst nein: es gibt keine Möglichkeit, das letzte zu tun - aber was Sie tun sollten, ist eine Basiswährung zu wählen und nur die Liste von diesem Typ zu machen - einfach vor dem Hinzufügen konvertieren – Carsten

Antwort

7

In Bezug auf die erste Frage, nein, Sie können nicht, aber ich denke, Sie brauchen keine Maßeinheiten für dieses Problem, wie Sie in Ihrer zweiten Frage angeben.

Denken Sie, wie Sie planen, diese Datensätze zur Laufzeit (Benutzereingabe, von einer Datenbank, aus einer Datei, ...) zu erhalten, und erinnern Maßeinheiten sind eine Kompilierzeit Features, zur Laufzeit gelöscht. Es sei denn, diese Aufzeichnungen sind immer fest codiert, was Ihr Programm nutzlos macht.

Mein Gefühl ist, dass Sie zur Laufzeit mit diesen Währungen handeln müssen und es sinnvoller ist, sie als Daten zu behandeln.

Versuchen zum Beispiel ein Feld Expense Hinzufügen Währung genannt:

type Expense = { 
    name: string 
    quantity: int 
    amount: decimal 
    currency: Currency 
} 

dann

type CurrencyRate = { 
    currencyFrom: Currency 
    currencyTo: Currency 
    rate: decimal 
    date: System.DateTime} 
1

Als Alternative zu Gustavos akzeptierte Antwort, wenn Sie noch jemand verhindern wollen und jede Funktion versehentlich Summieren JPY mit DKK-Beträgen, können Sie Ihre Idee der diskriminierten Vereinigung wie folgt halten:

let sep10 = System.DateTime(2015,9,10) 

type Money = 
    | DKK of decimal 
    | JPY of decimal 

type Expense = { 
    name: string 
    quantity: int 
    amount: Money 
    date : System.DateTime 
} 

type RatesTime = { JPY_TO_DKK : decimal ; DKK_TO_JPY : decimal ; Date : System.DateTime} 

let rates_sep10Tosep12 = [ 
    { JPY_TO_DKK = 1773.65m ; DKK_TO_JPY = 5.36m ; Date = sep10} 
    { JPY_TO_DKK = 1779.42m ; DKK_TO_JPY = 5.31m ; Date = sep10.AddDays(1.0)} 
    { JPY_TO_DKK = 1776.07m ; DKK_TO_JPY = 5.33m ; Date = sep10.AddDays(2.0)} 
    ] 

let travel_expenses = [ 
    { name = "flight tickets"; quantity = 1; amount = DKK 5000m; date =sep10 } 
    { name = "shinkansen ->"; quantity = 1; amount = JPY 10000m; date = sep10.AddDays(1.0)} 
    { name = "shinkansen <-"; quantity = 1; amount = JPY 10000m ; date = sep10.AddDays(2.0)} 
] 

let IN_DKK (rt : RatesTime list) (e : Expense) = 
    let {name= _ ;quantity = _ ;amount = a ;date = d} = e 
    match a with 
    |DKK x -> x 
    |JPY y -> 
     let rtOfDate = List.tryFind (fun (x:RatesTime) -> x.Date = d) rt 
     match rtOfDate with 
     | Some r -> y * r.JPY_TO_DKK 
     | None -> failwith "no rate for period %A" d 

let total_expenses_IN_DKK = 
    travel_expenses 
    |> List.fold(fun acc e -> (IN_DKK rates_sep10Tosep12 e) + acc) 0m 

Noch besser wäre es, die Funktion IN_DKK als Mitglied vom Typ Expense zu machen und eine Einschränkung (privat, ...) auf das Feld "Betrag" zu setzen. Ihre ursprüngliche Idee von Maßeinheiten ist sinnvoll, um die Summierung verschiedener Währungen zu verhindern, aber leider verhindert sie nicht die Konvertierung von einem zum anderen und zurück zur ersten Währung.Und da Ihre Raten nicht invers sind (r * r '<> 1, wie Ihre Daten zeigen), ist die Maßeinheit für Währungen gefährlich und fehleranfällig. Hinweis: Ich habe das Feld "Quantity" in meinem Snippet nicht berücksichtigt.

Verwandte Themen