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
您尝试的问题是您不能在参数 x
和 y
上使用运算符 +
或 *
,因为不知道它们的类型'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 类代码,并非旨在解决任何实际问题。因此,大多数关于它的思考都有完全无用的危险。
如果您有实际问题,也许可以尝试分享它,有人会针对该特定案例提出最佳解决方案。
特别是,我有一种挥之不去的感觉,您实际上可能不需要更高级别的函数(当函数作为参数传递时不会失去通用性时,它就是这样称呼的)。
回复:
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
您尝试的问题是您不能在参数 x
和 y
上使用运算符 +
或 *
,因为不知道它们的类型'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 类代码,并非旨在解决任何实际问题。因此,大多数关于它的思考都有完全无用的危险。
如果您有实际问题,也许可以尝试分享它,有人会针对该特定案例提出最佳解决方案。
特别是,我有一种挥之不去的感觉,您实际上可能不需要更高级别的函数(当函数作为参数传递时不会失去通用性时,它就是这样称呼的)。