4
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.

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Marko Grdinić
  • 3,798
  • 3
  • 18
  • 21
  • I assume you explored and rejected method overloading and active patterns for some reason but would be interested to know why. composability? type inference? – s952163 Dec 26 '16 at 23:14
  • 1
    To be honest, what I really need right now are GADTs in order to compose operations for my library in a type safe manner and what you see here are just failed attempts at getting the required polymorphism. I could do it with just functions and I do not really need the polymorphism...except I might need it and I am terrified of committing as it would rigidify the structure of the library too much. With union types on the other hand, I would get the ability to do lookahead due to pattern matching and easy context dependent execution, but I would be working in a kind of dynamic DSL inside F#. – Marko Grdinić Dec 27 '16 at 08:44
  • 1
    Without impredicative polymorphism those `inline` functions really are kind of like C functions in the sense that they just stand at the top and you can call them, but you can't pass them inside lambdas without risking a type error. In the past iteration of the library, I kind of used them pretty stupidly too and got bit by this. I think I'll try butting the bullet and using DUs in a dynamic manner - without generic parameters. – Marko Grdinić Dec 27 '16 at 08:52
  • 1
    Also, I do not think it needs to be said, but DUs have a duality with functions in the sense that they are just functions with the return type left out, so that is where polymorphism comes into play with them. I have difficult choices at every turn it seems. – Marko Grdinić Dec 27 '16 at 08:53

4 Answers4

4

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
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 2
    Your reply is good given what I asked, but reading it made me realize that I did not explain the problem is sufficient depth. Please see the edit. – Marko Grdinić Dec 25 '16 at 17:10
3

The compiler ends up defaulting to int, and the kind of polymorphism you want is difficult to achieve in F#. This article articulates the point.

Perhaps, you could work the dark arts using FSharp.Interop.Dynamic but you lose compile time checking which sort of defeats the point.

Asti
  • 12,447
  • 29
  • 38
  • 1
    I do not think the stuff in the article is the kind of polymorphism I am trying to get here. Just now I tried translating the example straightforwardly to Haskell, and like in F#, it gives the same error there. I think what I am asking here for is [impredicative polymorphism](https://ghc.haskell.org/trac/ghc/wiki/ImpredicativePolymorphism). That page, like a lot of Haskell stuff reads like gibberish to me, but what I need to make the program in my answer is probably that. I doubt it will happen anytime soon. – Marko Grdinić Dec 26 '16 at 14:22
2

I've come to the conclusion that what I am trying to is impossible. I had a hunch that it was already, but the proof is in the following:

let vale (x,_,_) = x
let adde (_,x,_) = x
let multe (_,_,x) = x

let val_ x d =
    let f = vale d
    f x

let add x y d =
    let f = adde d
    f (x d) (y d)

let mult x y d =
    let f = multe d
    f (x d) (y d)

let in_1 =
    let val_ (x: int) = x
    let add x y = x+y
    let mult x y = x*y
    val_,add,mult

let in_2 =
    let val_ (x: int) = string x
    let add x y = sprintf "(%s + %s)" x y
    let mult x y = sprintf "(%s * %s)" x y
    val_,add,mult

let r2 d = add (val_ 1) (val_ 3) d

//let test x = x in_1, x in_2 // Type error.

let a2 = r2 in_1 // Works
let b2 = r2 in_2 // Works

The reasoning goes that if it cannot be done with plain functions passed as arguments, then it definitely won't be possible with interfaces, records, discriminated unions or any other scheme. The standard functions are more generic than any of the above, and if they cannot do it then this is a fundamental limitation of the language.

It is not the lack of HKTs that make the code ungeneric, but something as simple as this. In fact, going by the Finally Tagless paper linked to in the Reddit post, Haskell has the same problem with needing to duplicate interpreters without the impredicative types extension - though I've looked around and it seem that impredicative types will be removed in the future as the extension is difficult to maintain.

Nevertheless, I do hope this is only a current limitation of F#. If the language was dynamic, the code segment above would in fact run correctly.

Marko Grdinić
  • 3,798
  • 3
  • 18
  • 21
2

Unfortunately, it's not completely clear to me what you're trying to do. However, it seems likely that it's possible by creating an interface with a generic method. For example, here's how you could get the code from your answer to work:

type I = abstract Apply : ((int -> 'a) * ('a -> 'a -> 'a) * ('a -> 'a -> 'a)) -> 'a

//let test x = x in_1, x in_2 // Type error.
let test (i:I) = i.Apply in_1, i.Apply in_2

let r2' = { new I with member __.Apply d = add (val_ 1) (val_ 3) d }
test r2' // no problem

If you want to use a value (e.g. a function input) generically, then in most cases the cleanest way is to create an interface with a generic method whose signature expresses the required polymorphism.

Community
  • 1
  • 1
kvb
  • 54,864
  • 2
  • 91
  • 133
  • The trouble is that with DUs (and similarly for records and interfaces) you can't really write something like Exp<'a> -> Exp<'b> -> ('a -> 'b -> 'c) -> Exp<'c>. The reason is that the above would require 3 generic type parameters when you only have one. On the other hand, that kind of signature would be very much possible with GADTs and with vanilla functions - except vanilla functions would have an extra return argument at the end. At any rate, despite what I wrote in reply to s952163, I decided to go with functions after all. I figured out how to do pattern matching in a functional manner... – Marko Grdinić Dec 29 '16 at 07:33
  • ...for any kind of data structure represented by a closure (which I will use for NN layers.) It can be done similarly as with parser combinators. The trick is to define a separate mini-DSL for pattern matching on that structure and pass along the resulting function from that via a continuation. This would actually be more ideal for my use case as I would not have redundant types floating around everywhere for each and every kind of layer and operation. It would not be as concise as regular pattern matching, but it would be much better than doing everything by hand. – Marko Grdinić Dec 29 '16 at 07:37