非惯用的全局运算符重载如何工作?

How does non idiomatic global operator overloading work?

我想了解this答案

中的代码
type Mult = Mult with
    static member inline ($) (Mult, v1: 'a list) = fun (v2: 'b list) -> 
        v1 |> List.collect (fun x -> v2 |> List.map (fun y -> (x, y))) : list<'a * 'b>
    static member inline ($) (Mult, v1:'a      ) = fun (v2:'a) -> v1 * v2 :'a

let inline (*) v1 v2 = (Mult $ v1) v2

F# 可以解析重载的成员。 (因为它不支持柯里化成员)。所以,我想,它也应该适用于方法

但事实并非如此:

type Mult = Mult with
        static member inline Do (Mult, v1: 'a list) = fun (v2: 'b list) -> 
            v1 |> List.collect (fun x -> v2 |> List.map (fun y -> (x, y))) : list<'a * 'b>
        static member inline Do (Mult, v1:'a      ) = fun (v2:'a) -> v1 * v2 :'a
    let inline (<.>) v1 v2 = (Mult.Do (Mult,v1)) v2

A unique overload for method 'Do' could not be determined based on type information prior to this program point. A type annotation may be needed. Candidates: static member Mult.Do : Mult:Mult * v1: ^a -> ( ^a -> ^a) when ^a : (static member ( * ) : ^a * ^a -> ^a), static member Mult.Do : Mult:Mult * v1:'a list -> ('b list -> ('a * 'b) list)

定义运算符 $ 的语法令人困惑。它接受大写标识符作为运算符的第一个参数并且 Visual Studio 不会抱怨它

Mult 被推断为 mult 类型,但令人惊讶的是这不起作用:

type Mult = Mult with
    static member inline (!!) (mlt:Mult, v1: 'a list) = fun (v2: 'b list) -> 
        v1 |> List.collect (fun x -> v2 |> List.map (fun y -> (x, y))) : list<'a * 'b>
    static member inline (!!) (mlt:Mult, v1:'a      ) = fun (v2:'a) -> v1 * v2 :'a

let inline (<!>) v1 v2 = (Mult !! v1) v2

error FS0003: This value is not a function and cannot be applied

您的第二个示例不起作用,因为 F# 不会像使用运算符那样自动使用方法推断静态成员约束。

所以是的,这是可能的,但您必须手动编写约束,编译器不会为您推断它们:

type Mult = Mult with
    static member inline Do (Mult, v1: 'a list) = fun (v2: 'b list) -> 
        v1 |> List.collect (fun x -> v2 |> List.map (fun y -> (x, y))) : list<'a * 'b>
    static member inline Do (Mult, v1:'a      ) = fun (v2:'a) -> v1 * v2 :'a

let inline impl m v1 v2 = ((^T or ^a) : (static member Do:^T* ^a->(^b-> ^c)) (m,v1)) v2
let inline (<.>) a b = impl Mult a b

你说的大写标识符匹配的是只有一个case的Discriminated Union,所以它总是会成功,而且case的名字和类型的名字是一样的。所有这些都是为了缩短代码量,因为 DU 是一个虚拟类型。如果它令人困惑,这里有一个正常的例子 class:

type Mult() = class end with
    static member inline ($) (_:Mult, v1: 'a list) = fun (v2: 'b list) -> 
        v1 |> List.collect (fun x -> v2 |> List.map (fun y -> (x, y))) : list<'a * 'b>
    static member inline ($) (_:Mult, v1:'a      ) = fun (v2:'a) -> v1 * v2 :'a

let inline (*) v1 v2 = (Mult() $ v1) v2

您的第三个示例不起作用,因为 (!!) 是一元运算符,而不是像 ($)

这样的二元运算符

有关此旧技术的更多信息,请参见 this old blog