Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to validate CSV data in f#

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?

like image 312
stimms Avatar asked Feb 08 '23 05:02

stimms


2 Answers

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.

like image 124
Mark Seemann Avatar answered Feb 09 '23 19:02

Mark Seemann


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.

like image 41
Grundoon Avatar answered Feb 09 '23 18:02

Grundoon