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.
Related
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 }
I'm trying to filter and array of people and get the results in a specific order. Which mean when user enter someString, I want to present first the people that that their name match that someString, and then start with that someString and after that any name that contain that some string.
Any way I can do it with Array "Filter" or I must preform another manual sort after that?
Here's my filtering code:
self.filteredNames = self.names.filter({$0.name.lowercaseString.hasPrefix(text!) | $0.name.lowercaseString.containsString(text!) | $0.allEmails.lowercaseString.containsString(text!.lowercaseString) | $0.allNumbers.lowercaseString.containsString(text!.lowercaseString)})
filter can only remove items from a collection. It can't do any ordering operations for you.
You probably want to solve this using a single pass of the array so it will perform decently. Don't use filter and instead do something basic like this:
var query = "test"
let allStrings = ["another test", "test", "testing", "random"]
var matchStrings = [String]()
var prefixStrings = [String]()
var containsStrings = [String]()
for str in allStrings {
if query == str {
matchStrings.append(str)
} else if str.hasPrefix(query) {
prefixStrings.append(str)
} else if str.containsString(query) {
containsStrings.append(str)
}
}
let results = matchStrings + prefixStrings + containsStrings
Use sort:
func sortingFunction(obj1: YourClass, _ obj2: YourClass) -> Bool {
return (str1.name.lowercaseString.hasPrefix(text!) && !obj2.name.lowercaseString.hasPrefix(text!)) ||
(obj1.name.lowercaseString.containsString(text!) && !obj2.name.lowercaseString.containsString(text!)) ||
(obj1.allEmails.lowercaseString.containsString(text!) && !obj2.allEmails.lowercaseString.containsString(text!)) &&
(obj1.allNumbers.lowercaseString.containsString(text!) && !obj2.allNumbers.lowercaseString.containsString(text!))
}
self.filteredNames = self.names.sort(sortingFunction)
Your code doesn't match your problem description. I'm gonna go with what you described since your code doesn't do what you want anyway.
You can score each match and sort by the matching score:
struct Person {
var name: String
}
let names = [
Person(name: "John Smith"),
Person(name: "Alan Johnson"),
Person(name: "Jane Doe"),
Person(name: "John")
]
let searchText = "John".lowercaseString // take this from you search box
let filteredNames = names.flatMap { p -> (Person, Int)? in
var score = 0
let name = p.name.lowercaseString
if name == searchText {
score += 3
} else if name.hasPrefix(searchText) {
score += 2
} else if name.containsString(searchText) {
score += 1
}
return score == 0 ? nil : (p, score)
}.sort { $0.1 > $1.1 }
.map { $0.0 }
print(filteredNames) // John, John Smith, Alan Johnson
Here what it does:
flatMap acts as a map and a filter at the same time. Each person is assigned a matching score based on your criteria. If the matching score is 0, we return nil so flatMap excludes it.
sort sort the result by the matching score
map remove the matching score so the final array only contains the Person
You can extend this to email, phone numbers, etc. Just play around the scoring mechanism.
I have the following struct defined.
struct Person {
var firstName :String
var lastName :String
var active :Bool
}
I have created a collection of Person as shown below:
var persons :[Person] = []
for var i = 1; i<=10; i++ {
var person = Person(firstName: "John \(i)", lastName: "Doe \(i)", active: true)
persons.append(person)
}
and Now I am trying to change the active property to false using the code below:
let inActionPersons = persons.map { (var p) in
p.active = false
return p
}
But I get the following error:
Cannot invoke map with an argument list of type #noescape (Person) throws
Any ideas?
SOLUTION:
Looks like Swift can't infer types sometimes which is kinda lame! Here is the solution:
let a = persons.map { (var p) -> Person in
p.active = false
return p
}
THIS DOES NOT WORK:
let a = persons.map { p in
var p1 = p
p1.active = false
return p1
}
There are exactly two cases where the Swift compiler infers the return
type of a closure automatically:
In a "single-expression closure," i.e. the closure body
consists of a single expression only (with or without explicit
closure parameters).
If the type can be inferred from the calling context.
None of this applies in
let inActionPersons = persons.map { (var p) in
p.active = false
return p
}
or
let a = persons.map { p in
var p1 = p
p1.active = false
return p1
}
and that's why
you have to specify the return type explicitly as in Kametrixom's answer.
Example of a single-expression closure:
let inActionPersons = persons.map { p in
Person(firstName: p.firstName, lastName: p.lastName, active: false)
}
and it would compile with (var p) in or (p : Person) in as well, so this has nothing to do with whether the closure arguments are given
explicitly in parentheses or not.
And here is an example where the type is inferred from the calling
context:
let a : [Person] = persons.map { p in
var p1 = p
p1.active = false
return p1
}
The result of map() must be a [Person] array, so map needs
a closure of type Person -> Person, and the compiler infers
the return type Person automatically.
For more information, see "Inferring Type From Context" and "Implicit Returns from Single-Expression Closures" in the
"Closures" chapter in the Swift book.
When using the brackets for arguments so that var works, you have to put the return type as well:
let inActionPersons = persons.map { (var p) -> Person in
p.active = false
return p
}
Swift 5
The accepted answer no longer works, as of Swift 5, anyway. Closures cannot have keyword arguments anymore which means that each element of the iteration must remain a constant. Therefore, to mutate structures using map, new elements must be initialized within each iteration:
let activePersons = persons.map { (p) -> Person in
return Person(firstName: p.firstName, lastName: p.lastName, active: true)
}
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.
I have a set of records:
type Person =
{
Name : string
Age : int
}
let oldPeople =
set [ { Name = "The Doctor"; Age = 1500 };
{ Name = "Yoda"; Age = 900 } ]
Unlike the hardcoded example above, the set of data actually comes from a data source (over which I have very little control). Now I need to subtract a set of data from another data source. In general, the data in this second source matches, but occasionally there is a difference in captialization:
let peopleWhoAreConfusedAboutTheirAge =
set [ { Name = "THE DOCTOR"; Age = 1500 } ]
When I attempt to subtract the second set from the first, it fails because the string comparison is case sensitive:
let peopleWhoKnowHowOldTheyAre =
oldPeople - peopleWhoAreConfusedAboutTheirAge
val peopleWhoKnowHowOldTheyAre : Set<Person> =
set [{Name = "The Doctor";
Age = 1500;}; {Name = "Yoda";
Age = 900;}]
Is there a way to perform a case-insensitive comparison for the Name field of the People record?
This is what I've implemented so far, though there may be a better way to do it.
My solution was to override the Equals function on the People record so as to perform a case-insensitive comparison. Set subtraction uses the Equals function to determine if two records match one another. By overriding Equals, I was forced (via warning and error) to override GetHashCode and implement IComparable (as well as set the CustomEquality and CustomComparison attributes):
[<CustomEquality; CustomComparison>]
type Person =
{
Name : string
Age : int
}
member private this._internalId =
this.Name.ToLower() + this.Age.ToString()
interface System.IComparable with
member this.CompareTo obj =
let other : Person = downcast obj
this._internalId.CompareTo( other._internalId )
override this.Equals( other ) =
match other with
| :? Person as other ->
System.String.Compare( this._internalId, other._internalId ) = 0
| _ -> false
override this.GetHashCode() =
this._internalId.GetHashCode()
This, however, seems to do the trick:
let oldPeople =
set [ { Name = "The Doctor"; Age = 1500 };
{ Name = "Yoda"; Age = 900 } ]
let peopleWhoAreConfusedAboutTheirAge =
set [ { Name = "THE DOCTOR"; Age = 1500 } ]
let peopleWhoKnowHowOldTheyAre =
oldPeople - peopleWhoAreConfusedAboutTheirAge
val peopleWhoKnowHowOldTheyAre : Set<Person> = set [{Name = "Yoda";
Age = 900;}]
If you know a better solution (involving less code), please post it rather than comment on this answer. I will happily accept a less verbose, awkward solution.
Here's another approach:
type Name(value) =
member val Value = value
override this.Equals(that) =
match that with
| :? Name as name -> StringComparer.CurrentCultureIgnoreCase.Equals(this.Value, name.Value)
| _ -> false
override this.GetHashCode() =
StringComparer.CurrentCultureIgnoreCase.GetHashCode(this.Value)
type Person =
{
Name: Name
Age: int
}
{Name=Name("John"); Age=21} = {Name=Name("john"); Age=21} //true