F#: How to match on a substring? - f#

I am new to F#, but not new to programming. Most of my experience is in the C# and SQL world. MSDN and other sites I've looked at haven't quite made this simple enough for my little brain yet, so I'm wondering if you can give me a nudge in the right direction.
I'm trying to write a simple function that returns true if a string is null, empty, or starts with "//", else it should return false.
#light
let thisIsACommentOrBlank line =
match line with
| null -> true
| "" -> true
| notSureWhatToPutHere -> true
| _ -> false
Thanks!
Update
Thanks to all of your suggestions, in the end, I was able to collapse everything down to a lambda as follows:
|> List.filter (fun line -> not (System.String.IsNullOrWhiteSpace(line) || line.StartsWith("//")))
Thanks again.

Here is one way to do it:
let thisIsACommentOrBlank line =
match line with
| a when System.String.IsNullOrWhiteSpace(a) || a.StartsWith("//") -> true
| _ -> false;;

You can use a when clause, like this:
let thisIsACommentOrBlank line =
match line with
| null -> true
| "" -> true
| s when s.StartsWith "//" -> true
| _ -> false
But for that matter, this is a lot simpler:
let thisIsACommentOrBlank line =
(String.IsNullOrEmpty line) || (line.StartsWith "//")

You can just omit last match:
let thisIsACommentOrBlank line =
match line with
| null -> true
| "" -> true
| s -> s.StartsWith "//"

You can use a when condition (see here):
let thisIsACommentOrBlank line =
match line with
| null -> true
| "" -> true
| s when s.StartsWith "//" -> true
| _ -> false
But your function can be optimized:
let thisIsACommentOrBlank = function
| null | "" -> true
| s -> s.StartsWith "//"

Related

How can I use TryParse in a guard expression in match?

I have built a toy spreadsheet to help learn F#. When I process the text for a new cell I store it as a discriminated type. To parse it I feel I should be able to do something like:
let cv =
match t with
| _ when t.Length=0 -> Empty
| x when t.[0]='=' -> Expr(x)
| x when t.[0]='\"' -> Str(x)
| (true,i) when Int32.TryParse t -> IntValue(i) // nope!
| _ -> Str(t)
I have tried quite a few combinations but I cannot get TryParse in the guard. I have written a helper:
let isInt (s:string) =
let mutable m:Int64 = 0L
let (b,m) = Int64.TryParse s
b
I can now write:
| _ when Utils.isInt t -> IntValue((int)t)
This seems like a poor solution as it discards the converted result. What the correct syntax to get TryParse into the guard?
I think an active pattern will do what you want:
let (|Integer|_|) (str: string) =
let flag, i = Int32.TryParse(str)
if flag then Some i
else None
let cv =
match t with
| _ when t.Length=0 -> Empty
| x when t.[0]='=' -> Expr(x)
| x when t.[0]='\"' -> Str(x)
| Integer i -> IntValue(i)
| _ -> Str(t)
But if you really want TryParse in the guard condition (and you don't mind parsing twice), you could do this:
| x when fst (Int32.TryParse(t)) -> IntValue (Int32.Parse(x))

Convert nested match expression to function

I have some nested Discriminated Unions
type Job = Sniff | Guard
type Dog = Chihuahua | GermanShepherd of Job
Here's a function that takes a Dog and returns a string.
let dogPrinter d =
match d with
| Chihuahua -> ""
| GermanShepherd g ->
match g with
| Sniff -> ""
| Guard -> ""
I can convert the first match to the function syntax:
let dogPrinter = function
| Chihuahua -> ""
| GermanShepherd g ->
match g with
| Sniff -> ""
| Guard -> ""
How can I convert the second match to function?
The idiomatic way of avoiding nested matches in scenarios like this is to use nested patterns:
let dogPrinter = function
| Chihuahua -> ""
| GermanShepherd Sniff -> ""
| GermanShepherd Guard -> ""
You can nest patterns as deeply as you need, just like you can nest expressions when creating the values.
The only way I could think of, which I think might generally be the best approach, because it separates the concerns appropriately. However, I don't think this function keyword adds any value. I usually just stick with the match keyword.
let jobPrinter = function
| Sniff -> ""
| Guard -> ""
let dogPrinter = function
| Chihuahua -> ""
| GermanShepherd job -> job |> jobPrinter
Although I think #glennsl answer is what you should consider doing here's an aswer to what OP asked for:
type Job = Sniff | Guard
type Dog = Chihuahua | GermanShepherd of Job
let dogPrinter = function
| Chihuahua -> "Voff"
| GermanShepherd g ->
g |> ( function
| Sniff -> "Sniff"
| Guard -> "Guard"
)

In F#, How can I attach metadata to discriminated union values?

I want to create something that's kind of like an enum with an F# record type for a value instead of an int. For example, if I've got the union:
type BologneseIngredients = | Spaghetti
| Tomatoes
| MincedBeef
| GrandmasSecretIngredient
I know that spaghetti is always 30cm long and tomatoes are always red. What I could do is have a 'get metadata' function:
let getMetadata = function
| Spaghetti -> { length: 30.0<cm> }
| Tomatoes -> { colour: Color.Red }
| _ -> { }
but I'd really like to keep the definition of the union and the data together. Is there a nice way to do this?
You could add properties to your discriminated union...
type BologneseIngredients =
| Spaghetti
| Tomatoes
| MincedBeef
| GrandmasSecretIngredient
member x.Color =
match x with
| Spaghetti -> Color.AntiqueWhite
| Tomatoes -> Color.Red
| MincedBeef -> Color.Firebrick
| GrandmasSecretIngredient -> Color.Transparent
let foo = Tomatoes
printfn "%A" foo.Color
> Color [Red]
my suggestion:
module Recipes =
type BologneseIngredients = | Spaghetti
| Tomatoes
| MincedBeef
| GrandmasSecretIngredient
let length (ind : BologneseIngredients) : float<cm> option =
match ind with
| Sphaghetti -> Some 30.0<cm>
| _ -> None
// .. or a bit more "metadata"ish
type Metadata =
| Length of float<cm>
| Color of System.Drawing.Color
let metadata =
function
| Sphaghetti -> [ Length 30.0<cm ]
| Tomatoes -> [ Color System.Drawing.Color.Red ]
| ...
let metaLength meta =
meta |> List.tryPick (function | Length l -> Some l | _ -> None)
let getLength = metadata >> metaLength

How can I remove a character from a string while doing character by character processing?

I'm fairly new to F# but I'm struggling to find how to properly represent the null character in the language. Can anyone tell me how to represent the null character in F#?
More to the point, what started me down the path is I'm trying to do some string processing with String.mapi, but I can't figure out how to remove a character in the below function:
let GetTargetFrameworkFolder version =
let versionMapper i c =
match c with
| 'v' -> if i = 0 then char(0x000) else c
| '.' -> char(0x000)
| _ -> c
match version with
| "v3.5" -> "net35"
| "v4.0" -> "net40"
| "v4.5" -> "net45"
| vers -> vers |> String.mapi versionMapper
GetTargetFrameworkFolder "v4.5.1" |> Dump
How can I remove a character from a string while doing character by character processing, as in the case with String.map and String.mapi?
You cannot remove a character using String.mapi, as this function maps exactly one character from the input to one character from the output. The null character is not the same thing as removing a character; it's just another character that happens to have the code 0.
In your case, if I understand correctly you want to remove the initial 'v' (if any) and remove dots. I would do it like this:
let GetTargetFrameworkFolder version =
match version with
| "v3.5" -> "net35"
| "v4.0" -> "net40"
| "v4.5" -> "net45"
| vers ->
let vers = if vers.[0] = 'v' then vers.[1..] else vers
vers.Replace(".", "")
Another way of doing this if you wanted to keep your original approach would be to write your own choose function for strings:
module String =
let choosei predicate str =
let sb = System.Text.StringBuilder()
let choose i (c:char) =
match predicate i c with
| Some(x) -> sb.Append(c) |> ignore
| None -> ()
str |> String.iteri choose
sb.ToString()
Then use it as follows:
let GetTargetFrameworkFolder version =
let versionMapper i = function
| 'v' when i = 0 -> None
| '.' -> None
| c -> Some(c)
match version with
| "v3.5" -> "net35"
| "v4.0" -> "net40"
| "v4.5" -> "net45"
| vers -> vers |> String.choosei versionMapper
GetTargetFrameworkFolder "v4.5.1" |> Dump
You can achieve this by using an array comprehension:
let GetTargetFrameworkFolder version =
match version with
| "v3.5" -> "net35"
| "v4.0" -> "net40"
| "v4.5" -> "net45"
| vers -> new String([|
for i in 0 .. vers.Length - 1 do
match i, vers.[i] with
| 0, 'v' | _, '.' -> () // skip 'v' at [0] and all '.'s
| _, c -> yield c // let everything else through
|])
By character processing while removing a character is filtering (string is a sequence of char):
let version (s: String) =
s
|> Seq.filter (fun ch -> ch <> '.' && ch <> 'v')
|> String.Concat
UPDATE:
To skip first 'v':
let version (s: String) =
s
|> Seq.skip (if s.StartsWith "v" then 1 else 0)
|> Seq.filter ((<>) '.')
|> String.Concat

pattern matching and returning new object based on pattern

Say I've got some code like this
match exp with
| Addition(lhs,rhs,_) -> Addition(fix lhs,fix rhs)
| Subtraction(lhs,rhs,_) -> Subtraction(fix lhs,fix rhs)
is there any way that would allow me to do something like
match exp with
| Addition(lhs,rhs,_)
| Subtraction(lhs,rhs,_) -> X(fix lhs,fix rhs)
where X be based on the actual pattern being matched
I like #kvb's answer.
This does suggest that you may want to redefine the DU, though:
type Op = | Add | Sub
type Expr = | Binary of Op * Expr * Expr
You can use an active pattern:
let (|Binary|_|) = function
| Addition(e1,e2) -> Some(Addition, e1, e2)
| Subtraction(e1,e2) -> Some(Subtraction, e1, e2)
| _ -> None
let rec fix = function
| Binary(con,lhs,rhs) -> con(fix lhs, fix rhs)
| _ -> ...

Resources