如何在OCaml中获取多态单体运算符?



我正在尝试学习如何使用bindmap等函数来撰写 monads,到目前为止,我的代码可以工作,但它真的很冗长。这是我刚刚写的一个小脚本来说明我的意思,它将一个string投射到一个int option中,如果有的话,数字会加倍。

open Base
open Stdio
let is_number str = String.for_all ~f:(Char.is_digit) str
let str_to_num_opt (str: string) : int option =
if is_number str then Some (Int.of_string str)
else None
let double n = n * 2
let () =
let maybe_num = Option.map (str_to_num_opt "e123") ~f:double in
match maybe_num with
| Some n -> printf "Number found in string : %dn" n
| None   -> printf "No number found in string.n"

maybe_num的定义相当冗长,组合链越长,情况就会呈指数级恶化,所以我尝试使用>>|运算符,但由于它附加到Option模块,我不能直接将其用作中缀函数,而是将其称为Option.(>>|)(这与使用Option.map基本相同,但我丢失了命名参数)。

我所做的是在文件顶部添加open Option,然后重写我的()函数,如下所示:

let () =
let maybe_num =
str_to_num_opt "123"
>>| double
in match maybe_num with
| Some n -> printf "Number found in string : %dn" n
| None   -> printf "No number found in string.n"

代码现在干净多了,但我只能对每个文件的一个模块使用这个技巧,因为如果我在文件顶部的open Option之后添加open List(例如),它将遮蔽>>|的定义,运算符将只处理列表,从而破坏我的代码。

我所希望的是同时共存>>|的两个定义,并让编译器/解释器在运行代码时选择具有正确签名的那个(类似于在 Rust 中为不同类型的trait实现),但我无法让它工作。甚至可能吗?

在 OCaml 中,要执行您想要的操作,您可以在let块内以let open List in ...形式本地打开模块(有关其他形式的本地打开,请参阅 https://v2.ocaml.org/manual/moduleexamples.html#s%3Amodule%3Astructures 的手册),并且可以隐藏变量,例如,您可以通过引用List.map为某些代码定义运算符>>|,随后通过引用Option.map为同一文件中的其他代码重新定义它。

同样,您可以有一个定义let ( let* ) = Option.bind,然后有一个let ( let* ) = Result.bind其他代码的影子定义。

但是,OCaml 不实现临时多态性(也称为函数重载),因为编译器不会自动为您选择正确版本的运算符>>|。 将来可能会也可能不会通过模块化隐式提供此功能。

另一种解决方案是为经常使用的每个模块定义绑定。因此,例如,您可以使用let*表示Option.bindlet+用于Result.bind,...这样做的优点是使用起来非常短且轻量级,同时又是明确的(因为您可以通过最后一个字符识别 monad)。您甚至可以将所有此类定义放入它们自己的模块中,您将在每个文件的开头open这些模块,这样您就不必每次都重新定义绑定。

相关内容

  • 没有找到相关文章

最新更新