In F#, how to use Array.Pick on strings? - f#

(Newbie question).
In F#, please assume an array of CompanyNames of the format:
"BLUE CROSS BLUE SHIELD OF ALABAMA, BIRMINGHAM"
This from the below where GetAllCompanies reads from the database the company names and cities:
CompanyNames = GetAllCompanies |> Array.map (fun i -> sprintf "%s , %s" i.companyname i.city )
This fails, but is my best attempt at going the other way. That is, given the company name and city as a string like above, I want to get back the company it came from (y is the company,city string from above):
let company = Array.pick (fun (k:company1) ->
match sprintf "%s , %s" k.companyname k.city with
| y -> Some k
| _ -> None ) GetAllCompanies
The compiler gives the warning on _ as: "This rule will never be matched".
How is this done?
TIA
Just to clarify, I will be getting a string of "company name"+","+"city" as typed in by the user. I need to check if this string matches a composite string of "company name"+","+"city" that was built from the company details and then return all the company details when it matches. GetAllCompanies returns an array of company details. Thanks.

I believe you are asking the wrong question. It seems you're trying to find the first company that matches the user input. Unless you want to transform the found company, Array.pick is not the right function, and you should use another function. You probably also don't want an exception if the company is not found.
type Company = { Name: string; City: string }
let companies =
[|
{ Name = "Foo"; City = "Oslo"}
{ Name = "Bar"; City = "Lillehammer"}
|]
let userInput = "Bar,Lillehammer"
let company =
companies
|> Array.tryFind (fun c -> c.Name + "," + c.City = userInput)
Now you can match on company.
There are still several weaknesses. Two of them: What if several companies match the user input? I'd use Array.filter then, if you want to present all of them to the user during interactive input. What if the user types spaces, e.g. a space after the comma? Or if there are commas in the company info?

Related

A better way to use Seq.fold in F#

I wrote a small console app that update a Type record without using any mutable variable. If that looks simple for seasoned functional programmers, it was quite a hard work for me.
It works, but there is one thing I am not happy with. But before that, let's start with the code:
open System
//------------------------------------------------------------------------------------
// Type, no data validation to keep it simple
//------------------------------------------------------------------------------------
[<StructuredFormatDisplay("{FirstName} {LastName} is a {Age} year old {Sex}")>]
type Student = {
FirstName: string
LastName : string
Sex : char
Age: int
}
//------------------------------------------------------------------------------------
// I/O functions
//------------------------------------------------------------------------------------
let getConsoleChar message =
printf "\n%s" message
Console.ReadKey().KeyChar
let getConsoleString message =
printf "\n%s" message
Console.ReadLine()
let getConsoleInt = getConsoleString >> Int32.Parse //no tryparse to keep it simple, I'm sure you can type an integer
let isValidCommand command = [ 'f'; 'l'; 's'; 'a'; 'x'] |> List.contains command
let isStopCommand = (=) 'x'
let processCommand student command =
match command with
| 'f' -> { student with FirstName = (getConsoleString "First Name: ")}
| 'l' -> { student with LastName = (getConsoleString "Last Name: ")}
| 's' -> { student with Sex = (getConsoleChar "Sex: ")}
| 'a' -> { student with Age = (getConsoleInt "Age: ")}
| 'x' -> student
| _ -> failwith "You've just broken the Internet, theorically you cannot be here"
//------------------------------------------------------------------------------------
// Program
//------------------------------------------------------------------------------------
let initialStudent = {
FirstName = String.Empty
LastName = String.Empty
Sex = Char.MinValue
Age = 0
}
let commands = seq {
while true do
yield getConsoleChar "Update [f]irst name, [l]ast name, [s]ex, [a]ge or e[x]it: " }
let finalStudent =
commands
|> Seq.filter isValidCommand
|> Seq.takeWhile (not << isStopCommand)
|> Seq.map (fun cmd -> (initialStudent, cmd))
|> Seq.fold (fun student studentAndCommand -> processCommand student (snd studentAndCommand)) initialStudent
printfn "\n<<<< %A >>>>\n" finalStudent
My problem is with
|> Seq.map (fun cmd -> (initialStudent, cmd))
|> Seq.fold (fun student studentAndCommand -> processCommand student (snd studentAndCommand)) initialStudent
It looks bizarre to transform a sequence of char into a Student*char to be able to plug it with aSeq.fold. Also, if using the initialStudent as a starting point for Seq.fold is logical, it feel weird to use it in the mapping transformation (I'm not sure anyone would understand the logic if this code was pushed in prod).
Is there a better way to treat the sequence of commands or is this code standard and acceptable in the functional world?
You can get rid of the map and simplify the fold considerably:
commands
|> Seq.filter isValidCommand
|> Seq.takeWhile (not << isStopCommand)
|> Seq.fold processCommand initialStudent
I'm not sure why you thought you had to map the seq<char> into a seq<Student * char> to begin with. Since you immediately use snd to extract the char from the tuple, undoing the map, the tuples' first elements are totally ignored. Much cleaner to simply avoid creating tuples in the first place

Is there a way to get Record fields by string in F#?

I would like to get the value of a field in a Record by looking it up with a string.
type Test = { example : string }
let test = { example = "this is the value" }
let getByName (s:string) =
???? //something like test.GetByName(s)
Standard .net reflection should be working fine for such scenario. Record fields are exposed as properties, so you can just query the type with reflection API.
It could look like this:
let getByName (s:string) =
match typeof<Test>.GetProperties() |> Array.tryFind (fun t -> t.Name = s)
with
| Some pi -> Some(pi.GetValue(test))
| None -> None

Why can't F# compiler infer type in this case?

It seems that the p argument in printPerson function can't be inferred to be Person, but intelisense shows for both of printPerson calls that I'm passing p : Person. Help me understand what I'm doing wrong please?
type Person (name:string) =
member e.Name = name
type EmployeeNode =
| Leader of Person * int * list<EmployeeNode>
| Employee of Person * int
let printPerson level p =
printfn "%s %s" <| String.replicate (level) "#" <| p.Name
let rec print node =
match node with
| Employee(p, level) -> printPerson level p
| Leader(p, level, nodes) ->
printPerson level p
List.iter print nodes
Multiple types could have a member this.Name, even if they don't in this example, so the compiler doesn't know that you meant Person. Say for example you had
type Person (name : string) =
member this.Name = name
type School (name : string, address : string) =
member this.Name = name
member this.Address = address
let printName x = printfn "%s" x.Name // This is a type error.
The compiler can't tell which you meant - Person or School. It doesn't matter if you haven't defined another type with a member of the same name, the compiler still won't take the type because things like type extensions can add members onto types after compilation.
Intellisense knows the types you're trying to pass when you call the function, but that's not the same as the compiler enforcing type-safety. In general, any function which accesses class methods or members will need a type annotation for that class.
To fix your example, you just need to change to
let printPerson level p =
to
let printPerson level (p : Person) =
If you are working with simple immutable data containers in F#, you will find it easier to write idiomatic code, making use of type inference, with Records rather than standard .NET classes.
You would change your definition of Person as follows:
type Person = {Name : string}
If you use a record, you do not need to use a type annotation and can keep your code as it is:
let printPerson level p =
printfn "%s %s" <| String.replicate (level) "#" <| p.Name
I would recommend this approach, especially because records give you additional bonuses for free such as automatic structural equality and comparison.
If you use a standard .NET class you must provide a type annotation to disambiguate the specific type you are referring to which exposes the Name property:
type Person (name:string) =
member e.Name = name
let printPerson level (p : Person) =
printfn "%s %s" <| String.replicate (level) "#" <| p.Name
In printPerson, the only information about p the compiler has is that it has a Name member. Since there can be other types than Person which have one, it can't infer p to be Person.
In calls of printPerson the type of p is determined from the pattern, not from the call.

F# Read Fixed Width Text File

Hi I'm looking to find the best way to read in a fixed width text file using F#. The file will be plain text, from one to a couple of thousand lines long and around 1000 characters wide. Each line contains around 50 fields, each with varying lengths. My initial thoughts were to have something like the following
type MyRecord = {
Name : string
Address : string
Postcode : string
Tel : string
}
let format = [
(0,10)
(10,50)
(50,7)
(57,20)
]
and read each line one by one, assigning each field by the format tuple(where the first item is the start character and the second is the number of characters wide).
Any pointers would be appreciated.
The hardest part is probably to split a single line according to the column format. It can be done something like this:
let splitLine format (line : string) =
format |> List.map (fun (index, length) -> line.Substring(index, length))
This function has the type (int * int) list -> string -> string list. In other words, format is an (int * int) list. This corresponds exactly to your format list. The line argument is a string, and the function returns a string list.
You can map a list of lines like this:
let result = lines |> List.map (splitLine format)
You can also use Seq.map or Array.map, depending on how lines is defined. Such a result will be a string list list, and you can now map over such a list to produce a MyRecord list.
You can use File.ReadLines to get a lazily evaluated sequence of strings from a file.
Please note that the above is only an outline of a possible solution. I left out boundary checks, error handling, and such. The above code may contain off-by-one errors.
Here's a solution with a focus on custom validation and error handling for each field. This might be overkill for a data file consisting of just numeric data!
First, for these kinds of things, I like to use the parser in Microsoft.VisualBasic.dll as it's already available without using NuGet.
For each row, we can return the array of fields, and the line number (for error reporting)
#r "Microsoft.VisualBasic.dll"
// for each row, return the line number and the fields
let parserReadAllFields fieldWidths textReader =
let parser = new Microsoft.VisualBasic.FileIO.TextFieldParser(reader=textReader)
parser.SetFieldWidths fieldWidths
parser.TextFieldType <- Microsoft.VisualBasic.FileIO.FieldType.FixedWidth
seq {while not parser.EndOfData do
yield parser.LineNumber,parser.ReadFields() }
Next, we need a little error handling library (see http://fsharpforfunandprofit.com/rop/ for more)
type Result<'a> =
| Success of 'a
| Failure of string list
module Result =
let succeedR x =
Success x
let failR err =
Failure [err]
let mapR f xR =
match xR with
| Success a -> Success (f a)
| Failure errs -> Failure errs
let applyR fR xR =
match fR,xR with
| Success f,Success x -> Success (f x)
| Failure errs,Success _ -> Failure errs
| Success _,Failure errs -> Failure errs
| Failure errs1, Failure errs2 -> Failure (errs1 # errs2)
Then define your domain model. In this case, it is the record type with a field for each field in the file.
type MyRecord =
{id:int; name:string; description:string}
And then you can define your domain-specific parsing code. For each field I have created a validation function (validateId, validateName, etc).
Fields that don't need validation can pass through the raw data (validateDescription).
In fieldsToRecord the various fields are combined using applicative style (<!> and <*>).
For more on this, see http://fsharpforfunandprofit.com/posts/elevated-world-3/#validation.
Finally, readRecords maps each input row to the a record Result and chooses the successful ones only. The failed ones are written to a log in handleResult.
module MyFileParser =
open Result
let createRecord id name description =
{id=id; name=name; description=description}
let validateId (lineNo:int64) (fields:string[]) =
let rawId = fields.[0]
match System.Int32.TryParse(rawId) with
| true, id -> succeedR id
| false, _ -> failR (sprintf "[%i] Can't parse id '%s'" lineNo rawId)
let validateName (lineNo:int64) (fields:string[]) =
let rawName = fields.[1]
if System.String.IsNullOrWhiteSpace rawName then
failR (sprintf "[%i] Name cannot be blank" lineNo )
else
succeedR rawName
let validateDescription (lineNo:int64) (fields:string[]) =
let rawDescription = fields.[2]
succeedR rawDescription // no validation
let fieldsToRecord (lineNo,fields) =
let (<!>) = mapR
let (<*>) = applyR
let validatedId = validateId lineNo fields
let validatedName = validateName lineNo fields
let validatedDescription = validateDescription lineNo fields
createRecord <!> validatedId <*> validatedName <*> validatedDescription
/// print any errors and only return good results
let handleResult result =
match result with
| Success record -> Some record
| Failure errs -> printfn "ERRORS %A" errs; None
/// return a sequence of records
let readRecords parserOutput =
parserOutput
|> Seq.map fieldsToRecord
|> Seq.choose handleResult
Here's an example of the parsing in practice:
// Set up some sample text
let text = """01name1description1
02name2description2
xxname3badid-------
yy badidandname
"""
// create a low-level parser
let textReader = new System.IO.StringReader(text)
let fieldWidths = [| 2; 5; 11 |]
let parserOutput = parserReadAllFields fieldWidths textReader
// convert to records in my domain
let records =
parserOutput
|> MyFileParser.readRecords
|> Seq.iter (printfn "RECORD %A") // print each record
The output will look like:
RECORD {id = 1;
name = "name1";
description = "description";}
RECORD {id = 2;
name = "name2";
description = "description";}
ERRORS ["[3] Can't parse id 'xx'"]
ERRORS ["[4] Can't parse id 'yy'"; "[4] Name cannot be blank"]
By no means is this the most efficient way to parse a file (I think there are some CSV parsing libraries available on NuGet that can do validation while parsing) but it does show how you can have complete control over validation and error handling if you need it.
A record of 50 fields is a bit unwieldy, therefore alternate approaches which allow dynamic generation of the data structure may be preferable (eg. System.Data.DataRow).
If it has to be a record anyway, you could spare at least the manual assignment to each record field and populate it with the help of Reflection instead. This trick relies on the field order as they are defined. I am assuming that every column of fixed width represents a record field, so that start indices are implied.
open Microsoft.FSharp.Reflection
type MyRecord = {
Name : string
Address : string
City : string
Postcode : string
Tel : string } with
static member CreateFromFixedWidth format (line : string) =
let fields =
format
|> List.fold (fun (index, acc) length ->
let str = line.[index .. index + length - 1].Trim()
index + length, box str :: acc )
(0, [])
|> snd
|> List.rev
|> List.toArray
FSharpValue.MakeRecord(
typeof<MyRecord>,
fields ) :?> MyRecord
Example data:
"Postman Pat " +
"Farringdon Road " +
"London " +
"EC1A 1BB" +
"+44 20 7946 0813"
|> MyRecord.CreateFromFixedWidth [16; 16; 16; 8; 16]
// val it : MyRecord = {Name = "Postman Pat";
// Address = "Farringdon Road";
// City = "London";
// Postcode = "EC1A 1BB";
// Tel = "+44 20 7946 0813";}

How to create record in match pattern

I want to write an application that read ip address from xml file. The file looks like
<range>
<start>192.168.40.1</start>
<end>192.168.50.255</end>
<subnet>255.255.255.0</subnet>
<gateway>192.168.50.1</gateway>
</range>
I create an records type to save the ip address
type Scope = { Start: IPAddress; End: IPAddress; Subnetmask: IPAddress; Gateway: IPAddress }
I wrote a unit function, that output the ip's.
loc
|> Seq.iter (fun e -> match e.Name.LocalName with
|"start" -> printfn "Start %s" e.Value
|"end" -> printfn "End %s" e.Value
|"subnet" -> printfn "Subnet %s" e.Value
|"gateway" -> printfn "Gateway %s" e.Value
| _ -> ())
How can I return the scope records type instead of unit?
As mentioned in the comments, the XML type provider makes this a lot easier. You can just point it at a sample file, it will infer the structur and let you read the file easily:
type RangeFile = XmlProvider<"sample.xml">
let range = RangeFile.Load("file-you-want-to-read.xml")
let scope =
{ Start = IPAddress.Parse(range.Start)
End = IPAddress.Parse(range.End)
Subnetmask = IPAddress.Parse(range.Subnet)
Gateway = IPAddress.Parse(range.Gateway) }
That said, you can certainly implement this yourself too. The code you wrote is a good start - there is a number of ways to do this, but in any case, you'll need to do some lookup based on the local name of the element (to find start, end, etc.).
One option is to load all the properties into a dictionary:
let lookup =
loc
|> Seq.map (fun e -> e.Name.LocalName, IPAddress.Parse(e.Value)
|> dict
Now you have a lookup table that contains IPAddress for each of the keys, so you can create Scope value using just:
let scope =
{ Start = lookup.["start"]; End = lookup.["end"];
Subnetmask = lookup.["subnet"]; Gateway = lookup.["gateway"] }
That said, the nice thing about the XML type provider is that it removes the need to do lookup based on string values and so you are less likely to make mistakes caused by typos.

Resources