F# 静态成员,缺少泛型类型的实例化——可能是错误?

F# static member, instantiation of generic type missing--possible bug?

我正在编写我自己的 Writer monad 版本,用于自学。我正在尝试具有一定的通用性(但不尝试 achieve/approach 通过静态解析的类型参数或其他解决方法完全实现)。

我的第一个版本收到警告:

type Writer<'w, 'a> = | Writer of 'a * List<'w>  with 

  static member sum (l1:'w list) (l2: 'w list) = l1 @ l2

[<AutoOpen>]
module WriterMonadMod = 

  module Writer =
      let apply (mf:Writer<'w, ('a -> 'b)>) (ma:Writer<'w, 'a>) : Writer<'w, 'b> =
          let (Writer (f, l1)), (Writer (a, l2)) = (mf, ma)       
          let b = f a
          Writer (b, Writer.sum l1 l2)  // Warning, on "Writer.sum", 
//Instantiation of generic type missing, can't be inferred. 

好的,有道理。我们想要哪个静态总和?对于哪些特定的 Writer 实例化类型?我不知道我是否可以忽略此警告,或者它最终是否会咬我。所以我尝试在“.sum”之前在 "Writer" 上放置一个类型参数——但现在这会导致错误:

          Writer (b, Writer<'w, 'b>.sum l1 l2)  //Error, on "<'w, 'b>",
//unexpected type arguments. 

这让我感到困惑,因为它看起来像 Why can't F# infer the type in this case? 的另一个 SO 答案中的语法(Cell<float>.Create 1.0;这对我有用,没有错误,没有警告;并且尝试非泛型类型不会改变我的问题。)

所以我胡乱使用名称,将类型与其值构造函数区分开来,添加 "T"-- 现在它自己修复了!:

type WriterT<'w, 'a> = | Writer of 'a * List<'w>  with 
//...
    static member sum (l1:'w list) (l2: 'w list) = l1 @ l2

[<AutoOpen>]
module WriterMonadMod = 

    module WriterT =
        let apply (mf:WriterT<'w, ('a -> 'b)>) (ma:WriterT<'w, 'a>) : WriterT<'w, 'b> =
            let (Writer (f, l1)), (Writer (a, l2)) = (mf, ma)       
            let b = f a
            Writer (b, WriterT<'w, 'b>.sum l1 l2)  //No warning, no error.
//(With "T" distinguishing the type name from value constructor.) 

这有意义吗?为什么显然对类型和构造函数使用相同名称的通常做法似乎在这里引起歧义?

(旁注:我猜 "T" 不是一个很好的选择,因为这不是一个 monad 转换器。apply 上所有类型注释的原因是为了调试。)

根据 Tomas 的回答更新:

奇怪的是,对我来说,这也可以避免错误和警告。 通配符 用于解决歧义 警告!?

    module WriterT =
        let apply (mf:WriterT<_,_>) (ma:WriterT<_,_>) : WriterT<_,_> =
            let (Writer (f, log1)), (Writer (a, log2)) = (mf, ma)       
            let b = f a
            Writer ( b, WriterT<_,_>.sum log1 log2 )      

第二种情况下的"unexpected type arguments"错误非常令人困惑(甚至可能是一个错误)。我认为这里发生的事情是编译器首先将 Writer 解析为案例名称,然后在找到类型参数时报告错误。然后它意识到您实际上指的是一种类型并更改计划。重命名类型(在您的最后一个示例中)解决了这种歧义。

解决这个问题的另一种方法是添加 RequireQualifiedAccess 属性,这将隐藏类型名称后面的联合大小写,因此您必须编写 Writer.Writer 并且第一个名称指的是类型:

[<RequireQualifiedAccess>]
type Writer<'w, 'a> = | Writer of 'a * List<'w>  with     
  static member sum (l1:'w list) (l2: 'w list) = l1 @ l2

module Writer =
  let apply (mf:Writer<'w, ('a -> 'b)>) (ma:Writer<'w, 'a>) : Writer<'w, 'b> =
      let (Writer.Writer (f, l1)), (Writer.Writer (a, l2)) = (mf, ma)       
      let b = f a
      Writer.Writer (b, Writer<_,_>.sum l1 l2)

现在您可以键入 Writer<_, _>.sum 并且它可以工作,因为类型引用已解析。