I just want to throw out there an idea I’ve had for enhancing OCaml’s syntax and gauge the reactions.
Because we have no type-based dispatch (outside of objects), it’s really important for us to be able to be able to create modules on the fly in OCaml. This is similar to the way that functions have low-cost syntax – the easier it is to create lambdas, the more they can be used. Well, OCaml’s ‘currency’ is really modules, and the easier it is to create and manipulate them, the more easily they can be used:
module MyModule = struct
type t
let foo x = x
let bar y = y
let baz z = z
end
(* This is syntactic sugar for importing only type t, val foo, val bar *)
open MyModule[type t; foo; bar]
(* It should be approximately equivalent to
module M = struct type t = MyModule.t let foo = MyModule.foo let bar = MyModule.bar end
open M *)
...
(* Syntactic sugar for locally opening MyModule with only foo and bar exposed in the type *)
MyModule[foo; bar].(let x = x + foo 3 in print_int x)
...
(* If a local selective open is made multiple times, we may want to factor it into a module: *)
module M = MyModule[foo; bar]
(* This is equivalent to
module M = struct let foo = MyModule.foo let bar = MyModule.bar end
*)
The concise syntax would be shorthand for creation of a local anonymous module. This takes care of the problem with local opens introducing potentially shadowing variables into a local context, as indexing only the variables you want to include is trivial:
(* In file modules.ml *)
module A = struct
let foo x = x + 1
end
module B = struct
let bar y = y + 2
(* The following line is sneaked in one day, potentially causing a problem *)
let foo y = 15
end
(* in file main.ml *)
open Modules
let () =
A.(foo 10 +
B.(bar 4 + foo 12)) (* here we aren't calling the right foo anymore *)
|> print_int
(* Safe alternative: *)
let () =
A[foo].(foo 10 +
B[bar].(bar 4 + foo 12)) (* this works *)
|> print_int
(* Alternatively *)
let () =
let open A[foo] in
let open B[bar] in
foo 10 + bar 4 + foo 12 |> print_int
(* Also, opens are the only way to import operators, so *)
module Infix = struct let (-) x y = x - y let (+) x y = x + y end
open Infix[-]
(* Is a great way to specify which infix operator you want *)
(* It's also worth thinking about excluding certain imports. Sometimes this is more concise *)
module M = module MyModule[not foo; not bar]
(* would take everything in the module but values foo and bar. Once you choose to exclude values/types, you cannot include any, as that would become too confusing *)
I’m sure I’m not thinking of nuances in the module language right now, but I really think this could solve a lot of problems for us, while making the module language easier to use. Opinions?