11

For the background: It's a variation on functional DI. Following Scott's post I wrote an interpreter. The twist is that my interpreter is generic and parametrized based on what you feed to it.

For testing purposes I'd like to pass another interpreter in, and therein lies the rub - how can I? Here's the simplified outline of the problem:

let y f =
    let a = f 1
    let b = f 2L
    (a,b)

f is my generic interpreter, but here it is obviously constrained by the first use to int -> 'a. In this simplified scenario I could just pass the interpreter twice, but in my actual implementation the type space is rather large (base type x3 output types).

Is there some F# mechanism that would let me do that, w/o too much overhead?

Eugene Tolmachev
  • 822
  • 4
  • 14
  • You can use types with a single method, something similar to delegates but static. If want I can show you how. The other alternative is with interfaces as showed in the answer by @fyodorsoikin – Gus Mar 04 '17 at 17:54
  • 1
    You can't use a static type and/or method. Won't be able to pass it as argument. – Fyodor Soikin Mar 04 '17 at 18:04
  • Yes, you can. I will post an example showing how. – Gus Mar 04 '17 at 18:45
  • Please do. I'm very curious. – Fyodor Soikin Mar 04 '17 at 19:20
  • @FyodorSoikin Although this questions is already marked as answered, I added it as another answer by your request and because I don't have enough space in this comment to show you the code. – Gus Mar 04 '17 at 19:31
  • Actually the interface approach (as it was explained by @FyodorSoikin) has a limitation, you can't change the signature of the abstract member, for instance in the example showed in the answer, changing the member to something different to ``'a->'a`` would not work. Once you choose the signature, for example ``'a->'a`` you have to stick to it, if you add a function like ``{ new Wrapper with member __.f x = string x }`` it won't compile. If this is a problem, see my answer which doesn't have that limitation. – Gus Mar 04 '17 at 20:57
  • @Gostavo: but your approach has another limitation - everything has to be known at compile time. You can't pick different instances of `f` depending on input, "remember" them somewhere (e.g. store in a list, or pass through another chain of functions), and then pass to `y` later. You'd have to call `y` right away. – Fyodor Soikin Mar 04 '17 at 21:54
  • @FyodorSoikin You are correct. I think these limitations on both approaches makes them useful for different scenarios. I use the static approach when I need to generalize over a generic structure. Although I know the dynamic approach since long time, I never used it, but if I ever find a situation where it fits well, I would use it. – Gus Mar 05 '17 at 16:10

4 Answers4

15

You can't do this in F# with functions. Functions lose genericity when passed as values.

However, F# does have a mechanism for doing it anyway, albeit a bit awkwardly: interfaces. Interface methods can be generic, so you can use them to wrap your generic functions:

type Wrapper =
    abstract member f<'a> : 'a -> 'a

let y (w: Wrapper) = 
   let a = w.f 1 
   let b = w.f 2L 
   (a, b)

let genericFn x = x

// Calling y:
y { new Wrapper with member __.f x = genericFn x }

The downside is, you can't go back to higher-order functions, lest you lose genericity. You have to have interfaces all the way down to the turtles. For example, you can't simplify the instance creation by abstracting it as a function:

let mkWrapper f = 
   // no can do: `f` will be constrained to a non-generic type at this point
   { new Wrapper with member __.f x = f x }

But you can provide some convenience on the other side. At least get rid of type annotations:

type Wrapper = abstract member f<'a> (x: 'a): 'a

let callF (w: Wrapper) x = w.f x

let y w = 
   let a = callF w 1 
   let b = callF w 2L 
   (a,b)

(NOTE: there may be minor syntactic mistakes in the above code, as I'm writing on my phone)

Gus
  • 25,839
  • 2
  • 51
  • 76
Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
7

Not sure if you're still interested, since you already accepted an answer, but as @Fyodorsoikin requested it, here's the 'static' way, it all happens at compile time, so no runtime overhead:

let inline y f =
    let a = f $ 1
    let b = f $ 2L
    (a, b)

type Double = Double with static member inline ($) (Double, x) = x + x
type Triple = Triple with static member inline ($) (Triple, x) = x + x + x

type ToList = ToList with static member        ($) (ToList, x) = [x]

let res1 = y Double
let res2 = y Triple
let res3 = y ToList

I use this technique when I need a generic function over arbitrary structures, I use to name the types with a single method 'Invokable'.

UPDATE

To add parameters to the function you add it to the DU, like this:

type Print<'a> = Print of 'a with
    static member inline ($) (Print printer, x) = printer (string x)

let stdout (x:string) = System.Console.WriteLine x
let stderr (x:string) = System.Console.Error.WriteLine x

let res4 = y (Print stdout)
let res5 = y (Print stderr)

This is just a quick and simple sample code but this approach can be refined: you can use a method name instead of an operator, you can avoid having to repeat the DU in the declaration, and you can compose Invokables. If you are interested in these enhancements, let me know. I used a refinement of this approach before in production code and never had any issue.

Gus
  • 25,839
  • 2
  • 51
  • 76
  • 1
    Hey, that's cheating! The function is inline! This wouldn't answer the original question, and even less so seeing how the goal was "dependency injection". – Fyodor Soikin Mar 04 '17 at 19:33
  • @FyodorSoikin Yes, well then you can call me a cheater developer, because I use inline functions every time I need them. – Gus Mar 04 '17 at 19:37
  • 3
    I use them too. Widely. The usage itself is not cheating. Cheating is to use it in the context of this specific question. – Fyodor Soikin Mar 04 '17 at 19:44
  • 1
    @FyodorSoikin Why? The code compiles. You can define another function later for testing purposes as stated in the question and it will work. If you want to stick strict to the question the answer is that there is no answer since it's not possible to pass a single function to get multiple result types. – Gus Mar 04 '17 at 19:48
  • @Gustavo I'd love to be able to do this, but how would you augment this solution if `f` was curried with another argument prior to being passed into `y`? How do you see this working for large domain (let's say 100 types)? – Eugene Tolmachev Mar 04 '17 at 23:16
  • @EugeneTolmachev That's not a problem, this solution supports additional parameters, see the example I added in the update. Whether you use this approach with 2 types of 1000000 types doesn't change anything, precisely one of the two interesting differences with the other approach is that the type of the function is not restricted at all. The other interesting point is that it all happens at compile-time, so you have all compile checks and don't have any runtime penalty. – Gus Mar 05 '17 at 06:06
  • 1
    @FyodorSoikin I am using so much inline functions lately that I pray every night I'll never have an uncought runtime exception as most likely the filename/lineno will be totally out of whack and I will never be able to find the source of it . Also I pray every night for TCs & HKTs and Higher Ranked Types ;-) – robkuz Mar 05 '17 at 11:03
  • @robkuz HKT is not what would help in this case, but [existential types](https://en.wikibooks.org/wiki/Haskell/Existentially_quantified_types) would. And now that I know what to look for, apparently there's this [question](http://stackoverflow.com/questions/16284680/expressing-existential-types-in-f) – Eugene Tolmachev Mar 05 '17 at 14:53
  • @EugeneTolmachev I think robkuz was rather referring to Higher Ranked Types and if so, he's correct, that's the feature that would provide a solution to your problem. – Gus Mar 06 '17 at 07:48
3

Please look at Crates.

Here is a quick snippet describing the crux of what you want to accomplish. I believe this snippet is valuable in it helps teach us how we can formally reason about using F# and other ML type systems, by using mathematical language. In other words, it not only shows you how, it teaches you the deep principle of why it works.

The issue here is that we have reached a fundamental limitation of what is directly expressible in F#. It follows that the trick to simulating universal quantification is, therefore, to avoid ever passing the function around directly, instead hiding the type parameter away such that it cannot be fixed to one particular value by the caller, but how might one do that?

Recall that F# provides access to the .NET object system. What if we made our own class (in the object-oriented sense) and put a generic method on that? We could create instances of that which we could pass around, and hence carry our function with it (in the form of said method)?

// Encoding the function signature...
// val id<'a> : 'a -> 'a
// ...in terms of an interface with a single generic method
type UniversalId = abstract member Eval<'a> : 'a -> 'a

Now we can create an implementation which we can pass around without the type parameter being fixed:

// Here's the boilerplate I warned you about.
// We're implementing the "UniversalId" interface
// by providing the only reasonable implementation.
// Note how 'a isn't visible in the type of id -
// now it can't be locked down against our will!
let id : UniversalId =
  { new UniversalId with
      member __.Eval<'a> (x : 'a) : 'a = x
  }

Now we have a way to simulate a universally quantified function. We can pass id around as a value, and at any point we pick a type 'a to pass to it just as we would any value-level argument.

EXISTENTIAL QUANTIFICATION

There exists a type x, such that…

An existential is a value whose type is unknown statically, either because we have intentionally hidden something that was known, or because the type really is chosen at runtime, e.g. due to reflection. At runtime we can, however, inspect the existential to find the type and value inside.

If we don’t know the concrete type inside an existentially quantified type, how can we safely perform operations on it? Well, we can apply any function which itself can handle a value of any type – i.e. we can apply a universally quantified function!

In other words, existentials can be described in terms of the universals which can be used to operate upon them.

This technique is so useful that it is used in datatype generic programming library TypeShape, which allows you to scrap your boilerplate .NET reflection code, as well as MBrace and FsPickler for "packing existential data types". See Erik Tsarpalis' slides on TypeShape for more on "encoding safe existential unpacking in .NET" and encoding rank-2 types in .NET.

A reflection helper library like TypeShape also intuitively should cover most if not all your use cases: dependency injection needs to implement service location under the hood, and so TypeShape can be thought of as the "primitive combinator library" for building dependencies to inject. See the slides starting with Arbitrary Type Shapes: In particular, note the Code Lens data type:

type Lens<'T,'F> =
{
    Get : 'T -> 'F
    Set : 'T -> 'F -> 'T
}

Finally, for more ideas, you may care to read Don Stewart's PhD dissertation, Dynamic Extension of Typed Functional Languages.

We present a solution to the problem of dynamic extension in statically typed functional languages with type erasure. The presented solution retains the benefits of static checking, including type safety, aggressive optimizations, and native code compilation of components, while allowing extensibility of programs at runtime.

Our approach is based on a framework for dynamic extension in a statically typed setting, combining dynamic linking, runtime type checking, first class modules and code hot swapping. We show that this framework is sufficient to allow a broad class of dynamic extension capabilities in any statically typed functional language with type erasure semantics.

Uniquely, we employ the full compile-time type system to perform runtime type checking of dynamic components, and emphasize the use of native code extension to ensure that the performance benefits of static typing are retained in a dynamic environment. We also develop the concept of fully dynamic software architectures, where the static core is minimal and all code is hot swappable. Benefits of the approach include hot swappable code and sophisticated application extension via embedded domain specific languages.

Here are some coarse-grained design patterns Don lays out for future engineers to follow:

  1. Section 3.6.3: Specializing Simulators Approach.
    • Demonstrates how to apply program specialization techniques to Monte-Carlo simulation of polymer chemistry. This approach demonstrates how you can "inject" specialized code to address so-called "peephole optimizations".

and a general chart to help frame the "tower of extensibility":

Don Stewart's framework for dynamic extension of typed functional languages

John Zabroski
  • 2,212
  • 2
  • 28
  • 54
1

You could do that with a full fledged type:

type Function() =
    member x.DoF<'a> (v:'a) = v

let y (f: Function) =
    let a = f.DoF 1
    let b = f.DoF 2L
    (a,b)

y (Function())

I don't know a way to make it work with first class functions in F#

smoothdeveloper
  • 1,972
  • 18
  • 19
  • 1
    But the main problem here, rather than being unable to use first classes functions, is that you will not be able to 'switch' to another function, so there is no point in passing it as an argument. – Gus Mar 04 '17 at 19:41