F# "Code is not sufficiently generic" 实现接口时“^T 无法泛化”

F# "Code is not sufficiently generic" "^T could not be generalized" when implementing interface

我正在尝试在一个项目中从 Giraffe.Serialization.Json 实现 IJsonSerializer 以使用 Microsoft.FSharpLu.Json,但是我在使用其中一种通用方法(下面的代码)时遇到了问题

type FSharpLuSerializer () =
    interface Giraffe.Serialization.Json.IJsonSerializer with
        member __.Deserialize<'T> (json : string) =
            Microsoft.FSharpLu.Json.Default.deserialize<'T> json

我遇到错误

This code is not sufficiently generic. The type variable ^T could not be generalized because it would escape its scope

我看到其他问题也有同样的错误,但我不确定如何将他们的解决方案应用到我的情况中。我想这与使用 ^T 的 FSharpLu.Json 有关,但我不知道我的解决方法是什么

https://github.com/Microsoft/fsharplu/blob/master/FSharpLu.Json/Default.fs

这是 Giraffe IJSonSerializer 接口

[<AllowNullLiteral>]
type IJsonSerializer =
    abstract member SerializeToString<'T>      : 'T -> string
    abstract member SerializeToBytes<'T>       : 'T -> byte array
    abstract member SerializeToStreamAsync<'T> : 'T -> Stream -> Task

    abstract member Deserialize<'T>      : string -> 'T
    abstract member Deserialize<'T>      : byte[] -> 'T
    abstract member DeserializeAsync<'T> : Stream -> Task<'T>

这是一个混乱的问题,一般来说,接口需要通过方法上的泛型来捕获要应用的类型,更复杂的是,你的 FSharpLu.Json 库正在使用内联 SRTP,SRTP 内联模板方法对 ^ 约束类型的调用,因此在编译后的代码中,每次调用都有一个具体的固定类型方法,这与 .net 中使用的在运行时解析类型的普通具体化泛型不同。

由于您无法内联接口,请尝试在接口上添加类型约束,这将捕获类型并避免泛化问题。

type IJsonSerializer<'T> =
abstract member SerializeToString      : 'T -> string
...

我运行跨过这个的时候遇到了同样的问题。我刚刚写了这个,它似乎正在工作。我会 运行 进一步研究它,如果它很好,我会推出一个 NuGet 包,可以插入它。

open Giraffe.Serialization
open Microsoft.FSharpLu.Json
open Newtonsoft.Json
open System.IO
open System.Text
open System.Threading.Tasks

type FSharpLuJsonSerializer() =
  interface IJsonSerializer with
    member __.SerializeToString<'T>      (x : 'T) =
      Compact.serialize x
    member __.SerializeToBytes<'T>       (x : 'T) =
      Text.Encoding.UTF8.GetBytes (Compact.serialize x)
    member __.SerializeToStreamAsync<'T> (x : 'T) (stream : Stream) =
      let serializer = new JsonSerializer()
      serializer.Converters.Add (CompactUnionJsonConverter ())
      use sw = new StreamWriter (stream)
      use writer = new JsonTextWriter (sw)
      serializer.Serialize (writer, obj)
      Task.CompletedTask

    member __.Deserialize<'T> (json : string) =
      (Compact.deserialize >> box) json :?> 'T
    member __.Deserialize<'T> (bytes : byte[]) =
      (Compact.deserialize >> box) (Encoding.UTF8.GetString (ReadOnlySpan bytes)) :?> 'T
    member __.DeserializeAsync<'T> (stream : Stream) =
      Task.FromResult<'T> ((Compact.deserializeStream >> box) stream :?> 'T)

也欢迎来自 F# 人员的反馈。

跳转到 F# Slack 后,有一种更简单的方法可以做到这一点,不需要所有的装箱和转换。 FSharpLu 在底层使用 Newtonsoft.Json,其紧凑的功能来自 CompactUnionJsonConverter 类型。您可以将此转换器添加到 Giraffe 的默认 JsonSerializerSettings,它可以工作 - 这意味着您不必完全重新实现 IJsonSerializer.

  open Giraffe.Serialization
  open Microsoft.FSharpLu.Json
  open Newtonsoft.Json

  let jsonSettings = 
    let x = NewtonsoftJsonSerializer.DefaultSettings
    x.Converters.Add (CompactUnionJsonConverter (true))
    x

然后,您在哪里配置服务...

    services.AddSingleton<IJsonSerializer>(NewtonsoftJsonSerializer jsonSettings)

非常感谢 Nino Floris (@ninofloris) 的建议。