Unwrap a single case union 'in place' - f#

I've got this beautiful piece of F# code, mapping a Pizza to a PizzaModel:
type Cheese = Cheese of string
type Pizza
{
Name: string
Cheese: Cheese
}
type PizzaModel =
{
PizzaName : string
Cheese: string
Toppings: string
}
let mapPizza pizza =
let (Cheese c) = pizza.Cheese
{ PizzaName = pizza.Name; Cheese = c}
Is it possible to write this different, can I unwrap Cheese in place?

It took me some time to figure out what your type definitions might be. I got your code to compile with the following, so I'm assuming that's what you have:
type Cheese = Cheese of bool
type CheesePizza = { PizzaName : string; Cheese : bool }
type Pizza = { Name : string; Cheese : Cheese}
I don't think there is a way to unwrap the cheese inline on the last line of your function, but you can unwrap both the name and the cheese in a pattern on the first line:
let mapPizza { Cheese = Cheese c; Name = name } =
{ PizzaName = name; Cheese = c}
This pattern matches on the argument and extracts the cheese using a nested pattern.
EDIT Another approach would be to modify the Cheese type and add a member that lets you easily access the wrapped value. This can be done quite easily:
type Cheese =
| Cheese of string
member x.Value = let (Cheese v) = x in v
Now you can unwrap the value using pizza.Cheese.Value inline:
let mapPizza pizza =
{ PizzaName = pizza.Name; Cheese = pizza.Cheese.Value }

Related

How to model an entity with a currency?

I am trying to model a bond entity in F# using a unit of measure for the currency.
type Bond = {
Isin: string
Issuer: string
Maturity: DateTime
Price: float<???>
}
Let's assume that a static table with all the available/possible currencies is available.
type Currency = {
Code : string
Name : string
}
I can go for Price as float and PriceCurrency as string or even as a Currency type but I think that this is not ideal.
Any ideas?
I don't think F# units of measure are a good match for this use case, since the compiler isn't aware of the currency table. If you want to use units of measure anyway, each currency would have to be hard-coded into your source, like this:
open System
[<Measure>] type Dollar
[<Measure>] type Pound
type Bond =
{
Isin: string
Issuer: string
Maturity: DateTime
}
type DollarBond =
{
Bond: Bond
Price: float<Dollar>
}
type PoundBond =
{
Bond: Bond
Price: float<Pound>
}
let poundsPerDollar = 0.73<Pound/Dollar>
let toPoundBond (dollarBond : DollarBond) =
{
Bond = dollarBond.Bond
Price = dollarBond.Price * poundsPerDollar
}
let dollarBond : DollarBond =
{
Bond = {
Isin = "My isin"
Issuer = "My issuer"
Maturity = DateTime.Parse("1/1/2050")
}
Price = 1000.0<Dollar>
}
printfn "%A" <| toPoundBond dollarBond

ToString() override in F# doesn't work for a type containing another type

I have a type 'Team' which contains another type 'Employee'. I have overridden the ToString() for the type 'Employee'. However, when I do ToString() for the type 'Team', the details from 'Employee' is pretty-printed with the standard ToString() implementation and my overriding logic was never used. Can someone help understand why the override didn't work? Here is the code:
type Employee =
{
name : string
address : string
}
override this.ToString() = sprintf "Hello %s" this.name
type Team =
{
employee1 : Employee
}
with member this.ToTightString =
this.ToString().Replace(" ","")
let employee = { name="Bob"; address="Unknown"; }
let team = {employee1=employee}
printfn "%s" (employee.ToString()) // Override works!
// OUTPUT: Hello Bob
printfn "--------------------"
printf "%s" team.ToTightString // Override doesn't work
// OUTPUT: {employee1={name="Bob";address="Unknown";};}
As #rmunn has said above, the textual representation of a type (say, type1) specified in StructuredFormatDisplay is retained even if one calls ToString() on a type that contains the 'type1' type. Here's an example:
open System.Text.RegularExpressions
[<StructuredFormatDisplay("name=Always Harry address={address}")>]
type Employee =
{
name : string
address : string
}
type AddressContainer =
{
employee: Employee
containerName: string
}
let address1 = { name="Bob"; address="Random City" }
let addressContainer1 = { employee=address1; containerName= "container1"}
printf "%s" (address1.ToString()) // prints "name=Always Harry address=Random City"
printf "%s" (addressContainer1.ToString()) // prints {employee = name=Always Harry address=Random City; containerName = "container1";}

How to deconstruct union with "with"?

I have some problems to use "with" with a discriminated union:
type NaturalPerson = {
FirstName: string
LastName: string
}
type CorporateEntity = {
Name1: string
Name2: string option
}
type Person =
| Natural of NaturalPerson
| Company of CorporateEntity
let company = Company { Name1 = "Foo Bar AG"; Name2 = Some "Baz" }
Now I want to change Name2 to None, but I could not figure out how.
Something like:
let company2 = Company { company with Name2 = None }
In my "real world example" of course this is nested, otherwise I could use the correct type.
Maybe this isn't possible, because I have to pattern match for an edge case, that can not exist (but the compiler is not smart enough to know).
If you break it out a bit more it is easier to see the problem. In fact what is probably making this difficult is the naming.
let company = Company { Name1 = "Foo Bar AG"; Name2 = Some "Baz" } // Person
let company2 = Company { company with Name2 = None } // Person, but broken because expecting company to be type of CorporateEntity
So you are trying to create a CorporateEntity with a Person type, which are not the same.
This works because the correct type is used.
let c1 : CorporateEntity = { Name1 = "Foo Bar AG"; Name2 = Some "Baz" }
let p1 : Person = Company c1
let c2 : CorporateEntity = { c1 with Name2 = None }
let p2 : Person = Company c2
I have added the types and changed the name to make the type more apparent.
You could match on this...
match company with // <- rename company to person so it is clearer
| Natural _ -> company
| Company c -> Company { c with Name2 = None }
If you wanted to match in a function you could do it like this:
let noCompanyName2 (c:CorporateEntity) = // c:CorporateEntity -> Person
let { Name1 = n1; Name2 = _ } = c
let company3 = Company { Name1 = n1; Name2 = None }
company3
Or more concisely:
let noCompanyName2 ({ Name1 = n1; Name2 = _ }) = Company { Name1 = n1; Name2 = None }
Hope this helps.
Here's how you would do it (I'm assuming that if the variable company is a NaturalPerson then you want it unchanged):
match company with
| Person _ -> company
| Company corpEntity -> Company { corpEntity with Name2 = None }

Computation Expression for constructing complex object graph

Given the following types:
type Trip = {
From: string
To: string
}
type Passenger = {
Name: string
LastName: string
Trips: Trip list
}
I'm using the following builders:
type PassengerBuilder() =
member this.Yield(_) = Passenger.Empty
[<CustomOperation("lastName")>]
member __.LastName(r: Passenger, lastName: string) =
{ r with LastName = lastName }
[<CustomOperation("name")>]
member __.Name(r: Passenger, name: string) =
{ r with Name = name }
type TripBuilder() =
member __.Yield(_) = Trip.Empty
[<CustomOperation("from")>]
member __.From(t: Trip, f: string) =
{ t with From = f }
// ... and so on
to create records of type Passenger, like so:
let passenger = PassengerBuilder()
let trip = TripBuilder()
let p = passenger {
name "john"
lastName "doe"
}
let t = trip {
from "Buenos Aires"
to "Madrid"
}
how would I go about combining the PassengerBuilder and the TripBuilder so that I can achieve this usage?
let p = passenger {
name "John"
lastName "Doe"
trip from "Buenos Aires" to "Madrid"
trip from "Madrid" to "Paris"
}
that returns a Passenger record like:
{
LastName = "Doe"
Name = "John"
Trips = [
{ From = "Buenos Aires"; To = "Madrid" }
{ From = "Madrid"; To = "Paris" }
]
}
Is there any reason why you want to use computation expression builder? Based on your example, it does not look like you're writing anything computation-like. If you just want a nice DSL for creating trips, then you could quite easily define something that lets you write:
let p =
passenger [
name "John"
lastName "Doe"
trip from "Buenos Aires" towards "Madrid"
trip from "Madrid" towards "Paris"
]
This is pretty much exactly what you asked for, except that it uses [ .. ] instead of { .. } (because it creates a list of transformations). I also renamed to to towards because to is a keyword and you cannot redefine it.
The code for this is quite easy to write and follow:
let passenger ops =
ops |> List.fold (fun ps op -> op ps)
{ Name = ""; LastName = ""; Trips = [] }
let trip op1 arg1 op2 arg2 ps =
let trip =
[op1 arg1; op2 arg2] |> List.fold (fun tr op -> op tr)
{ From = ""; To = "" }
{ ps with Trips = trip :: ps.Trips }
let name n ps = { ps with Name = n }
let lastName n ps = { ps with LastName = n }
let from n tp = { tp with From = n }
let towards n tp = { tp with To = n }
That said, I would still consider using normal F# record syntax - it is not that much uglier than this. The one drawback of the version above is that you can create passengers with empty names and last names, which is one thing that F# prevents you from!
I'm not sure this is what you wanted, but nothing prevents you from creating a new operation called trip on your PassengerBuilder:
[<CustomOperation("trip")>]
member __.Trip(r: Passenger, t: Trip) =
{ r with Trips = t :: r.Trips }
and then using it like this:
let p = passenger {
name "John"
lastName "Doe"
trip (trip { from "Buenos Aires"; to "Madrid" })
trip (trip { from "Madrid"; to "Paris" })
}
Arguably, you can even make it cleaner by dropping the TripBuilder altogether:
let p = passenger {
name "John"
lastName "Doe"
trip { From = "Buenos Aires"; To = "Madrid" }
trip { From = "Madrid"; To = "Paris" }
}
If this is somehow not what you wanted, then please specify how. That is, what is missing or what is extra in this solution.

How to categorize over units of measure?

The problem is simple, I wish to do some calculations on some travel expenses which include both expenses in DKK and JPY. Thus I've found a nice way to model currency so I am able to convert back and forth:
[<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 }
I proceed to model expenses as a record type
type Expense<[<Measure>] 'a> = {
name: string
quantity: int
amount: decimal<'a>
}
and here I have an example list of expenses:
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> }
]
And this is where the show stops... F# doesn't like that list, and complaints that all of the list should be DKK, -which of course makes sense.
Then I thought that there must be some smart way to make a discriminated union of my units of measures to put them in a category, and then I attempted with:
[<Measure>] type Currency = JPY | DKK
But this is not possible and results in The kind of the type specified by its attributes does not match the kind implied by its definition.
The solution I've come up with so far is very redundant, and I feel that it makes the unit of measure quite pointless.
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>) }
]
Is there a good way of working with these units of measures as categories? like for example
[<Measure>] Length = Meter | Feet
[<Measure>] Currency = JPY | DKK | USD
or should I remodel my problem and maybe not use units of measure?
Regarding the first question no, you can't but I think you don't need units of measures for that problem as you state in your second question.
Think how do you plan to get those records at runtime (user input, from a db, from a file, ...) and remember units of measures are a compile-time features, erased at runtime. Unless those records are always hardcoded, which will make your program useless.
My feeling is that you need to deal at run-time with those currencies and makes more sense to treat them as data.
Try for instance adding a field to Expense called currency:
type Expense = {
name: string
quantity: int
amount: decimal
currency: Currency
}
then
type CurrencyRate = {
currencyFrom: Currency
currencyTo: Currency
rate: decimal
date: System.DateTime}
As an alternative to Gustavo's accepted answer, If you still want to prevent anybody and any function accidentally summing JPY with DKK amounts, you can keep your idea of discriminated union like so :
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
Even better would be to make function IN_DKK as a member of type Expense and put a restriction (private,...) on the field "amount".
Your initial idea of units of measure makes sense to prevent summing different currencies but unfortunately it does not prevent from converting from one to another and back to the first currency. And since your rates are not inverse (r * r' <> 1 as your data shows), unit of measure for currencies are dangerous and error prone. Note : I did not take into account the field "quantity" in my snippet.

Resources