简体   繁体   中英

Implementing an interface with a generic parameter on a F# record

I'm trying to implement the an Microsoft.Extensions.Logging.ILogger (copied below for brevity) on a F# Record

using System;

namespace Microsoft.Extensions.Logging
{
    public interface ILogger
    {
        void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter);
        bool IsEnabled(LogLevel logLevel);
        IDisposable BeginScope<TState>(TState state);
    }
}

This is the record implementation.

  type ILoggerRcd<'TState> = 
    {
      BeginScope : 'TState -> IDisposable
      IsEnabled : LogLevel -> bool
      Log : LogLevel * EventId * 'TState * exn * Func<'TState,exn,string> -> unit
    }
    interface ILogger with
          override this.BeginScope(state : 'TState): IDisposable = 
              this.BeginScope (state)
          override this.IsEnabled(logLevel: LogLevel): bool = 
              this.IsEnabled logLevel
          override this.Log(logLevel: LogLevel, eventId: EventId, state : 'TState, ``exception``: exn, formatter: Func<'TState,exn,string>): unit = 
              this.Log(logLevel, eventId, state, ``exception``, formatter)

However, I'm getting this error on BeginScope: One or more of the explicit class or function type variables for this binding could not be generalized, because they were constrained to other types .

And this error on Log: The generic member 'Log' has been used at a non-uniform instantiation prior to this program point. Consider reordering the members so this member occurs first. Alternatively, specify the full type of the member explicitly, including argument types, return type and any additional generic parameters and constraints. The generic member 'Log' has been used at a non-uniform instantiation prior to this program point. Consider reordering the members so this member occurs first. Alternatively, specify the full type of the member explicitly, including argument types, return type and any additional generic parameters and constraints.

I've read through a few issues on fsharp compiler itself but nothing seems to be exactly this case I'm hitting. Is this something that can be done?

The ILogger interface requires that you can log objects of any type, but you're trying to log only those of type 'TState .

Take the signature of BeginScope :

IDisposable BeginScope<TState>(TState state);

See that <TState> bit there? That's a generic parameter. This signature means that every time anybody calls this method, they get to choose a type TState to use with that call.

Again: the caller chooses the generic type, not the implementer.

For example:

let l : ILogger = ...
l.BeginScope 42   // TState = int
l.BeginScope true // TState = bool

This means that your implementation of BeginScope has to be able to work with any type, not just the type with which your ILoggerRcd record was created.

The problem here is that you are trying to constrain ILogger 's 'TState to match the ILoggerRcd 's 'TState , but it's not your choice to do that.

To see this, note that a caller of ILogger.BeginScope can pass an int state on one call, and then a string state on another call to the same ILogger instance . Your implementation attempts to prevent this, hence the compiler errors.

The only way I can see around this is to use methods in your type instead of a record of functions. I don't think there's any way to do what you want using vanilla F# records.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM