It's not quite time to tackle this yet, but it's getting close. There's some narsty ergonomic issues with modules as implemented in OCaml, and while in principle I'm okay just sucking it up and living with it, I do think it's probably possible to make life better than it is. There's a few separate ideas I have for this:
let x: Vec<i32> = ...
in any part of the code you are very literally creating a new instance of Vec
and all its methods, specializing them to i32
, and then deduplicating them so they all point to the same actual implementation and typecheck to the same result. That's possible because due to orphan rules, any Vec<i32>
is always the same as any other Vec<i32>
. With modules this is not the case, it's possible to have two different Vec<i32>
implementations that are very distinct things, and so traditionally you always have to create each Vec<i32>
manually and thread them through your code. And back in my OCaml days, having to make a new copy of Hashtbl.t (int, string)
for each file and explicitly write out that creation for each (int, string)
pair I wanted to use and choose which one I want to use was really damn confusing and felt like a lot of needless busy-work.List
and Enum
that you call explicitly rather than doing trait-like "dispatch based on the type of the value", and while a little verbose it's honestly not terrible.pub
right up until something forces me not to, which helps.I made this entire issue simply so I had a place to put the following conversation:
icefox Finally getting around to trying out nicer interface/ML module/platypus syntax. Not final, but I don't hate it. --- functor returning a struct implementing a particular module impl fn TryFromFrom(|In| from_impl: From[In]) TryFrom[In] = type Self = from_impl.Self type Err = Never try_from: fn(input: In) Result[Self, Err] = Result.Ok(from_impl.from(input)) end end Vi Btw name suggestion for the impl block: `TryFromFromFrom` Because its making TryFrom from From Big improvement Do users always manually invoke module impls? icefox So far yes, but there's vague plans for some kind of modular implicit functionality Vi Could be nice to have anonymous module impls in that case, and allow: impl fn(|In| from_impl From[In]) TryFrom[In] = ... end callable modules could help with this? // ugly syntax just to demo impl Vec = fn call_module(|a|) = fn pop(self) a = ... end end fn new(|a| values: Iterator[a]) Vec[a] end Vec.new(1, 2, 3) icefox: Anyway this kinda leans hard on the function call inference and coalescing of pure modules... But... The function call inference is there already and I was already leaning hard on purity for (most) module impl's. You'd have to be able to name Vec[I32] as a type in some random places but I'm 99% sure you already can... :thinking: just feels a little weird. Vi Yeah I think having Vec(u32) = Vec(u32) is p important with modules ... icefox Circling back around again, what is the `fn pop(self)` here doing? Listing the functions that are accessible when calling the module implicitly? Vi So the idea there is that Vec and Vec[T] are actually completely separate modules, and the functions that depend on T (like pop) live in Vec[T], then we can also include all of Vec inside Vec[T] Though that was sorta open to interpretation :) There's probably some tweaks that'll make that more ergonomic
Soooooo it's fuzzy, but I rather like the idea of not having to do this:
const VecU32 = Vec(|U32|) let mut x = VecU32.new() VecU32.extend(x, [1,2,3])
but rather just doing:
let mut x = Vec[U32].new() Vec.extend(x, [1,2,3])
In principle, it's totally possible to infer a type for
Vec.extend()
because we already know the type forx
, and then it's possible to instantiate an instance of the moduleVec
with that type. As long as we have only one instance ofVec[U32]
(which is the usual case) then it's unambiguous and as long as creatingVec[U32]
is pure then there shouldn't be any coherence issues. If I'm understanding correctly. Or, to lean less on sugar and inference, as Vi said you could haveVec
be a separate module that takes some type parameter and attempts to dispatch off to another module chosen by modular implicits??? Or include one module inside the other (which is not implemented in Garnet yet and which I haven't had much need for yet, since it smacks of subtyping.)~plecra is this vaguely accurate as to what you were thinking?
What would that look like with modular implicits, actually? Let's try it out:
const thingy = Vec(|U32|) -- We need another module `Vec`, separate from `Vec[T]`, that takes an implicit impl of `Vec[T]` and just calls out to it let mut x = Vec.new(|U32|) -- actually calls `Vec.new(|U32| thingy)` Vec.extend(x, [1,2,3]) -- actually calls `Vec.new(thingy, x, [1,2,3])`
Hmmm. It works? I think? (I could be totally wrong, it's been a long day.) But it doesn't actually solve the explicit-instantiation problem, and in general feels a bit like taking the long way around.