the following code snippet does not compile:

[<Interface>]
type IEnvironment<'T> =
    abstract Service: 'T
type IStringEnvironment = IEnvironment<string>
type IIntEnvironment = IEnvironment<int>

module StringEnvironment =

    let get (env: #IStringEnvironment) = env.Service

module IntEnvironment =

    let get (env: #IIntEnvironment) = env.Service

module Composition =

    let get env = 
        let a = StringEnvironment.get env
        let b = IntEnvironment.get env
        a,b 

I wonder why?

I have almost the same setup in my code, except that i dont use the IEnvironment interface, but I have different "Environments" for different contextx, e.g. i have the following:

type ILoggerEnvironment =
    abstract GetLogger: ILogger

[<Interface>]
type IDateTimeEnvironment =
    abstract GetCurrent: DateTime

and then the Composition seems to work.

I tried different solutions, writing all functions as inline, but the compiler expects the type of the first usage of env to be the type (even with the prepended #, before the parameter type).

2

There are 2 best solutions below

0
sabotero On
module Composition =

  let get env = 
    let a = StringEnvironment.get env
    let b = IntEnvironment.get env
    a,b 

an instance of env can only be either IEnvironment<string> or IEnvironment<int> it can not be both at the same time.

As I don't know what is your final goal I cannot give recommendations, but normally if you want to work with different kinds of underling values you can unify them with Discriminated Unions (DU) like this:

type Env =
  | Str of IEnvironment<string>
  | Int of IEnvironment<int>

Still an instance of Env will only be either Str or Int so the composition you are trying to do does not hold.

0
Brian Berns On

You can do this in C# using type constraints, as follows:

interface IEnvironment<T>
{
    abstract T Service { get; }
}

interface IStringEnvironment : IEnvironment<string>
{
}

interface IIntEnvironment : IEnvironment<int>
{
}

class Composition()
{
    static (string, int) Get<U>(U env)
        where U : IStringEnvironment, IIntEnvironment   // <-- type constraints
    {
        var a = ((IStringEnvironment) env).Service;
        var b = ((IIntEnvironment) env).Service;
        return (a, b);
    }
}

However, the corresponding code in F# doesn't compile:

[<Interface>]
type IEnvironment<'T> =
    abstract Service: 'T

[<Interface>]
type IStringEnvironment =
    inherit IEnvironment<string>

[<Interface>]
type IIntEnvironment =
    inherit IEnvironment<int>

module StringEnvironment =

    let get (env: IStringEnvironment) = env.Service

module IntEnvironment =

    let get (env: IIntEnvironment) = env.Service

module Composition =

    let get<'U when 'U :> IStringEnvironment and 'U :> IIntEnvironment> (env : 'U) =   // error FS0193: Type constraint mismatch
        let a = StringEnvironment.get env
        let b = IntEnvironment.get env
        a,b

I don't know why F# has this limitation. It could be a bug in the compiler.

Using type aliases instead of true interfaces doesn't change the outcome:

type IStringEnvironment = IEnvironment<string>
type IIntEnvironment = IEnvironment<int>

Related Questions in F#

Related Questions in F#-COMPILER-SERVICES