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.
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?
CodePudding user response:
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.
CodePudding user response:
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 generic 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.