【问题标题】:F#: Overriding global operator with static method overload or type conditional operatorF#:使用静态方法重载或类型条件运算符覆盖全局运算符
【发布时间】:2017-11-30 14:12:34
【问题描述】:

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

我想覆盖一个已经在项目中定义的自定义运算符,经典的 compose >=> 运算符,这样如果它与我的类类型一起使用,它将使用它的静态运算符重载,但每次我使用该类时使用 >=> 运算符,它给我的错误是我的类与全局运算符定义不兼容。帮助解释:

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 
                                ]

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

我正在考虑用静态成员 (>=>) 重载 FSharpFunc>,但在 f# 中,这需要在原始声明位置声明,这是不受限制的。

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

所有这些黑客(为了更好的词)是在使用 Node 类存储这些处理程序以构建到 Node 树中的同时维护 compose 运算符格式。子列表是可选的这一事实意味着,尽管我可以使用 (key, composedHandlers) / (key, composedHandlers, ChildList) 的元组重载构造函数,但这太丑陋且无法接受,即:

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

我可以将类静态运算符更改为 => / ==> 之类的东西,但它会导致混淆何时使用 >=> 或使用 =>,要求用户弄清楚是否处理程序正在馈送到一个节点,或者它们只是将两个处理程序组合成一个。

如果静态解析的类型条件没有锁定到仅核心,我可以执行以下操作...但不允许:

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

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

---编辑---

@Gustavo 链接的帖子正是我想要的:

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

【问题讨论】:

    标签: types f# operator-overloading overriding function-composition


    【解决方案1】:

    我对@9​​87654321@ 的回答展示了如何重新连接全局运算符。

    如果您发布了一个最小的复制品,我可以向您展示如何将它应用到您的案例中。

    话虽如此,我还是建议使用FSharpPlus,它已经包含运算符&gt;=&gt;better global definition,而要使其与您的类一起使用,您只需定义Bind 和@987654326 @,像这样:

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

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

    【讨论】:

    • 您在上一篇文章中的解决方案正是我想要的,非常感谢!!!我能够处理所有三种情况!
    【解决方案2】:

    我认为没有办法完全按照你的意愿去做,但如果你的目标是制作一个漂亮的 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 是什么?为什么需要“处理”?处理的结果是什么?从您的代码看来,“处理”的结果是另一个“处理程序”,但这是正确的吗?以此类推。

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

    【讨论】:

    • 感谢您的帮助,我试图避免使用 DU 进行包装,因为 b 第二个变量也可能是 Node list,这样链中的所有后续处理程序也需要被包装和只会让整个事情变得混乱。感谢节点的上下文有点模糊所以道歉。
    猜你喜欢
    • 1970-01-01
    • 2011-01-15
    • 2012-01-25
    • 2016-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-26
    • 1970-01-01
    相关资源
    最近更新 更多