I agree to be as generic as possible. I should have used this second
example instead, a quite common scenario where being generic doesn't make
as much sense:
function output(context::Context, strings::Vector{String})
context.something()
# Call the single-string version of the function on each item
for string in strings output(string) end
end
output(context, ["Hi there"])
I have already encountered this pattern a number of times in just my few
weeks with Julia; wrapping a function with a version that takes a single
item with one that takes a collection of them.
The style guide at
http://julia.readthedocs.org/en/latest/manual/style-guide/#handle-excess-argument-diversity-in-the-caller
seems to encourage my approach,
and
http://julia.readthedocs.org/en/latest/manual/style-guide/#don-t-use-unnecessary-static-parameters
directly condemns Iain's recommendation.
On Sunday, May 25, 2014 7:17:22 PM UTC-4, Jason Merrill wrote:
>
> 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.
>