davidtme
davidtme

Reputation: 71

F# Type mismatch on curried functions

I'm having a bit of trouble with the following FSharp/F# code:

module File1

let api a =
    printf ("VALUE = %A") a

let router ops =
    [|
       api (ops (fun (list, _) -> list()))
       api (ops (fun (_, get) -> get 1))
    |]

let withContext ops handler =
    let context = "CONTEXT"
    handler (ops context)

let operations context =
    printf ("CONTEXT = %s") context

    let list () = [|1;2;3|]
    let get id = "Test"
    (list, get)

let setup() =
    let ops = withContext operations
    router ops

Results in the following error

Results in the following compation error
Error   1   Type mismatch. Expecting a
    ((unit -> int []) * (int -> int []) -> int []) -> 'a    
but given a
    ((unit -> int []) * (int -> string) -> 'b) -> 'b    
The type 'int []' does not match the type 'string'

I know the problem is that ops function has been bound to return a int[] but I want to be able to also return a string.

I think I'm missing a trick with some generic declarations but after hours of moving code around I can’t seem to work it out.

(I've simplified the code to highlight my problem)

Upvotes: 3

Views: 138

Answers (2)

kvb
kvb

Reputation: 55184

Your code is hard for me to understand, but I think the basic issue is that you want withContext to have a "rank 2" type (so that the universal quantification of the type variable 'b can happen after the application of the first argument). In F#, this can be accomplished by creating a new type with a generic method and using that:

let api a =
    printf ("VALUE = %A") a

type Handler<'a> = abstract Handle<'b> : f:('a->'b) -> 'b

let router (ops:Handler<_>) =
    [|
       api (ops.Handle (fun (list, _) -> list()))
       api (ops.Handle (fun (_, get) -> get 1))
    |]
let withContext ops =
    let context = "CONTEXT"
    { new Handler<_> with member __.Handle f = f (ops context) }

let operations context =
    printf ("CONTEXT = %s") context

    let list () = [|1;2;3|]
    let get id = "Test"
    (list, get)

let setup() =
    let ops = withContext operations
    router ops

Upvotes: 1

Paweł Jasiurkowski
Paweł Jasiurkowski

Reputation: 317

The error is because ops needs to have a return type of its handler resolved at compilation, and you want to return different types base on some run-time logic.

It is basically an equivalent of:

let fun1 switch arg2 arg3 =
  if switch then
     arg2
  else
     arg3

and you want to run it this way:

fun1 true 1 "string"

Of course, arg2 and arg3 need to have the same type, so it won't work

What you can do is to run "api" function on a handler result, before returning it (so it will always the same type - unit).

let router ops =
    [|
        ops (fun (list, _) -> api <| list()))
        ops (fun (_, get) -> api <| get 1))
    |]

Alternatively, you could return objects of discriminated union type (then you will need some more logic in api function).

(Technically, you could also return obj).

Bonus

You don't need the array of units to be returned in a router function, returning one unit is just fine:

let router ops =
    ops (fun (list, _) -> api <| list()))
    ops (fun (_, get) -> api <| get 1))

In this way, setup function will also return unit and you will be able to run it without a need to run ignore on the result to get rid of This expression should have type 'unit', but has type 'unit[]' warning.

Upvotes: 2

Related Questions