【问题标题】:Higher order functions with generic argument in F#F# 中具有泛型参数的高阶函数
【发布时间】:2021-10-17 22:15:44
【问题描述】:

回复:What is the best way to pass generic function that resolves to multiple types

请先阅读参考链接,然后再继续往下看

我正在尝试扩展这个概念并传递一个通用函数,该函数接受 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

【问题讨论】:

  • 问题是,您不能在参数x 和y 上使用运算符+ 或*,因为不知道它们的类型'a 是否定义了这些运算符。
  • 看来您正面临与泛型类型的经典混淆:选择类型的是泛型函数的调用者,而不是实现者。
  • @FyodorSoikin 在引用的链接中,您提出了接口版本,它比静态版本有优势。你能想出一种方式(不同于我的方式)来使用接口方法获得结果吗?作为替代方案,当这两种方法都可能时,您会采用什么标准在两种方法之间进行选择?
  • 我也遇到了这个限制,我认为一般接口方法的问题是你不能使用内联方法,因此所有 F#(非 .Net)静态约束都是无用的。这就是为什么我经常最终使用静态解决方案。

标签: f# higher-order-functions


【解决方案1】:

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

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

后一种情况在您的情况下会很有用:让两种类型都实现接口,然后约束类型参数 '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 方法不仅适用于 any 类型,而且适用于可以执行某些操作的类型,则必须指定这些内容是什么。在 SRTP 情况下,您可以通过定义 (+) 和 (*) 运算符来做到这一点。在接口案例中,您通过实现接口来做到这一点。

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

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


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

如果您有实际问题,不妨尝试分享一下,有人会针对该特定案例提出最佳解决方案。

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

【讨论】:

  • 其实dotnet可以支持接口中的静态成员(比如operator+),但是从net6开始支持,F#还没有支持。 Link
  • 我看不出这与这里有什么关系
猜你喜欢
  • 2020-03-11
  • 1970-01-01
  • 2020-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 2022-01-04
相关资源
最近更新 更多