Just trying to wrap my head around some F# here and I'm having an issue.
I have a CSV file which looks like
CorrelationId,TagNumber,Description,CreationDate,UpdateDate,Discipline
8D3F96F3-938F-4599-BCA1-66B13199A39A,Test 70-2,Test tag - Ignore,2016-04-05 14:55:23.503,2016-04-05 14:55:23.503,Mechanical
A9FD4B9D-F7A1-4B7D-917F-D633EA0321E3,test-4,A test tag 24,2016-03-23 15:09:54.667,2016-03-30 17:35:29.553,Civil
And I'm reading it in using the CSV type provider
open FSharp.Data
type Tag = CsvProvider<"tags.csv">
let readTags (path:string) =
let tags = Tag.Load(path)
printfn "%i" ( tags.Rows |> Seq.length )
let tag = tags.Rows |> Seq.head
Then I'd like to validate the rows so I took a hint from the fsharpforfunandprofit railway oriented programming.
type Result<'TSuccess,'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
let bind switchFunction twoTrackInput =
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
let validateTagName tag =
if String.length tag.TagNumber = 0 then Failure "Tag number cannot be empty"
else Success tag
let validateTagDescription tag =
if String.length tag.Description = 0 then Failure "Tag description cannot be empty"
else Success tag
But I'm getting a problem in the validation methods that I need to annotate the functions with a type. I have no idea what type to annotate these as. I tried playing with creating a new type and mapping to it
type tagType = { TagNumber: string; Description: string}
which made those functions compile properly but I just kicked the problem down the road because now I'm not sure how to map from the Tag.Row to tagType. Ideally I'd do this validation without having to do any mapping.
How should all this look?
You already have the Tag
type from the type provider. With that particular data sample, it provides a nested type called Tag.Row
. You can annotate your functions with that type:
let validateTagName (tag : Tag.Row) =
if String.length tag.TagNumber = 0 then Failure "Tag number cannot be empty"
else Success tag
let validateTagDescription (tag : Tag.Row) =
if String.length tag.Description = 0 then Failure "Tag description cannot be empty"
else Success tag
These functions compile.
To add to Mark's answer, the problem is that dotting into a class in the OOP way generally needs a type annotation, because the type inference can't normally tell what type is being used.
For example, what is the type of x
here?
let doSomething x = x.Length // error FS0072: Lookup on object of indeterminate type
Using a function attached to a module would give the type inference the information it needs:
let doSomething x = List.length x // Compiles OK
The type inference will normally work with record types that you have defined:
type tagType = { TagNumber: string; Description: string}
let doSomething x = x.TagNumber // Compiles OK
But in this case you are working with a class defined by a type provider, so the type inference is not working as well.
As Mark says, the easiest thing to do is to use a type annotation, in the way that he demonstrates.
The alternative would be to write a converter function from the type provider Tag
type to your own MyTag
type, and then do
let myTags = tags.Rows |> Seq.map convertToMyTag
to convert each row into your type. I sometimes do that when I want a more sophisticated domain type than just a simple record with fields.
In this scenario, though, that would be overkill (and you'd still need to add an annotation to the converter function!)
Finally, here are two posts that might be useful: understanding type inference and troubleshooting common compiler errors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With