In C# it is straightforward to define a generic class when one generic parameter inherits from another, e.g.:
public class MyClass<TClass, TInterface> where TClass : class, TInterface
{
}
    
This is used to "force" the class TClass to implement the interface TInterface. I want to do the same in F# and surprisingly it does not seem to work. For example, the following code:
type Startup<'S, 'I when 'I : not struct and 'S : not struct and 'S :> 'I>() =
    member _.x = 0
results in FS0663 - This type parameter has been used in a way that constrains it to always be ''I when 'I : not struct'. I wonder if the generic structure similar to C# above can be implemented in F#.
At this point one may wonder why do I need that? Here is why. I want to have a generic F# interop with CoreWCFlike:
open CoreWCF
open CoreWCF.Configuration
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.DependencyInjection
module Startup =
    type Startup<'S, 'I when 'I : not struct and 'S : not struct>() =
        let createServiceModel (builder : IServiceBuilder) = 
            builder
                .AddService<'S>()
                .AddServiceEndpoint<'S, 'I>(new BasicHttpBinding(), "/basichttp")
                .AddServiceEndpoint<'S, 'I>(new NetTcpBinding(), "/nettcp")
            |> ignore
        member _.ConfigureServices(services : IServiceCollection) =
            do services.AddServiceModelServices() |> ignore
        member _.Configure(app : IApplicationBuilder, env : IHostingEnvironment) =
            do app.UseServiceModel(fun builder -> createServiceModel builder) |> ignore
which then can be used as follows:
open System.Net
open CoreWCF.Configuration
open Microsoft.AspNetCore
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Server.Kestrel.Core
module Builder =
    let CreateWebHostBuilder() : IWebHostBuilder =
        let applyOptions (options : KestrelServerOptions) =
            let address : IPAddress = IPAddress.Parse("192.168.1.89")
            let port = 8080
            let endPoint : IPEndPoint = new IPEndPoint(address, port)
            options.Listen(endPoint)
        WebHost
            .CreateDefaultBuilder()
            .UseKestrel(fun options -> applyOptions options)
            .UseNetTcp(8808)
            .UseStartup<Startup<EchoWcfService, IEchoWcfService>>()
where EchoWcfService, of course, implement interface IEchoWcfService. The problem with Startup without generic constraint that 'S implements 'I as above is that nothing precludes writing something like: .UseStartup<Startup<EchoWcfService, string>>(), which will, of course, blow up at runtime.
Update
Given the comments and the references I tried to address the issue in a mixed F# + C# way. So, we can easily create a generic class in C#:
public class WcfStartup<TService, TInterface>
    where TService : class
    //where TService : class, TInterface
{
    private void CreateServiceModel(IServiceBuilder builder)
    {
        builder
            .AddService<TService>()
            .AddServiceEndpoint<TService, TInterface>(new BasicHttpBinding(), "/basichttp")
            .AddServiceEndpoint<TService, TInterface>(new NetTcpBinding(), "/nettcp");
    }
    public void ConfigureServices(IServiceCollection services) =>
        services.AddServiceModelServices();
    public void Configure(IApplicationBuilder app, IHostingEnvironment env) =>
        app.UseServiceModel(CreateServiceModel);
}
and then change .UseStartup<Startup<EchoWcfService, IEchoWcfService>>() into .UseStartup<WcfStartup<EchoWcfService, IEchoWcfService>>() after referencing C# project with that class.
Now, if I comment where TService : class and uncomment where TService : class, TInterface then F# project will no longer compile with This expression was expected to have type 'EchoWcfService' but here has type 'IEchoWcfService', which is basically the same as the "original" F# compile error but just with a different flavor.
I'd call it a bug in F# compiler...
As from the F# spec:
New constraints of the form type :> 'b are solved again as type = 'b.
There are some popular F# language suggestions that aim to solve this, see:
https://github.com/fsharp/fslang-suggestions/issues/255
https://github.com/fsharp/fslang-suggestions/issues/162
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With