F# 中具有泛型参数的高阶函数

Higher order functions with generic argument in F#

回复:

Please read the referenced link before going further below

我正在尝试扩展这个概念并传递一个通用函数,该函数接受 2 个参数并用它们做一些事情。

静态方法有效,但是基于接口的方法会导致编译错误(请参阅标有 ​​//error 的代码行):

The declared type parameter '?' cannot be used here since the type parameter cannot be resolved at compile time.

有人知道怎么解决吗?

module MyModule

type T = Content of int
with 
    static member (+) ((Content i1), (Content i2)) = Content (i1 + i2)
    static member (*) ((Content i1), (Content i2)) = Content (i1 * i2)

type W = { Content: int }
with 
    static member (+) ({Content = i1}, {Content = i2}) = { Content = i1 + i2 }
    static member (*) ({Content = i1}, {Content = i2}) = { Content = i1 * i2 }

type Sum = Sum with static member inline ($) (Sum, (x, y)) = x + y
type Mul = Mul with static member inline ($) (Mul, (x, y)) = x * y

let inline f1 (la: 'a list) (lb: 'b list) reducer = 
    let a = la |> List.reduce (fun x y -> reducer $ (x, y))
    let b = lb |> List.reduce (fun x y -> reducer $ (x, y))
    (a, b)

type I = abstract member Reduce<'a> : 'a -> 'a -> 'a

let f2 (la: 'a list) (lb: 'b list) (reducer: I) = 
    let a = la |> List.reduce reducer.Reduce
    let b = lb |> List.reduce reducer.Reduce
    (a, b)

let main ()=
    let lt = [Content 2; Content 4]
    let lw = [{ Content = 2 }; { Content = 4 }]

    let _ = f1 lt lw Sum
    let _ = f1 lt lw Mul

    let _ = f2 lt lw { new I with member __.Reduce x y = x + y} //error
    let _ = f2 lt lw { new I with member __.Reduce x y = x * y} //error
    0

您尝试的问题是您不能在参数 xy 上使用运算符 +*,因为不知道它们的类型'a 定义了那些运算符。

要在评论中回答你关于如何实现它的进一步问题 - 如果你想对调用者选择的任何类型 'a 使用乘法和加法,你必须指定它。对于接口方法,唯一的方法是通过约束类型参数 'a,.NET 运行时支持的唯一两种约束是“具有无参数构造函数”和“实现给定的接口或继承给定的class”。

后一种方法对您的情况很有用:让两种类型都实现接口,然后约束类型参数 'a 来实现该接口:

type IArithmetic<'a> =
    abstract member add : 'a -> 'a
    abstract member mult : 'a -> 'a

type T = Content of int
  with
    interface IArithmetic<T> with
        member this.add (Content y) = let (Content x) = this in Content (x + y)
        member this.mult (Content y) = let (Content x) = this in Content (x * y)

type W = { Content: int }
  with
    interface IArithmetic<W> with
        member this.add y = { Content = this.Content + y.Content }
        member this.mult y = { Content = this.Content * y.Content }

type I = abstract member Reduce<'a when 'a :> IArithmetic<'a>> : 'a -> 'a -> 'a
//                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^
//                                  the constraint right here

... 

  let _ = f2 lt lw { new I with member __.Reduce x y = x.add y }
  let _ = f2 lt lw { new I with member __.Reduce x y = x.mult y }

是不是有点尴尬?我想是的,但你对 SRTP 版本做了同样的事情,为什么不呢?

核心思想是:如果您希望 Reduce 方法不仅适用于 任何 类型,而且仅适用于可以做某些事情的类型,您有指定那些东西是什么。在 SRTP 案例中,您通过定义 (+)(*) 运算符来实现。在接口的情况下,你通过实现接口来做到这一点。

Q:但是我可以让界面以某种方式获取 (+)(*) 运算符吗?
A:一般不会。 .NET 运行时只是不支持像“任何具有特定签名方法的类型”这样的约束。这意味着此类约束无法编译为 IL,这意味着它们无法在接口实现中使用。

这是您为使用 SRTP 付出的代价:所有这些 inline 函数 - 它们不会被编译为 IL,它们总是在使用站点被扩展(插入、替换)。对于小而简单的功能,这没什么大不了的。但是如果你的整个程序都是这样,你可能会看到一些意想不到的编译代码膨胀,可能会导致启动时间变慢等。


说了这么多,我必须指出,您展示的代码是玩具 POC 类代码,并非旨在解决任何实际问题。因此,大多数关于它的思考都有完全无用的危险。

如果您有实际问题,也许可以尝试分享它,有人会针对该特定案例提出最佳解决方案。

特别是,我有一种挥之不去的感觉,您实际上可能不需要更高级别的函数(当函数作为参数传递时不会失去通用性时,它就是这样称呼的)。