I have these types
type IntervalWithBreak = { Start: DateTime; End: DateTime }
type IntervalWithoutBreak = { Start: DateTime; End: DateTime; Break: TimeSpan }
type WorkTime =
| Workshop of IntervalWithBreak
| Mounting of IntervalWithBreak
| Ill of IntervalWithoutBreak
type WorktimeDefinition = {Weekday: DayOfWeek}
Now I want to create a function which allows me to find elements matching a condition.
Code completion for workTime in the matches function however doesn't provide the Start or End fields until I use this pattern match.
let matches (worktime:WorkTime) (worktimeDefinition:WorktimeDefinition) =
match worktime with
| Workshop w -> w.Start.DayOfWeek = worktimeDefinition.Weekday
| Mounting m -> m.Start.DayOfWeek = worktimeDefinition.Weekday
| Ill i -> i.Start.DayOfWeek = worktimeDefinition.Weekday
List.find (matches worktime) definitions
Can I have a more generic match so I don't have to check against all union cases but instead match against IntervalWithoutBreak and IntervalWithBreak?
My type definition is based on this suggestion.
As the argument of the Workshop case and the Mounting case is of the same type, the first thing you can do is to join them into an or-pattern:
let matches (worktime:WorkTime) (worktimeDefinition:WorktimeDefinition) =
match worktime with
| Workshop m
| Mounting m -> m.Start.DayOfWeek = worktimeDefinition.Weekday
| Ill i -> i.Start.DayOfWeek = worktimeDefinition.Weekday
This will not work for the Ill case, which contains an argument of a different type. But since they are all records, you can deconstruct the record in the pattern matching too:
let matches (worktime:WorkTime) (worktimeDefinition:WorktimeDefinition) =
match worktime with
| Workshop { Start = start }
| Mounting { Start = start }
| Ill { Start = start } -> start.DayOfWeek = worktimeDefinition.Weekday
As you now have just one case, you could actually write this using let, but I probably would not do this. I think match is more readable. But just for the record:
let matches (worktime:WorkTime) (worktimeDefinition:WorktimeDefinition) =
let (Workshop { Start = s } | Mounting { Start = s } | Ill { Start = s }) = worktime
s.DayOfWeek = worktimeDefinition.Weekday
Related
I have some types like this:
type Workshop = { Start: DateTime; End: DateTime }
type Mounting = { Start: DateTime; End: DateTime }
type FixedWorkTime = { Start: DateTime; End: DateTime }
type WorkTime =
| Workshop of Workshop
| Mounting of Mounting
type ApprovedWorkTime =
| ExistingWorkTime of WorkTime
| FixedWorkTime of FixedWorkTime
Now I want to create a function with this signature WorkTime -> ApprovedWorkTime
let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
match workTime with
| WorkTime.Workshop workshop -> ApprovedWorkTime.ExistingWorkTime { Start = workshop.Start, End = workshop.End }
| WorkTime.Mounting mounting -> ApprovedWorkTime.FixedWorkTime { Start = mounting.Start, End = mounting.End }
The match fails to compile with this error:
This expression was expected to have type 'WorkTime' but here has type 'FixedWorkTime'
How can I fix this?
Use semicolons, not commas, between the fields of the records. Whenever you see a comma, assume that means a tuple.
Second, ExistingWorkTime is of WorkTime, which is also a DU, so you have a hierarchy.
let fixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
match workTime with
| Workshop workshop -> ExistingWorkTime (Workshop { Start = workshop.Start; End = workshop.End })
| Mounting mounting -> FixedWorkTime { Start = mounting.Start; End = mounting.End }
The easy fix is to make the types match, as other answers already explained:
let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
match workTime with
| WorkTime.Workshop _ -> ExistingWorkTime workTime
| WorkTime.Mounting mounting -> FixedWorkTime { Start = mounting.Start; End = mounting.End }
However, I'd probably change your types a bit to make them more composable:
type Interval = { Start: DateTime; End: DateTime }
type WorkTime =
| Workshop of Interval
| Mounting of Interval
type ApprovedWorkTime =
| ExistingWorkTime of WorkTime
| FixedWorkTime of Interval
let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
match workTime with
| Workshop _ -> ExistingWorkTime workTime
| Mounting interval -> FixedWorkTime interval
Here:
ApprovedWorkTime.ExistingWorkTime { Start = workshop.Start, End = workshop.End }
You're trying to construct a value of ApprovedWorkTime with the ExistingWorkTime constructor, and you're passing it a record as a parameter.
But according to its definition:
| ExistingWorkTime of WorkTime
It does not expect a record, or expects a value of type WorkTime.
So the shortest way to fix it would be to it workTime as a parameter:
ApprovedWorkTime.ExistingWorkTime workTime
Although I severely doubt that this is what you meant to do.
Separately I'd like to point out that your types are needlessly complicated. Look: every single value has the same fields, and they only differ in their constructors, and you have to shovel those fields back and forth every time you convert between values.
A more efficient model would have the Start and End fields at the top level, and the kind of work as a third field:
type WorkTime<'kind>= { Start: DateTime; End: DateTime; Kind: 'kind }
type WorkTimeKind = Workshop | Mounting
type ApprovedWorkTimeKind = ExistingWorkTime of WorkTime | FixedWorkTime
Given the following parametric type
type SomeDU2<'a,'b> =
| One of 'a
| Two of 'a * 'b
I have to functions that check if the given param is the respective union case without regard to params
let checkOne x =
match x with
| One _ -> true
| _ -> false
let checkTwo x =
match x with
| Two _ -> true
| _ -> false
This works pretty nice and as expected
let oi = checkOne (One 1)
let os = checkOne (One "1")
let tis = checkTwo (Two (1, "1"))
let tsi = checkTwo (Two ("1", 1))
I can switch the types as I like.
Now However I like to combine those two functions into one creation function
let makeUC () = (checkOne, checkTwo)
and then instantiate like this
let (o,t) = makeUC ()
only it gives me this error message now
Value restriction. The value 'o' has been inferred to have generic type
val o : (SomeDU2<'_a,'_b> -> bool)
Either make the arguments to 'o' explicit or, if you do not intend for it to be generic, add a type annotation.
val o : (SomeDU2<obj,obj> -> bool)
Actually I dont want that - nor do I need that.
Probably its a instance of missing higher kinded types in F#
Is there a way around this?
Edit
Actually me question wasnt complety as per #johns comment below.
Obviously I can do the following
let ro1 = o ((One 1) : SomeDU2<int,int>)
let rt1 = t (Two (1,2))
which then will backwards infer o and t to be of type SomeDU2<int,int> -> bool (I find this backwards inference very strange thou). The problem then is that o wont allow for the below anymore.
let ro2 = o ((One "1") : SomeDU2<string,int>)
So I'd have to instantiate a specific o instance for every combination of generic parameters of SomeDU2.
You would run into the value restriction even without the tuple:
let o = (fun () -> checkOne)()
If you need the results of invoking a function to be applicable to values of any type, then one solution would be to create instances of a nominal type with a generic method:
type DU2Checker =
abstract Check : SomeDU2<'a,'b> -> bool
let checkOne = {
new DU2Checker with
member this.Check(x) =
match x with
| One _ -> true
| _ -> false }
let checkTwo = {
new DU2Checker with
member this.Check(x) =
match x with
| Two _ -> true
| _ -> false }
let makeUC() = checkOne, checkTwo
let o,t = makeUC()
let false = o.Check(Two(3,4))
I am working on a function that pattern matches some of my user-defined types in f# and converts them to strings. Part of the code looks like the following:
let gsToString (gs : general_structure) : string =
match gs with
| Date(Scattered(Eom(Ascending))) -> "Date(Scattered(Eom(Ascending)))"
| Date(Scattered(Eom(SameDate(dt)))) -> "Date(Scattered(Eom(SameDate(" + dt.ToString() + "))))"
| Number(AllNegative(Int1(Neither))) -> "Number(AllNegative(Int1(Neither)))"
| Number(AllNegative(Int1(SameInt(si)))) -> "Number(AllNegative(Int1(SameFloat(" + si.ToString() + "))))"
There are many other types being matched in this function, but these should be enough to convey the issue. Additionally, the types causing problems are:
| SameDate of System.DateTime
| SameFloat of float
Obviously, It is pretty trivial to do the first pattern matching function that converts my general_structure types to strings. However, a problem arises in my next function (which needs to be called later on in the code), where I need to reconvert the string representation back to a general_structure. The problem areas look like the following:
let stringToGS (str : string) : general_structure =
match str with
| "Date(Scattered(Eom(Ascending)))" -> Date(Scattered(Eom(Ascending)))
| "Date(Scattered(Eom(SameDate(dt))))"-> Date(Scattered(Eom(SameDate(System.DateTime.Parse dt))))
| "Number(AllNegative(Int1(Neither)))" -> Number(AllNegative(Int1(Neither)))
| "Number(AllPositive(Float1(SameFloat(sf))))" -> Number(AllPositive(Float1(SameFloat((float) sf))))
Although the first and the third cases in the stringToGS function work just fine, I am unable to find a way to convert the others back to their original form. If there any way to take a string inside of a pattern matching statement (in this case it would be dt and fs) and somehow parse only that portion of the pattern in order to return a different value (in this case I am trying to make them System.DateTimes and Floats, respectively) and return then to their original forms of:
Date(Scattered(Eom(SameDate(dt))))
Number(AllPositive(Float1(SameFloat(sf))))
? I would appreciate any help.
EDIT:
I was able to resolve the problem by doing something like the following with if statements for the cases that were causing problems:
if str.Contains("Scattered(Eom(SameDate")
then
let p1 = str.IndexOf(")")
let p2 = str.LastIndexOf("(")
let dt1 = str.Remove(p1)
let dt2 = dt1.Substring(p2 + 1)
let date = System.DateTime.Parse dt2
Date(Scattered(Eom(SameDate(date))))
Then, I could just do the normal pattern matching on all of the types that did not contain nested data.
You could also use active patterns, if there is a limited amount of classes and you don't want to use a serialization library:
open System
let (|RegexMatch|_|) pattern input =
let matches = System.Text.RegularExpressions.Regex.Matches(input, pattern)
if matches.Count = 1 then Some matches.[0].Groups.[1].Value
else None
type GeneralStructure =
| NoPayload
| DatePayload of DateTime
| StringPayload of string option
let toString = function
| NoPayload -> "NoPayload"
| DatePayload dt -> sprintf "DatePayload(%d)" <| dt.ToBinary()
| StringPayload None -> "StringPayload(None)"
| StringPayload (Some s) -> sprintf "StringPayload(Some(%s))" s
let fromString = function
| "NoPayload" -> NoPayload
| "StringPayload(None)" -> StringPayload None
| RegexMatch #"DatePayload\((.*)\)" dt -> DatePayload <| DateTime.FromBinary(Int64.Parse dt)
| RegexMatch #"StringPayload\(Some\((.*)\)\)" msg -> StringPayload <| Some msg
| o -> failwithf "Unknown %s %s" typeof<GeneralStructure>.Name o
let serialized = StringPayload <| Some "Foo" |> toString
let deserialized = fromString serialized
let serialized' = DatePayload DateTime.UtcNow |> toString
let deserialized' = fromString serialized'
// val serialized : string = "StringPayload(Some(Foo))"
// val deserialized : GeneralStructure = StringPayload (Some "Foo")
// val serialized' : string = "DatePayload(5247430828937321388)"
// val deserialized' : GeneralStructure = DatePayload 06.08.2015 18:04:10
Note that the regex is not foolproof, I made that up just to fit these cases.
Suppose I have this type:
type T = int option
and an observable of that type:
let o : IObservable<T> = // create the observable
I'm looking for a better way to express this:
o.Where(function | None -> false | Some t -> true)
.Select(function | Some t -> t)
An observable that only propagates the Some case.
There are several things that I don't like.
I'm using 2 operators
I'm pattern matching twice
The second pattern matching isn't exhaustive (makes visual studio show a warning and feels odd)
Too much code. The pattern repeats every time I need pattern matching.
Can't you use Observable.choose ? something like this :
let o1 : IObservable<int option> = // ...
let o2 = Observable.choose id o1
If you have a type that is not an option, say:
type TwoSubcases<'a,'b> = | Case1 of 'a | Case2 of 'b
and a partial active pattern:
let (|SecondCase|_|) = function
| Case1 _ -> None
| Case2 b -> Some b
then you can do:
let o1 : IObservable<TwoSubcases<int, float>> = // ...
let o2 : IObservable<float> = Observable.choose (|SecondCase|_|) o1
Thanks to #Lee I came up with a nice solution.
o.SelectMany(function | None -> Observable.Empty() | Some t -> Observable.Return t)
This works for any union type, not only Option.
I have the following code. For the last two match, the first period has type of DateTime option and the second one has type of int. Why the second one doesn't have option?
let (|Integer|_|) (str: string) =
let mutable intvalue = 0
if Int32.TryParse(str, &intvalue) then Some(intvalue)
else None
let (|DateyyMM|) (str: string) =
let mutable date = new DateTime()
if DateTime.TryParseExact(str,
"yyyyMM",
Globalization.DateTimeFormatInfo.InvariantInfo,
Globalization.DateTimeStyles.None,
&date)
then Some(date)
else None
let (|ParseRegex|_|) regex str =
let m = Regex(regex).Match(str)
if m.Success
then Some (List.tail [ for x in m.Groups -> x.Value ])
else None
.....
match url with
| ParseRegex "....." [DateyyMM period] -> //period type is DateTime option
......
match downloadLink.Url with
| ParseRegex "....." [name; Integer period] -> // period type is int
......
The second case has no option because you added _| at the end of the declaration.
This is setup to allow for a shorthand in a match - so that rather than
match x with
|Some_long_function(Some(res)) -> ...
|Some_long_function(None) -> ...
you can just do
match x with
|Some_long_function(res) -> ...
|_ -> ...
See the MSDN page on active patterns for more: http://msdn.microsoft.com/en-us/library/dd233248.aspx (in particular the secion on partial patterns)