haskellfunctional-programmingf#type-systems

F# error: "Either make the arguments to 'it' explicit or, if you do not intend for it to be generic, add a type annotation."


In F# interactive shell dotnet fsi, I'm trying to test flip function as in Haskell,

flip :: (a -> b -> c) -> b -> a -> c

> let flip = fun f -> fun a -> fun b -> f(b)(a);;
val flip: f: ('a -> 'b -> 'c) -> a: 'b -> b: 'a -> 'c

then, investigating the built-in pipe-operator,

> (|>);;
val it: ('a -> ('a -> 'b) -> 'b)

so far so good.

Now,

> flip (|>);;

  flip (|>);;
  ^^^^^^^^^

/..... : error FS0030: Value restriction. The value 'it' has been inferred to have generic type
    val it: (('_a -> '_b) -> '_a -> '_b)    
Either make the arguments to 'it' explicit or, if you do not intend for it to be generic, add a type annotation.

Can someone explain what's going on with this error in the F# type system?

To me,

val it: (('_a -> '_b) -> '_a -> '_b) should be actually the expected result, and how can I sort this out? Thanks.


Solution

  • My previous answer explains what Value Restriction is and why it happens. But now your next question is: ok, so what do I do about it?

    As the error message itself suggests, there are two possible ways: (1) give it explicit arguments or (2) if you don't want it to be generic, add a type annotation.

    1. Explicit arguments

    let flippedPipe x = flip (|>) x
    

    Even though logically this is the same as let flippedPipe = flip (|>), syntactically this is now a function, not a value. And syntax is what matters for the Value Restriction.

    2. A type annotation

    let flippedPipe : (int -> int) -> int -> int = flip (|>)
    

    This works because the function is no longer generic, so the Value Restriction does not apply. This is a desirable option in many circumstances, but judging by the kinds of functions you're working with here, I would assume this is not what you wanted in this case.

    3. Explicit type parameters

    The error message doesn't mention this, and in fairness, it can be considered a variation of option (2). The idea is that you can give your function explicit type parameters like this:

    let flippedPipe<'a, 'b> = flip (|>)
    

    This makes the Value Restriction go away, because, even though it still technically applies, the fact that you added the type parameters presumably shows that you know what you're doing, so the compiler shuts up.

    However, though this appears to work at first glance, it does so in the wrong way. If you look at the inferred type of this function, you'll see:

    val flippedPipe : (obj -> obj) -> obj  -> obj
    

    This happens because, even though you added type parameters, you didn't specify exactly where they go. They may not be used at all (aka "phantom types") for all the compiler knows.

    So to make it work properly, this should be the definition:

    let flippedPipe<'a, 'b> : ('a -> 'b) -> 'a -> 'b = flip (|>)