F#:使用静态方法重载或类型条件运算符覆盖全局运算符

F#: Overriding global operator with static method overload or type conditional operator

我已经四处寻找答案,但已经用尽了所有尝试。

我想覆盖项目中已定义的自定义运算符,即 classic compose >=> 运算符,这样如果它与我的 class 类型一起使用,它将而是使用它的静态运算符重载,但每次我将 class 与 >=> 运算符一起使用时,它都会给我错误消息,即我的 class 与全局运算符定义不兼容。帮忙解释一下:

type Handler = context -> context option // context just placeholder for type

let (>=>) (a:Handler) (b:Handler) = fun ctx -> match a ctx with | Some u -> b u | None

type Node(key) =
...
member static (>=>) (p:Node,h:Handler) = ...
member static (>=>) (p:Node,pl Node list) = ...

这样我就可以编写可以包装组合处理程序的代码,例如

// val Node = Node (overloaded >=>) Handler (overloaded >=>) Handler (overloaded >=>) [ ... ]
let node = Node "key1" >=> handler1 >=> hander2 >=> [
                                Node "key2" >=> handler3
// val Handler = Handler (global >=>) Handler
let handler3 = handler1 >=> handler2 
                                ]

但不幸的是,class 上的静态方法重载无法覆盖全局运算符并获得优先权...我可以做些什么来覆盖全局运算符以使其工作!? ...我知道我可以将我的 Handle 从类型扩充更改为完整的 class 实现,删除全局运算符并仅在两个 classes 上使用静态覆盖,但要与现有框架集成我'我在看,需要这个格式的函数...

我正在考虑用静态成员 (>=>) 重载 FSharpFunc>,但在 f# 中,这需要在禁止访问的原始声明位置声明。

我研究了 c# 中声称的使用虚方法重载运算符的方法,但这似乎不起作用。

所有这些 Hacking(需要一个更好的词)是在使用 Node class 存储这些处理程序以构建到 Node 树中的同时维护组合运算符格式。子列表是可选的这一事实意味着虽然我可以使用 (key, composedHandlers) / (key, composedHandlers, ChildList) 的元组来重载构造函数,但这太丑陋并且被包裹起来而无法被接受,即:

let node1 = Node ("key1", handle1 >=> handle2 , [
                                        Node ("key2",handle3 >=> handle4, [
                                                                    Node ("key3",handle5)
                                                                    ])
                                                ])

我可以将 class 静态运算符更改为 => / ==> 之类的东西,但它会导致混淆何时使用 >=> 或使用 =>,要求用户弄清楚处理程序是馈送到节点还是纯粹将两个处理程序组合成一个,这太过分了。

如果静态解析的类型条件没有锁定到核心,我可以做类似下面的事情......但不允许:

let (>=>) (a:^T) (b:Handler)
     when ^T : Handler   = ...
     when ^T : Node      = ...
     when ^T : Node list = ...

问题归结为,如何使运算符类型有条件或如何使重载的 class 运算符覆盖全局运算符,以便我可以根据中缀类型一起使用两者。

---编辑---

@Gustavo 链接 post 正是我要找的:

type ComposeExtension = ComposeExtension with
    static member        (?<-) (ComposeExtension, (a:PathNode) , (b:HttpHandler)) = a.AddHandler b
    static member        (?<-) (ComposeExtension, (a:PathNode) , (b:PathNode list)) = a.AddChildPaths b
    static member inline (?<-) (ComposeExtension, a , b) = a >=> b
let inline (>=>) a b = (?<-) ComposeExtension a b

我认为没有办法完全做你想做的,但如果你的目标是制作一个漂亮的 DSL,你可以稍微不同路线:将 "just node" 案例和 "node with handlers applied" 案例都包装到 DU 中,然后在该 DU 上定义运算符:

type Handler = context -> context option
type Node = Node of key:string
type Composable = CNode of Node | CHandled of Handler

let node key = CNode (Node key)

let inline (>=>) (a:Composable) (b:Handler) = 
    match a with
    | CNode n -> ...
    | CHandled h -> ...

// Usage:
let handler1 = fun ctx -> ...
let handler2 = fun ctx -> ...

let a = node "abc" >=> handler1 >=> handler2

这在语法上有效并且与您的原始签名匹配,但我必须说它对我来说看起来有点荒谬,这是因为我不太明白您的最终目标是什么:什么是 Node ?为什么需要 "handled"?处理的结果是什么?从您的代码来看,"handling" 的结果似乎是另一个 "handler",但这是正确的吗?等等。

如果您能更好地阐明您的域,我相信我们可以想出更好的解决方案。

我对 this question 的回答显示了如何重新连接全局运算符。

如果您post 一个最小的复制,我可以告诉您如何将它应用到您的案例中。

话虽如此,我还是建议使用运算符 >=>FSharpPlus which contains already a better global definition,要使其与 class 一起工作,您只需定义 BindReturn,像这样:

static member Return a = ...
static member Bind  (x, f) = ...

同样,如果您向我展示您的代码,我可以提供更多详细信息。