4 ms·
I agree that that would be better, I'm just wondering what the definition of an interface would look like. Technically "all" you need is to extend the 'where'
by contravariant 4y ago
I agree that that would be better, I'm just wondering what the definition of an interface would look like.
Technically "all" you need is to extend the 'where' keyword a "little":
Iterable{T,S} = TIter where {
iterate(iter::TIter) <: Tuple{T,S},
iterate(iter::TIter, state::S) <: Tuple{T,S}}
- bobbylarrybobby 4y agoI'm imagining it would look more or less like a struct definition, but with methods instead of fields. Something like interface{S,T} Iterable{T} Next = Tuple{Union{T,Nothing},S} iterate(self::Self)::Next iterate(self::Self, state::S)::Next end This means "Iterable has two type parameters, one of which (T) is named explicitly in the type and the other of which (S) is implicitly used in the definition of the interface but is otherwise not nameable.". I think the real question is how to implement structural sub typing of interfaces -- you wouldn't want to have to list the interfaces a struct implements when defining it because then you're limited to only those traits you (the struct's author) know about. Better (and more Julian) to automatically implement Iterable for any struct with those two iterate methods defined on it, but it seems like a pretty big task to keep track of that at runtime. Especially if you care about matching the return types of the interface's functions since in general those can't be type checked until they're actually called (or at the least compiled, but the whole point of Julia is to not compile the world ahead of time).
- contravariant 4y agoI don't think you really can list the interfaces a struct implements up front, the way I understand Julia you only implement those methods afterwards so you'd have an awkward gap in between the definition of the struct and the part where the definition is actually correct. In theory an interface ought to just be a shorthand for 'this duck-typing works', but in a way that ideally can be verified statically. A static type checker is the only possible (though major) advantage I see. It's debatable if it even makes sense for Julia to check interfaces at runtime, if something doesn't satisfy an interface but the code compiles, then why would it be wrong?
- ChrisRackauckas 4y agoYes, this is the major issue. When do you check for interface compatibility, and how? If you check for interface compatibility immediately then it will always fail because methods will be defined after the struct. Do you check when the module definition is then completed? Maybe another module completes the interface definition: do you disallow that? This question becomes more subtle with multiple dispatch. If you have a type parameter, A{T}, you may only define the iteration protocol on A{T <: Number}, but the struct definition may allow any T. Should that error? So then if you error if the interface is incomplete at the bottom of the module, should you error if there exists any possible dispatch that is missing an interface function? Maybe the answer is then to error at the bottom of the module if there exists any type parameter that does not implement this interface. Okay cool, that does sound sensible. But now let's take a case where A{T <: AbstractFloat} defines the interface by doing direct dispatches for every floating point type in Julia Base. It won't error because iterate(::A{Float32}), Float64, etc. are all defined. Now someone adds the package ArbFloats.jl. Do you error when `using ArbFloats` because the interface is now not satisfied for `A{ArbFloat64}`? That would be annoying for users who never use type A, but if you don't error then your interfaces are not guaranteed to work. This would mean interfaces should only ever be used with `A{T <: T2}` where all interface definitions are done with `iterate(A{T}) where {T <: T2}` in the generic form so that it automatically extends well. That would be good code, but does that mean you should enforce that `iterate(::A{<:AbstractFloat})` is defined and error if it's not, even if every `T <: AbstractFloat` case is handled in the current REPL? There's a lot to think about. But no matter how that's done, I cannot see it not breaking some code somewhere, hence probably being a 2.0.
- wnoise 4y agoThat's starting to look a lot Haskell's typeclasses.
- ChrisRackauckas 4y agoHaskell is a well-designed language so there is a lot to learn from it!