I find myself missing some functions from some modules in the Stdlib. Is it fine to simply overwrite them by writing new files, say,
stack.ml in my
/lib directory (in a project generated using
dune init proj project_name) and then calling, say,
Stack.mem, etc ? Or are there downsides and preferred solutions, maybe using different names for the extended modules, or functors ?
(* stack.mli *)
include module type of Stack
val mem : 'a -> 'a Stack.t -> bool
(** Check if the argument is a member of the stack. *)
val create_sng : 'a -> 'a Stack.t
(** Return a stack with the argument as single element. *)
val junk : 'a Stack.t -> unit
(** Remove the top element of a stack, throwing an exception in the case of
an empty stack. *)
(* stack.ml *)
let mem x t =
let f b y = b || y = x in
Stack.fold f false t
let create_sng x =
let res = create () in
Stack.push x res;
let junk t = ignore (Stack.pop t)
Edit: here’s a similar previous discussion Extend existing module
Along with another: Modules that extend modules from third-party packages
Not exactly the same, but provides a lot of context.
Thanks. I see that my method is discouraged, but it is not said in favor of which other method. It looks like I should rename my files
stack1.ml and should not use
include. Then, in my main file, I should call either
Stack.xxx for the functions of the Stdlib or
Stack1.yyy for my new functions.
Since I also want to add functions for lists, maybe it’s better to have files
util.ml where I put my stack functions and my list functions together, and then refer to them as
Util.xxx in my main file ?
I didn’t get the sense of a consensus that it’s discouraged from those threads. In your shoes I’d probably use the
module type of struct include Stack end form.
When I use a modified version of a Stdlib module, I alter it locally:
module Stdlib_submodule = struct
let extension ... = ...
Never really had the need for it though…
Another pragmatic option would be instead of writing an somewhat incomplete extended standard library, use one of the existing ones like Base or Containers, which come with a number of frequently reimplemented functions from the get-go.
Depends on your kind of pragmatism. If you just need a function here and there, simply adding them to your project is very pragmatic from a maintenance perpsective.
I don’t know Containers, I’m going to look at it, thanks ! As for Base, it actually overwrites some functions of the Stdlib (even some very common ones) and gives them a different meaning, which is a big NO.
By the way: I think it should be a big NO for everyone. What happens when a third person will want to rewrite a third “standard library” ? And a fourth ? We’ll end up with various OCaml-looking languages and maintenance (and learning) will be harder for everyone. It will be like the XKCD “standards” thing. Why couldn’t they join forces instead of competing ? Or if they want to enrich the library with other features, fine, but not rewriting the existing ones with a different meaning. And if they think of better design choices, they could push them to Stdlib, and everyone would benefit from it. Collaboration, not competition !
Because different entities have different constraints, goals, ideas and need for control. There’s no problem with that.
Just use what you think is good for yourself and avoid the rest.
I think this is actually a big advantage because it makes the Stdlib look like it was designed in 2015, that means it incorporates a lot of learnings that we got from the original standard library. For good reasons the OCaml stdlib can’t really change but I do enjoy that I can just point people to use
(Base.)List.find instead of always knowing not to use
Stdlib.List.find but use the slightly more obscure
Stdlib.List.find_opt, etc, etc.
In fact we already have a lot of “standard libraries” because often every project comes with its own implementation of things that were not in the standard library or weren’t there when the project was written or are not in the standard library of the minimum version of OCaml that they support, so for every project one has to look up how to achieve pretty simple things like “What do
Maps support here, how can I get a
Set, where are the prettyprinters, how do I deal with result, is there
let* syntax” etc and the situation is pretty much what you describe.
It doesn’t help that some of the standard extensions have pretty bad APIs, e.g. a
Map.update that can’t delete keys because the function does not accept
'a option as a return value, so you end up having to build your own version of
Map.update on top of it.
Thanks @Leonidas for the explanations. Obviously I cannot tell which one is better, but I was simply frustrated to see two different and incompatible “standard” libraries.
As @dbuenzli advises, I will continue with Stdlib, which seems sufficient for now (and hope that I won’t discover, when my program is twice larger, that I have to switch to Base).
Fwiw, containers is a very good overlay if you want something that plays nice with the Stdlib.