Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# Deedle accessing a row

Tags:

f#

f#-data

deedle

this is a basic question but i could not find the simple answer reading the tutorial

suppose i have this simple frame

type Person = 
  { Name:string; Age:int; Countries:string list; }

let peopleRecds = 
  [ { Name = "Joe"; Age = 51; Countries = [ "UK"; "US"; "UK"] }
    { Name = "Tomas"; Age = 28; Countries = [ "CZ"; "UK"; "US"; "CZ" ] }
    { Name = "Eve"; Age = 2; Countries = [ "FR" ] }
    { Name = "Suzanne"; Age = 15; Countries = [ "US" ] } ]

// Turn the list of records into data frame 
let peopleList = Frame.ofRecords peopleRecds
// Use the 'Name' column as a key (of type string)
let people = peopleList |> Frame.indexRowsString "Name"

How do i access the value the row for Joe ? (as a record, tuple or whatever format)

i tried this

getRow "Joe" people;;

 

Stopped due to error  System.Exception: Operation could not be completed due to earlier error  Value restriction. The value 'it' has been inferred to have generic type   val it : Series  Either define 'it' as a simple data term, make it a function with explicit arguments or, if you do not intend for it to be generic, add a type annotation. at 3,0

EDIT: thanks for the answer, still i would like to know why my syntax is incorrect because i think i respected the signature

val it :
   ('a -> Frame<'a,'b> -> Series<'b,'c>) when 'a : equality and 'b : equality
like image 783
Fagui Curtain Avatar asked Mar 07 '17 07:03

Fagui Curtain


2 Answers

I'll answer the second half of your question, why you got a "value restriction" error. If you search for [f#] value restriction on Stack Overflow you'll find lots of answers, which may or may not confuse you. But the really short version is: F# is built on top of the .Net framework, and .Net imposes certain limitations. Specifically, functions are allowed to be generic, but values cannot be generic. So you can do this:

let f<'TData> (a:'TData) = printfn "%A" a

but you cannot do this:

let (a:'TData) = Unchecked.defaultof<'TData>

The function definition is fine, because the underlying .Net framework knows how to handle generic functions. But you're not allowed to have generic values in .Net; any value must be a specific type.

(Note: I wrote the <'TData> in the f definition explicitly, but I didn't have to: I could have just written let f (a:'TData) = printfn "%A" a and the genericness of f would have still been understood. I could even have just written let f a = printfn "%A" a, and it would have done the same thing).

Now let's look at the error you got: "the value "it" has been inferred to have generic type val it : Series<string,obj>". If you look at the function signature of getRow that you posted, it looks like this:

('a -> Frame<'a,'b> -> Series<'b,'c>)

When you called it as getRow "Joe" people, the F# compiler was able to infer that the type 'a was string (because the parameter "Joe" is a string). And because the second argument people is a Frame<string,string>, the F# compiler was able to infer that the type 'b was also string. But the result of that function call is a Series<'b,'c>, and so far the F# compiler doesn't know anything about what 'c will be. And since you ran getRow "Joe" people at the F# interactive REPL, it tried to store the result of what you typed as the value of the name it (the F# interactive REPL always provides the value of the previous expression as it) -- but since the only type it knew so far was Series<string,'c>, F# couldn't figure out what specific type to assign to the value it. I know from looking at your code that the type 'c was the Person record, but the F# compiler couldn't know that from just that one call to getRow, because of how the getRow function is typed.

There are two ways you could have solved this value restriction error:

  1. One way to solve this would have been to pipe the result of getRow into another function, which would have allowed the F# compiler to infer the specific type of its result. Unfortunately, since I don't know Deedle that well, I can't give you a good example here. Maybe someone else will come up with one and comment on this answer, and I'll edit it in. It would look like:

    getRow "Joe" people |> (some Deedle function)
    

    But I don't know which Deedle function to use in my example: it would have to be a function that takes a Series and does some specific calculation with it, in a way that would allow F# to infer that this is a Series<string,Person>. Sorry this isn't a great example, but I'll leave it in anyway in case it helps.

  2. The second way you could have solved the error would have been to specify the type of the value you were getting. In F#, you do that with the : (type) syntax, e.g.:

    getRow "Joe" people : Series<string,Person>
    

    Or, since the F# compiler has enough information to infer the string part of that type, you could also have written:

    getRow "Joe" people : Series<_,Person>
    

    When you write _ in a type signature, you're telling the F# compiler "You figure out what type this is". This only works when the F# compiler has enough information to infer that type correctly, but it's often a handy shorthand when type signatures would be large and unwieldy.

Both of these approaches would have solved your immediate problem, gotten rid of the "value restriction" error, and allowed you to continue working.

I hope this answer helps you. If it hopelessly confuses you instead, let me know and I'll see if I can explain whatever has you confused.

EDIT: In the comments, Soldalma asks whether the F# compiler (which is a one-pass compiler that works top to bottom and left to right) can infer the type from a forward pipe. The answer is yes, because the expression isn't finished yet. As long as an expression isn't finished, F#'s type inference (which is based on the Hindley-Milner type system*) is fine with carrying around a set of not-yet-resolved types. And if the types are resolved before the expression is complete, then the expression can resolve to a specific value (or a specific function). If the types are not yet resolved when the expression is complete, then it has to resolve to a generic value or function. And generic functions are allowed in .Net, but not generic values, hence the "value restriction" error.

To see this in practice, let's look at some example code. Copy and paste the following code into an F# editor that lets you hover over a variable (or function) name to see its type. I recommend VS Code with the Ionide-fsharp extension since it's cross-platform, but Visual Studio will work just as well.

open System.Collections.Generic

let mkDict (key:'K) = new Dictionary<'K,'V>() // Legal

let getValueOrDefault (key:'a) (defaultVal:'b) (dict:Dictionary<'a,'b>) =
    match dict.TryGetValue key with
    | true,v -> v
    | false,_ -> defaultVal

let d = mkDict "foo" // Error: value restriction
let bar = mkDict "foo" |> getValueOrDefault "foo" "bar" // Legal: type string
let five = mkDict "foo" |> getValueOrDefault "foo" 5 // Legal: type int

Go ahead and hover your cursor over each function and variable name to see its type, or else hit Alt+Enter to send each function or variable declaration to F# Interactive. (And once you've seen that the let d line gives a "value restriction" error, comment it out so the rest of the code will compile).

What's happening here is a good demonstration of how this all works. The mkDict function has two unresolved types, 'K and 'V, so it has to be generic. But that's fine, because .Net has no problem with generic functions. (mkDict isn't actually very useful, since it actually "throws away" the data of its argument and does nothing to it. But it's supposed to be a trivial example, so just ignore the fact that it's kind of useless.) Likewise, getValueOrDefault has two unresolved types, 'a and 'b, so it's also a generic function.

However, let d = mkDict "foo" is not legal. Here, the generic type 'K has been resolved to be the specific type string, but 'V has not yet been resolved by the time the expression is complete so d would have to be generic (it would look like d<'V> in explicitly-generic syntax). But d is not a function (since it has no parameters), it's the name of a value, and .Net doesn't allow generic values.

But in the next two lines, the expression is not complete by the time the compiler has parsed mkDict "foo", so it doesn't yet have to "lock in" the unknown types. It can quite happily carry the unresolved type 'V into the next part of the expression. And there, the getValueOrDefault function has two specific types, string and string in the first line, and string and int in the second line. Because its 'b type corresponds to the 'V type from mkDict, therefore F# can resolve 'V in both lines. And so bar has type string, and five has type int.

* Scott Wlaschin says that it should "more accurately ... be called "Damas-Milner's Algorithm W" ". Since I haven't studied it in detail myself, I'll take his word for it -- but if you're interested in learning more, the Wikipedia link I provided is probably a halfway decent starting point.

like image 131
rmunn Avatar answered Nov 15 '22 12:11

rmunn


I'll be very short, promoting my comment to an answer. You need to use syntax reverse to the one you have tried: people.Rows.["Joe"]

like image 34
Oleg Bogdanov Avatar answered Nov 15 '22 13:11

Oleg Bogdanov