On Sunday, May 25, 2014 2:11:41 PM UTC-7, Adam Smith wrote: > > Actually, no. I'm not going to pretend this is a good thing. You're right > that it is "consistent and logical" when you're using an academic > type-correctness viewpoint. However, it is not consistent from a developer > perspective, and here's why. > > On most functions, I don't need to specify parametric types (which is > good: there is less clutter). Let's say I have a function: > function output(context::Context, string::String) > context.something() > println(string) > end > > output(context, "Hi there") > > And a few days later I decide it would be better to accept a list of > strings instead, so I do the most natural thing, and I just put Vector{} > around the type that was already working: > > function output(context::Context, strings::Vector{String}) > context.something() > for string in strings println(string) end > end > > output(context, ["Hi there"]) > > This is absolutely what every new Julia developer will expect to work > (regardless of how "correct" it is), and it will fail. It's especially > confusing because for some types, it works (like changing Int to > Vector{Int}). It is simply bizarre to new developers using the language > that the type matching worked on a single element of that type, but not on > a parametric collection of that type. > > Yes, I know why it is the way it is, and yes, I know why a textbook says > it should be this way, but thinking from a UX perspective (where the "user" > is a developer new-ish to Julia), it's quite off-putting. >
It's easier to compose different libraries together when implementers don't overtype their methods, and instead rely more on duck typing. To take your "output" example, the contract of the vector that's passed in is actually only that println needs to be defined on all of its elements, and not that every element must be a string. Suppose for performance reasons I decide to use some more exotic text data structure, like a Rope, and for reasons that are out of my control, it inherits from FunctionalContainer or something like that instead of from String. Julia doesn't have multiple inheritance, so this can easily happen. Then if I want to use your library, and you've decided to require a vector of String (or out of necessity, some concrete subtype of String), I'm out of luck. If you instead define output(context::Context, strings::Vector)then I can get on with my business. For this reason, I think it's better to be sparing with explicit type parameters: only add them when you actually want two different method bodies depending on the type. To make this kind of duck typing more explicit and robust, it might be nice if Julia eventually added shapes or interfaces or something like that that let you specify a little bit more about the contract of arguments in a way that depends on what methods are defined on them, but not on their explicit type.