Description
Opening this for discussion and ideas surrounding type classes, traits or a similar alternative. This is a low priority feature and will likely not be implemented any time soon, if at all.
Currently in grain if you want a function to work on multiple data types, you don't really have an option besides wrapping your data in an adt which creates a performance bottleneck and increases code complexity. In internal cases such as print
, marshal
, toString
and ==
we use unsafe matches and open types to provide functionality for multiple types through one function.
It would be beneficial if we could provide type-specific impl
and let the compiler convert these function calls to each type of implementation call.
Some functional ideas include rusts trait system and haskells type classes
Looking furthur into this Swift Extensions seem like a great implementation that is easy to understand of the type of system im describing. I do think we would need to tweak the semantics though as applying the extension to every existing instance of the type could produce additionally complexity and makes the code a lot harder to follow. I think we should try to avoid globally injected behaviour, maybe if we have extension produce a new instance of the type.
The benefits of this would be:
- More performant code, as we do not need to do all the runtime type matching.
- Our internal functions such as
print
,toString
and==
would be safer and more maintainable.- We might want to autogenerate a
toString
impl on types that don't specify one to maintain a good developer experience.
- We might want to autogenerate a
- We could avoid runtime type information in a lot more cases (Our Number type would still be a problem, i.e we probably couldn't put a float64 represented as a number into a wasm local), but we could avoid tagging as the impl would know what type it's operating on.
- We can write more generalized code as an example, the print function just needs to take a type that has a
print
impl and call its impl.
Ideas:
- It would be rather annoying to have to write an impl for
print
,toString
,==
and the such for each type a user makes (Specifically relating to records) so we might want to autogenerate these impls and allow the user to override them.
Notes:
- I feel that a big problem when reading Rust's code is the fact that an impl doesn't need to be implemented near the type definition while this allows for you to better organize code I think it makes understanding the full codebase a lot harder without reading the entire thing. so it would be nice if we could somehow keep impls near their type. (good LSP support could also help here, but shouldn't be a crutch).
- We may want to avoid the
a.test()
syntax, we seem to attract a lot of OOP devs, specifically JS devs and I think the method-looking syntax might cause a lot of confusion, it would be beneficial if something liketoString(t)
would be converted by the compiler intot.toString()
, I think this might need to be workshopped a little as if the user defines atoString
function in the scope how do we know which one the user wants to use. maybe we add a symbol to denote it something liketoString!(t)
but there are certainly issues with this.
Question:
- Could we allow for safer use of
WasmXX
types with this? for example, if we added atoString
impl toWasmF64
there is no good reason it needs to follow the standard grain abi where every type is aWasmI32
.
Expansions:
- We have talked about implementing iterators before specifically when it comes to slicing up arrays efficently, from what I understand rust implements iterators through the trait system as seen here