Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to make this continuation passing with codata example work in F#?

type Interpreter<'a> =
| RegularInterpreter of (int -> 'a)
| StringInterpreter of (string -> 'a)

let add<'a> (x: 'a) (y: 'a) (in_: Interpreter<'a>): 'a = 
    match in_ with
    | RegularInterpreter r -> 
        x+y |> r
    | StringInterpreter r -> 
        sprintf "(%s + %s)" x y |> r

The error message of it not being able to resolve 'a at compile time is pretty clear to me. I am guessing that the answer to the question of whether it is possible to make the above work is no, short of adding functions directly into the datatype. But then I might as well use an interface, or get rid of generic parameters entirely.

Edit: Mark's reply does in fact do what I asked, but let me extend the question as I did not explain it adequately. What I am trying to do is do with the technique above is imitate what what was done in this post. The motivation for this is to avoid inlined functions as they have poor composability - they can't be passed as lambdas without having their generic arguments specialized.

I was hoping that I might be able to work around it by passing an union type with a generic argument into a closure, but...

type Interpreter<'a> =
| RegularInterpreter of (int -> 'a)
| StringInterpreter of (string -> 'a)

let val_ x in_ =
    match in_ with
    | RegularInterpreter r -> r x
    | StringInterpreter r -> r (string x)

let inline add x y in_ = 
    match in_ with
    | RegularInterpreter r -> 
        x in_ + y in_ |> r
    | StringInterpreter r -> 
        sprintf "(%A + %A)" (x in_) (y in_) |> r

let inline mult x y in_ = 
    match in_ with
    | RegularInterpreter r -> 
        x in_ * y in_ |> r
    | StringInterpreter r -> 
        sprintf "(%A * %A)" (x in_) (y in_) |> r

let inline r2 in_ = add (val_ 1) (val_ 3) in_

r2 (RegularInterpreter id)
r2 (StringInterpreter id) // Type error.

This last line gives a type error. Is there a way around this? Though I'd prefer the functions to not be inlined due to the limits they place on composability.

like image 778
Marko Grdinić Avatar asked Dec 18 '22 10:12

Marko Grdinić


1 Answers

Remove the type annotations:

let inline add x y in_ = 
    match in_ with
    | RegularInterpreter r -> 
        x + y |> r
    | StringInterpreter r -> 
        sprintf "(%A + %A)" x y |> r

You'll also need to make a few other changes, which I've also incorporated above:

  • Change the format specifiers used with sprintf to something more generic. When you use %s, you're saying that the argument for that placeholder must be a string, so the compiler would infer x and y to be string values.
  • Add the inline keyword.

With these changes, the inferred type of add is now:

x: ^a -> y: ^b -> in_:Interpreter<'c> -> 'c
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b -> int)

You'll notice that it works for any type where + is defined as turning the input arguments into int. In practice, that's probably going to mean only int itself, unless you define a custom operator.

FSI smoke tests:

> add 3 2 (RegularInterpreter id);;
val it : int = 5
> add 2 3 (StringInterpreter (fun _ -> 42));;
val it : int = 42
like image 175
Mark Seemann Avatar answered May 16 '23 08:05

Mark Seemann