Austin said:
Type hints are primarily a
documentation feature. Let me repeat that for emphasis: it is a
documentation feature. If an optimizing Ruby compiler is able to
take advantage of it -- great.=20
And I still fail to see the semantic difference between the two.
Please explain, I'm still listening. In what case would a tool -- any
tool -- be it compiler, interpreter, rdoc, ide, or whatever want to
treat your 'type hint' differently than my 'type declaration'? As far
as I'm concerned, they're different words for the same thing. Just
because you want to put it in a comment is not a semantic difference;
it's just syntactical.
This sort of attitude is unnecessarily harsh. Whether you care or not
about all the same things someone else might, the fact remains that
the exact same mechanism that's needed for what you want
(documentation) can provide the information needed by an optimizing
compiler.
DO *NOT* USE POLS in your argument. It doesn't apply. Matz has
indicated that it's just so much crap.
The principle of least surprise is a good principle in language
design. Unfortunately, everyone is likely to see it subjectively:
principle of my least surprise. Matz interprets it this way, thus for
ruby, POLS really means POLMS (principle of least matz surprise). Has
matz said yet what is the least surprising to him, a type hint (or
declaration) in a comment before the function, or inside the function
header? I'm not aware that he has. Since I'm no good at reading matz'
mind, (or his blog, for that matter) I tried to come up with an
objective version of POLS, from the point of view of an average
programmer from another language.
Since POLS is really a form of appeal to authority, I also cited two
other principles that I believe apply in this case.
Oh, please *don't* try to lecture me on this stuff, Caleb.
Well, Austin, I will certainly lecture you if you seem to require
lecturing. (If that's waht you want to call it.) You made a logical
error in your argument. You equated static typing and class-based
typing. They are the same in most languages, but need not (and should
not, in my opinion) be the same thing in a ruby static type system.
1. It looks like the stuff in other languages, but because Ruby
isn't statically typed (thank GHU for that), it won't have the
same meaning as it does in those languages.
There are many dynamic languages with variable type declarations.
Javascript and scheme leap to mind. There are many things in dynamic
languages that don't have quite the same semantics as their equivalent
in a static language, but the same or similar syntax nevertheless.
Integer addition leaps to mind. Just because C also has integer
addition, using +, does that mean that ruby must use some different
syntax when adding integers, since it actually handles overflow?
2. If it does have the same meaning in those languages, we're no
longer talking about Ruby.
Every time you change the language, it's no longer the same language,
but so what?
3. It will encourage people to use it for the wrong reasons in the
wrong way.
I addressed this in my last message. You need to provide further
evidence rather than just repeat the same thing again.
4. It becomes more than informational.
I fail to understand what you mean by this word. A type declaration is
informational. It informs a language tool what the expected type of a
variable is. How is it more than that?
You haven't been paying attention. StringIO doesn't inherit from
String or IO, but it will act like either. If I add a #read method
and a #write method to a random object, it will act a lot like an
IO. If I add #to_str to said object, it acts a lot like a String.
Yes, yes, I know.=20
The method signature is not related to the "duck
type." If you've used Ruby for any length of time, you recognise
that as well. I'm not Matz -- or even Dave Thomas -- but duck typing
is, ultimately, not *caring* about the type of object you're
provided. It's just using the type of object.
I know what you say is gospel, but I think it's a bit of a simplification.=
=20
Consider something like this:
def process_it(file)
...
file.read
...
file.write
end
I've given the parameter a name that makes it seem that only a File is
possible, but we like to be able to pass other things, so long as they
adhere to process_it's view of file's method signature. (In this case,
read and write.) This is what is usually referred to as duck typing.
You can't really pass any kind of object to process_it. You can't give
it an Integer, or (sans some kind of help) a String. The set of types
that can usefully be assigned to file are in practice quite limited.
And it's relatively rare that you actually want file to be able to be
any object. There are cases, but it seems more common that the set of
types is limited. If it really is any object, you have to limit
yourself to methods of Object inside process_it.
We could have type declarations like this:
def process_it(file : IO|StringIO)
#declare file to be one of 2 types
end
or this:
def process_it(file : :read&:write)
...
end
The second form is what you'd want to use, mostly. It would declare
that process_it takes a parameter that can read and write. No classes
are mentioned in the declaration. Incidently, this part: :read&:write
is exactly what java calls an interface. Notice how much smaller and
cleaner it can be in ruby.
Rdoc, or whatever your favorite tool happens to be, could read the
type from a declaration just as easily as from a specially formatted
comment. And this is the type of information that's useful to rdoc;
when writing something that uses process_it, you like to know that it
can take a param that knows read and write, rather than being told
that it has to be a File (too restrictive), or (worst of all) Object
(far too broad).
No, we can't. Interface declarations are simply wasted code time and
space. It's that simple.
I'm sorry you feel that way, but I'm afraid you haven't convinced me.
(Hint: a bald statement of fact, with no support, isn't sufficient.)
I see you have a different view of java's interfaces. I believe that
they are intended as a way to add multiple inheiritance to a single
inheiritance system. A need which mixins in ruby satisfies better. But
interfaces provide much the same capability (in a
declare-everything-and-the-kitchen-sink ahead of time kind of way) in
java that duck typing provides for ruby. In either case a library
author can provide the user with the capability to pass in an object
whose exact class isn't specifically known ahead of time; it just has
to implement a certain calling protocol (or method signature). Ruby is
nice in that you don't have to lay out this protocol ahead of time,
like in java. But sometimes you want to or need to anyway.
Not at all. Self is an indication that my suggestion will work:
I know next to nothing about self, so I'm speculating here. What you
describe sounds like a slightly different kind of type inferencing --
profiler-driven inductive inferencing rather than static deductive
inferencing. I can see that this could be a big performance gain. But
it only tells you which types a variable is likely to have, and does
not reduce the total number of types that are possible. I's think that
the run-time consequence is likely to be more type checking than is
necessary in a statically typed program. Note that static typing can
help optimize this type of profiler-driven optimization just a little
bit more, by eliminating some of those checks. The difference in
performance may not be that great, tho.
As far as the fancy IDEs, I'd personally be a bit more interested in
knowing the total possible set of types, rather than the set of likely
types. This is especially true of my favorite IDE feature: I like to
be able to point to an identifier in my code and find its
definition(s) or the other references to it. This has been enourmously
useful to me when writing C in the past. Autocompletion just gets in
my way... but both require a similar level of type information about
the program.
Haskell, from the little I have read about it, has a very unique
approach to optimization. While successful, it seems like it's tied to
Haskell's functional nature.
Static typing doesn't increase safety, ability to be analysed, or
anything else -- that doesn't help the compiler of said statically
typed language. There are side benefits from IDEs that have been
As a prospective writer of tools that need to analyse ruby, I can tell
you that prospects look a lot brighter with a bit of static type
information to help out. If you really think it's so easy, then tell
me how you would do it. Really, I would love to read a monograph on
how you do, say, find references/find definition in ruby, without any
help or hints or declarations of variable types. Remember, you have to
handle method_missing, too. A given definition of method_missing has
to be found as a definition if it can be called from that particular
point in the program, but not if it can't be called.
at what point in a class definition can you conclusively say that it is
-- or is not -- an Enumerable?
Here you get at the big problem with variable type declaration in
ruby. If types are defined in terms of on object's capabilites, then
an object's type can change at runtime, as methods are added or
removed in the object's class.
The key to resolving this dilemma is to notice that changing a method
implementation at runtime doesn't alter the type in this sense. So,
assignments of values to variables where the declared type is
incompatible with the value should be allowed, if it looks like the
value might acquire the correct duck type before it's actually used.
Likewise, if a value has a correct type currently, but looks like it
might lose that type, it's still ok. We have to assume that the
programmer knows what he is doing in these cases and that by the time
the value is used (ie when we invoke a declared capability that might
be present, but isn't guaranteed) everything would be ok. The
detection of these 'might' values should be straightforward: they're
the values that might be the subject of 'class<<foo' or 'def foo.bar'
statements. Type inferencing plus data flow analysis should do a
pretty good job of detecting them.
Analysis tools have to be careful with these 'might' variables. A
compiler would have to assume that their declared type might not be
correct, so the type must be checked at every use. An IDE has to
assume that they really have all capabilities that they might have --
this might lead to some things in the IDE appearing to have
capabilites that aren't ever present.
Now, your example contains a bit of a twist that I haven't seen
before. To be pedantic (as a program would have to be), I would say
that the variable a is an Enumerable at both calls to inject. (After
all, a.kind_of?Enumerable will return true.) Granted, at the first
inject, it is not a useful Enumerable. We could call it an abstract
Enumerable at that point.