Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulating an 'Any' type in F#

Tags:

f#

I'd like to create a type with a definition a bit like this:

type LeftRight<'left, 'right> = {
    Left : 'left list
    Right : 'right list
    }

and a couple of functions:

let makeLeft xs = { Left = xs; Right = [] }

let makeRight ys = { Left = []; Right = ys }

and I'd like to provide a 'combiner' function:

let combine l r = { Left = l.Left @ r.Left; Right = l.Right @ r.Right }

When I try and make something, I (obviously!) get issues as my value is generic:

let aaa = makeLeft [1;2;3]
// Value restriction. The value 'aaa' has been inferred to have generic type
// val aaa : LeftRight<int,'_a>    

If I combine a left and a right, type inference kicks in and everything's A-OK:

let bbb = makeRight [1.0;2.0;3.0]
let comb = combine aaa bbb // LeftRight<int, float>

but I want to use one with only lefts on its own. I tried creating an 'Any' type:

type Any = Any

and explicitly specified the types on makeLeft and makeRight:

let makeLeft xs : LeftRight<_, Any> = { Left = xs; Right = [] }

let makeRight ys : LeftRight<Any, _> = { Left = []; Right = ys }

which makes the value definitions happy, but makes the combine function sad:

let combined = combine aaa bbb
// Type mismatch. Expecting a
//     LeftRight<int,Any>    
// but given a
//     LeftRight<Any,float>    
// The type 'int' does not match the type 'Any'

I feel like there's probably a way around this with loads of voodoo with .Net's overloading of function calls, but I can't make it work. Has anyone tried this before/have any ideas?

like image 282
Matt Kemp Avatar asked Oct 06 '16 14:10

Matt Kemp


1 Answers

The value restriction is not a problem in this case, you need the result of makeLeft or makeRight be generic if you ever hope to use them generically further down the line.

In F# (and OCaml), generic syntactic values must be explicitly marked as such, with full type annotations. Indeed, the compiler reports this:

error FS0030: Value restriction. The value 'aaa' has been inferred to have generic type val aaa : LeftRight Either define 'aaa' 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.

Without going into too much detail*, this is to avoid issues that can occur when combining polymorphism and side effects. The downside is that it does reject some perfectly safe code as a result.

So, the solution is simple, we make these values explicitly generic:

let aaa<'a> : LeftRight<int,'a> = makeLeft [1;2;3]

let bbb<'a> : LeftRight<'a, float> = makeRight [1.0;2.0;3.0]

Putting them together in FSI:

let comb = combine aaa bbb;;;
val comb : LeftRight<int,float> = {Left = [1; 2; 3];
                               Right = [1.0; 2.0; 3.0];}

Note that if you combine without intermediate let bindings, you no longer have a generic value and the proper type can be inferred by the compiler:

combine (makeLeft [1;2;3]) (makeRight [1.0;2.0;3.0]);;
val it : LeftRight<int,float> = {Left = [1; 2; 3];
                                 Right = [1.0; 2.0; 3.0];}

*For more detail, check out this article.

like image 52
TheInnerLight Avatar answered Oct 05 '22 18:10

TheInnerLight