1
votes

I'm reading Expert F# book and I found this code

open System.Collections.Generic
let divideIntoEquivalenceClasses keyf seq =
// The dictionary to hold the equivalence classes
  let dict = new Dictionary<'key,ResizeArray<'T>>()
  // Build the groupings
  seq |> Seq.iter (fun v ->
                          let key = keyf v
                          let ok,prev = dict.TryGetValue(key)
                          if ok then prev.Add(v)
                          else let prev = new ResizeArray<'T>()
                             dict.[key] <- prev
                             prev.Add(v))

 dict |> Seq.map (fun group -> group.Key, Seq.readonly group.Value)

and the example use:

> divideIntoEquivalenceClasses (fun n -> n % 3) [ 0 .. 10 ];;
val it : seq<int * seq<int>>
= seq [(0, seq [0; 3; 6; 9]); (1, seq [1; 4; 7; 10]); (2, seq [2; 5; 8])]

first for me this code is really ugly, even if this is safe, It looks more similar to imperative languages than to functional lang..specially compared to clojure. But the problem is not this...I'm having problems with the Dictionary definition

when I type this:

let dict = new Dictionary<'key,ResizeArray<'T>>();;

I get this:

pruebafs2a.fs(32,5): error FS0030: Value restriction. The value 'dict' has been inferred to have generic type
val dict : Dictionary<'_key,ResizeArray<'_T>> when '_key : equality    

Either define 'dict' 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.

is It ok?...

thanks so much


improve question:

Ok I've been reading about value restriction and I found this helpfull information

In particular, only function definitions and simple immutable data expressions are automatically generalized

...ok..this explains why

let dict = new Dictionary<'key,ResizeArray<'T>>();;

doesn't work...and show 4 different techniques, although in my opinion they only resolve the error but aren't solutions for use generic code:

Technique 1: Constrain Values to Be Nongeneric

 let empties : int list [] = Array.create 100 []

Technique 3: Add Dummy Arguments to Generic Functions When Necessary

let empties () = Array.create 100 []
let intEmpties : int list [] = empties()   

Technique 4: Add Explicit Type Arguments When Necessary (similar to tec 3)

let emptyLists = Seq.init 100 (fun _ -> [])
> emptyLists<int>;;
val it : seq<int list> = seq [[]; []; []; []; ...]

----- and the only one than let me use real generic code ------ Technique 2: Ensure Generic Functions Have Explicit Arguments

let mapFirst = List.map fst //doesn't work
let mapFirst inp = List.map fst inp

Ok, in 3 of 4 techniques I need resolve the generic code before can work with this...now...returning to book example...when the compile knows the value for 'key and 'T

let dict = new Dictionary<'key,ResizeArray<'T>>()

in the scope the code is very generic for let key be any type, the same happen with 'T

and the biggest dummy question is :

when I enclose the code in a function (technique 3):

let empties = Array.create 100 [] //doesn't work
let empties () = Array.create 100 []
val empties : unit -> 'a list []

I need define the type before begin use it
let intEmpties : int list [] = empties() 

for me (admittedly I'm a little dummy with static type languages) this is not real generic because it can't infer the type when I use it, I need define the type and then pass values (not define its type based in the passed values) exist other way define type without be so explicit..

thanks so much..really appreciate any help

4
Note on the update - it is very rare to run in to value restriction errors in real code, the inferrence can usually pick things up from usage - John Palmer

4 Answers

1
votes

This line

let dict = new Dictionary<'key,ResizeArray<'T>>();;

fails because when you type the ;; the compiler doesn't know what 'key and 'T are. As the error message states you need to add a type annotation, or allow the compiler to infer the type by using it later or make it a function

Examples

Type annotation change

let dict = new Dictionary<int,ResizeArray<int>>();;

Using types later

let dict = new Dictionary<'key,ResizeArray<'T>>()
dict.[1] <- 2

using a function

let dict() = new Dictionary<'key,ResizeArray<'T>>();;
1
votes

This actually doesn't cause an issue when it's defined all together. That is, select the entire block that you posted and send it to FSI in one go. I get this:

val divideIntoEquivalenceClasses :
  ('T -> 'key) -> seq<'T> -> seq<'key * seq<'T>> when 'key : equality

However, if you type these individually into FSI then as John Palmer says there is not enough information in that isolated line for the interpreter to determine the type constraints. John's suggestions will work, but the original code is doing it correctly - defining the variable and using it in the same scope so that the types can be inferred.

1
votes

for me this code is really ugly, even if this is safe, It looks more similar to imperative languages than to functional lang.

I agree completely – it's slightly tangential to your direct question, but I think a more idiomatic (functional) approach would be:

let divideIntoEquivalenceClasses keyf seq =
    (System.Collections.Generic.Dictionary(), seq)
    ||> Seq.fold (fun dict v ->
        let key = keyf v
        match dict.TryGetValue key with
        | false, _ -> dict.Add (key, ResizeArray(Seq.singleton v))
        | _, prev  -> prev.Add v
        dict)
    |> Seq.map (function KeyValue (k, v) -> k, Seq.readonly v) 

This allows sufficient type inference to obviate the need for your question in the first place.

0
votes

The workarounds proposed by the other answers are all good. Just to clarify based on your latest updates, let's consider two blocks of code:

let empties = Array.create 100 []

as opposed to:

let empties = Array.create 100 []
empties.[0] <- [1]

In the second case, the compiler can infer that empties : int list [], because we are inserting an int list into the array in the second line, which constrains the element type.

It sounds like you'd like the compiler to infer a generic value empties : 'a list [] in the first case, but this would be unsound. Consider what would happen if the compiler did that and we then entered the following two lines in another batch:

empties.[0] <- [1] // treat 'a list [] as int list []
List.iter (printfn "%s") empties.[0] // treat 'a list [] as string list []

Each of these lines unifies the generic type parameter 'a with a different concrete type (int and string). Either of these unifications is fine in isolation, but they are incompatible with each other and would result in treating the int value 1 inserted by the first line as a string when the second line is executed, which is clearly a violation of type safety.

Contrast this with an empty list, which really is generic:

let empty = []

Then in this case, the compiler does infer empty : 'a list, because it's safe to treat empty as a list of different types in different locations in your code without ever impacting type safety:

let l1 : int list = empty
let l2 : string list = empty
let l3 = 'a' :: empty

In the case where you make empties the return value of a generic function:

let empties() = Array.create 100 []

it is again safe to infer a generic type, since if we try our problematic scenario from before:

empties().[0] <- [1]
List.iter (printfn "%s") (empties().[0])

we are creating a new array on each line, so the types can be different without breaking the type system. Hopefully this helps explain the reasons behind the limitation a bit more.