在类型扩展中重载运算符

4

好的,我基本上是在尝试为选项类型添加绑定操作符,但似乎每次尝试都会有一些非显而易见的限制阻止我这样做。我怀疑这与.NET类型系统的限制有关,可能是用户代码无法实现类型类的相同原因。

无论如何,我尝试了几个方法。

首先,我只尝试了以下代码:

let (>>=) m f = ???

我意识到,根据m的类型,我想做不同的事情。F#不允许在函数上进行重载,但.NET允许在方法上进行重载,因此尝试方案二:

type Mon<'a> =
    static member Bind(m : Option<'a>, f : ('a -> Option<'b>)) =
        match m with
        | None -> None
        | Some x -> f x
    static member Bind(m : List<'a>, f : ('a -> List<'b>)) = 
        List.map f m |> List.concat

let (>>=) m f = Mon.Bind(m, f)

不行。无法根据之前给出的类型信息选择唯一的重载。添加类型注释。

我尝试过将运算符设置为内联,但仍然出现相同的错误。

然后我想到可以将 >>= 运算符作为类型的成员。我很确定这会起作用,但我不认为我可以在现有类型上进行修改。您可以使用 type Option<'a> with 扩展现有类型,但无法将运算符作为扩展。

这是我对这段代码的最后尝试:

type Option<'a> with
    static member (>>=) (m : Option<'a>, f : ('a -> Option<'b>)) =
        match m with
        | None -> None
        | Some x -> f x

扩展成员无法提供运算符重载。考虑将运算符定义为类型定义的一部分。很棒。

我还有其他选择吗?我可以在不同的模块中为不同的单子定义单独的函数,但如果您想在同一文件中使用多个版本,那听起来像是地狱。


@JohnPalmer 噢,抱歉。重点是为Bind方法设置多个重载,这样它会引发错误。我会在问题中修复它。 - Luka Horvat
我认为你的问题在于,你需要像Type-constructors这样的东西来为一般的(>>=)提供合理的类型(请记住,Monad具有kind * -> *)- 内联函数的静态约束也无法解决这个问题 - 所以,很遗憾,你没有办法。 - Random Dev
有时我会将这些运算符包装在一个内部模块中(通常命名为Operators),这样如果我需要它们,我只需打开MyMonad.Operators,或者不得不使用MyMonad.Operators.>>= :( - Random Dev
1
如果你真的非常绝望,你可以随时修改 Option.fs - John Palmer
@JohnPalmer +1 - 但是为什么?对我来说,opt >>= (fun x -> ...似乎并没有比opt |> Option.bind (fun x -> ...好多少。 - Random Dev
@CarstenKönig 可能的动机之一是为了摆脱括号,例如 opt >>= fun x -> ...,另一个动机是您可以使用相同的顶级缩进。所有你的 fun 都在同一个层次上对齐。 - kaefer
2个回答

9
你可以将.NET重载解析与内联/静态约束结合使用,以获得所需的行为。
这里是一个逐步解释,这里是一个针对您特定情况的小型工作示例:
type MonadBind = MonadBind with
    static member (?<-) (MonadBind, m:Option<'a>, _:Option<'b>) = 
        fun (f:_->Option<'b>) ->
            match m with
            | None -> None
            | Some x -> f x
    static member (?<-) (MonadBind, m:List<'a>, _:List<'b>) = 
        fun (f:_->List<'b>) ->
            List.map f m |> List.concat

let inline (>>=) m f : 'R = ( (?<-) MonadBind m Unchecked.defaultof<'R>) f

[2; 1] >>= (fun x -> [string x; string (x+2)]) // List<string> = ["2"; "4"; "1"; "3"]
Some 2 >>= (fun x -> Some (string x))          // Option<string> = Some "2"

您也可以手动指定约束条件,但在使用运算符时,它们会自动推断。

这种技术的改进(无运算符)是我们在FsControl中使用的,用于定义Monad、Functor、Arrow和其他抽象概念。

还请注意,您可以直接使用Option.bindList.collect来进行绑定定义。


不错!我应该使用FsControl还是fsharp-typeclasses? - Luka Horvat
@LukaHorvat 的 fsharptypeclasses 是一个初始的实验/演示项目。请使用 FsControl 代替,它被设计为生产 F# 项目的核心库。 - Gus

2

为什么需要重新定义"bind"?首先,Option.bind已经被定义。

您可以使用它来定义一个“计算表达式构建器”(F#中单调“do”语法糖的名称)。 请参见以前的答案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接