views:

259

answers:

3

Does "Value Restriction" practically mean that there is no higher order functional programming?

I have a problem that each time I try to do a bit of HOP I get caught by a VR error. Example:

let simple (s:string)= fun rq->1 
let oops= simple ""

type 'a SimpleType= F of (int ->'a-> 'a)
let get a = F(fun req -> id)  
let oops2= get ""

and I would like to know whether it is a problem of a prticular implementation of VR or it is a general problem that has no solution in a mutable type-infered language that doesn't include mutation in the type system.

+1  A: 

I didn't know the details of the value restriction, so I searched and found this article. Here is the relevant part:

Obviously, we aren't going to write the expression rev [] in a program, so it doesn't particularly matter that it isn't polymorphic. But what if we create a function using a function call? With curried functions, we do this all the time:

- val revlists = map rev;

Here revlists should be polymorphic, but the value restriction messes us up:

- val revlists = map rev;
stdIn:32.1-32.23 Warning: type vars not generalized because of
   value restriction are instantiated to dummy types (X1,X2,...)
val revlists = fn : ?.X1 list list -> ?.X1 list list

Fortunately, there is a simple trick that we can use to make revlists polymorphic. We can replace the definition of revlists with

- val revlists = (fn xs => map rev xs);
val revlists = fn : 'a list list -> 'a list list

and now everything works just fine, since (fn xs => map rev xs) is a syntactic value. (Equivalently, we could have used the more common fun syntax:

- fun revlists xs = map rev xs;
val revlists = fn : 'a list list -> 'a list list

with the same result.) In the literature, the trick of replacing a function-valued expression e with (fn x => e x) is known as eta expansion. It has been found empirically that eta expansion usually suffices for dealing with the value restriction.

To summarise, it doesn't look like higher-order programming is restricted so much as point-free programming. This might explain some of the trouble I have when translating Haskell code to F#.


Edit: Specifically, here's how to fix your first example:

let simple (s:string)= fun rq->1 
let oops= (fun x -> simple "" x)     (* eta-expand oops *)

type 'a SimpleType= F of (int ->'a-> 'a)
let get a = F(fun req -> id)
let oops2= get ""

I haven't figured out the second one yet because the type constructor is getting in the way.

Nathan Sanders
but my second example is not point free, just simple function in a type. And I can NOT do eta reduction (and I find it ugly anyway) :s
Sadache
`(fun req -> id)` is missing some arguments: `(fun req x -> id x)` is the eta-expanded version. Both funs are still higher order functions though. (But the second doesn't solve the value restriction, at least in OCaml.)
Nathan Sanders
yes, it doesn't solve the VR. So it is not about point free but rather about functions that return functions.
Sadache
+3  A: 
Norman Ramsey
Although what you said about VR is true in general, you didn't answer OP's question. First, eta-expanding oops2 isn't going to work if you read the other answer or tried the program yourself. Second, I think OP already understood the reason behind this restriction, and he wanted to know if there's any better solution.
Wei Hu
@Wei that's because I totally didn't understand OP's question (and still don't). But I have tried to clarify.
Norman Ramsey
`get` is a unary function, so you cannot write `let oops2 x = get "" x`.
Wei Hu
@Wei: Thanks for catching my error. I've fixed the `oops2` example and tested it now.
Norman Ramsey
"In practice the value restriction presents no barrier to the definition and use of higher-order functions; you just eta-expand"I am sorry but the use of "just" in the end of this sentence here is premature. Each time I want to do something that makes sense in types I have to find a hack that breaks any fluency in my api. I am sorry but i can not ask users of my api: Please do eta reduction and unpack pack your values.Can someone else see that this is non sense?
Sadache
you start explaining FP by "Function is a normal value that you can receive as an argument and return from another function" But hey, you do eta-reduction if you want to return a polymorphic function as a value. And worse it is inside a data structure.If you can't think of an example where it would make sense this doesn't mean that in practice it is OK.
Sadache
and I added the data structure not because i like to complicate things, take for instance type 'a Interesting = ('a->'a)*int Optionlet setI_andDoNothing i= Some (id,i)ans somewhere in the user's codelet x= setI_andDoNothing 1
Sadache
especially when i am not planning to expose data constructures outside modules.
Sadache
+2  A: 

Here is the answer to this question in the context of F#. To summarize, in F# passing a type argument to a generic (=polymorphic) function is a run-time operation, so it is actually type-safe to generalize (as in, you will not crash at runtime). The behaviour of thusly generalized value can be surprising though.

For this particular example in F#, one can recover generalization with a type annotation and an explicit type parameter:

type 'a SimpleType= F of (int ->'a-> 'a)
let get a = F(fun req -> id)  
let oops2<'T> : 'T SimpleType = get ""
Mitya