Intellisense and the psychology of typing

L

Lothar Scholz

Hello Austin,

AZ> But I don't want anything that can reasonably be considered remotely close
AZ> to static typing.

Maybe i missed a posting but i couldn't find anybody suggesting this.
 
C

Caleb Clausen

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.
If not ... too bad.

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.
 
G

Gavin Kistner

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.

Speaking for my own understanding/interpretation:

What is the semantic difference between a hint and a declaration?

A hint says "Hey, this parameter/return value *might* be of Type Foo".

A declaration says "This parameter/return value *must* be, is
*guaranteed* to be of Type Foo".

A hint is appropriate for suggesting methods that might be
applicable. It would be inappropriate to raise a compilation or
runtime error if the type was not was expected. (Depending on
preference/verbosity level, it might be appropriate to raise a warning.)

Statically-typed languages declare/ensure the types of objects. Ruby
(thankfully, IMO) does not. It would be inappropriate (again, IMO) to
use a syntax in Ruby for type hints which could be reasonably
mistaken for type declarations.

The principle of least surprise is a good principle in language
design.

Having just hijacked another thread to shout "guys, please stop
touting POLS for Ruby!", I will yet stand up and agree with you on
this. Any good UI design (be it visual or the interface to a
language) should strike a balance between performing as the user
expects and performing well.

When the benefit from violating a user's expectations is minimal,
it's usually not a good idea to violate those expectations. For
example, even if a study showed that people were 2% safer if the gas
and brake pedals on a car were reversed, it would be a logistical
nightmare to introduce such a change.

Violating the expectations of the majority of users may be desirable
if the benefits are legion, a paradigm shift of happiness.

[...] I tried to come up with an
objective version of POLS, from the point of view of an average
programmer from another language.

I think this may be the breakdown, however. By attempting to honor
the expectations of users from another language, you are encouraging
them to bring over related concepts as well. If Ruby were a
statically-typed language, this would be a great idea. But dynamic-
typing is a completely different beast from static-typing, and that
needs to be clear. IMO, it is thus important that anything we do that
hints at type is thus very syntactically different from systems which
declare type.
 
R

Robert C. Martin

Yesterday I typed in some C++ code that called a function with two
ints. Intellisense (auto-complete) helpfully told me that the first
formal parameter was called "frontLight" and the second "ringLight". It
occurred to me that I'm getting some semantic help here on top of the
obvious type safety. It seems to me that the existance of this kind of
support is tied to the static typing nature of C++.

I've always been interested in the psychology behind those heated
"static" vs. "dynamic" (quotes to avoid another lengthy discussion
about manifest, latent, explicit, ...) typing debates. So I googled
"Intellisense static dynamic typing" and tried to get a view of the
collective mental landscape of this subject. It appears that the
threads that talk about Intellisense soon run dry. I'm wondering if
this is because:

1) Intellisense is really just another crutch that does more harm than
good? There were a few hardcore defenders of this position but not
many.

2) Intellisense is really useful but hard to implement well in IDEs for
dynamic languages? Can anyone comment on the status of
Intellisense-like tools for dynamic-language IDEs?

3) Users of dynamic languages are always developing/debugging running
programs where the current type of a variable is known and hence
Intellisense is possible again? My own limited experience with dynamic
languages (Ruby) is limited to edit-run cycles.

Any opinions?

Static typing is a strong enabler of certain intelisense functions;
but not all, nor even most. A good IDE for a dynamically typed
language can make quite a few inferences to decide how to suggest
intellisense. That said, statically typed languages will always have
better intellisense than dynamically typed languages.

Indeed, one of the reasons that I have not switched to Ruby as my
standard language is the wonderful tools available with "IntelliJ" for
Java and "ReSharper" for C#.


-----
Robert C. Martin (Uncle Bob) | email: (e-mail address removed)
Object Mentor Inc. | blog: www.butunclebob.com
The Agile Transition Experts | web: www.objectmentor.com
800-338-6716


"The aim of science is not to open the door to infinite wisdom,
but to set a limit to infinite error."
-- Bertolt Brecht, Life of Galileo
 
J

Jacob Fugal

Sorry to take just a small incidental part of your post and veer
offtopic with it but I'm curious...

There are many dynamic languages with variable type declarations.
Javascript and scheme leap to mind.

In what way does Javascript have type declarations? I won't claim to
be an expert, but I am quite proficient with Javascript and can't seem
to come up with a candidate syntax for what might be type
declarations. Can you elaborate further?

Jacob Fugal
 
A

Austin Ziegler

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.
And I still fail to see the semantic difference between the two.
[...]

Type hints are just that: hints. They aren't restrictions. Type
declarations are restrictions. They aren't suggestions. They are
mandatory. It's that simple, and they're not -- and never have been
-- two different words for the same thing.

It is a huge semantic difference.
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.

1. The information may be useful for documentation purposes, but not
be useful for optimization. This is by design. If I say that my
return type is aString, that will not help an optimizer.
2. It may be useful for optimization, but will also be useful for
documentation. This is by design. If I say that my return type is
String, that will help an optimizer.

Type declarations require #2 only. Type hints permit #1. #1 is the
only thing that Ruby should concern itself with, through one means
or another.
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).

Matz has requested that this term not be used in any case when
discussing Ruby. Therefore, Caleb, don't use it in an argument. I
don't particularly care what people find surprising in Ruby. Every
language will surprise you at some time, and most of the time it
will be pleasant in Ruby.
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.

Seeing as Ruby does not have and should not have a static typing
system, I don't see where it would be incorrect. Having been through
this discussion many times, I think that it's quite clear that one
can't really have a meaningful type declaration system that isn't in
some way or another name-based. It might be through identifying
modules (e.g., EnumerableInterface or HashSemantics or
StringSemantics), but there has to be some short-hand way --
otherwise you're making this happen through runtime considerations
only (e.g., #respond_to?), and even that's not perfect because #[]
means something different for different classes, even though it's
similar.
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?

You are incorrect here, at least regarding ECMAscript/Javascript.

var foo =3D true

This does not declare foo as a Boolean variable. It sets the foo
variable to the Boolean value of true.

I don't know scheme. So, no, Javascript doesn't have variable type
declarations. It has variable declarations -- that are optional in
any case, IIRC.
Every time you change the language, it's no longer the same
language, but so what?

Untrue. Every time you change the language, you can end up with a
variant of the language. However, type declarations -- especially
restrictive type declarations -- are contra-Ruby in the sense that
they add static, name-based typing to Ruby.
I addressed this in my last message. You need to provide further
evidence rather than just repeat the same thing again.

No, you didn't address it, and it doesn't need further evidence. But
just for you:

* Restrictive typing (especially name-based typing, and there is no
meaningful type declaration that isn't name based) in Ruby is
axiomatically wrong. (This is not just my opinion; it is widely
held. See the recent discussions about homogeneous collections and
see how most people in the Ruby community have suggested that it
isn't the right way to think about the problem.)
* If restrictive typing is axiomatically wrong, a language feature
that makes it possible is wrong.
* If an informational tool is made to look like a restrictive typing
feature, then it is *also* wrong.

If you're not accepting the first point, then it's quite clear that
you're not going to agree with the follow-on points. But that first
point is the key.
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?

No, a type declaration is much more than informational. It is
declarative (rather obvious) and states a fact (see the definition
of declarative). Something that is informational is "A collection of
facts from which conclusions may be drawn."

The declaration is more restrictive than the hint.
I know what you say is gospel, but I think it's a bit of a
simplification. Consider something like this:
=20
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.

But they are not able to be encapsulated simply or even declared.
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

Which immediately restricts the object to those types -- and ignores
mock objects. Allowing this form would be wrong for the reasons
documented above.
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.

Disagree that I'd want to use that. First, it would mean that I
would need to document every method that I -- or a child class --
uses, otherwise someone might send me a file-like-object that can
#read and #write, but if I later add #seek to the method without
updating the requirements ... well, we're no better off than we are
now.

This is what I mean about the declaration being restrictive. You
have given me the false sense of security that all I need to provide
to #process_it is an object that is #read and #write capable. The
language might actually start placing those restrictions in place.
It's too damn bad if I've misrepresented this, or can't meaningfully
tell you what my child methods require. Whereas an rdoc hint can
tell you a lot more by suggesting what the input might be -- but
doesn't have to be.

Further, I don't see such #respond_to? declarations as being
remotely useful to an optimizing compiler. I could be wrong here,
but I just don't see it.

[...]
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.)

In Ruby, it's a waste of code time and space BECAUSE it provides
false and incomplete guarantees for no tangible benefit.

[...]
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.

If you want to lay out the protocol, I see it as a solely
documentation issue, not a compiler issue.
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.

That is what I described the first time around. I *did* emphasize
that it has to do more with long-running programs and a cache of
compiled code.
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
think that the run-time consequence is likely to be more type
checking than is necessary in a statically typed program.

Certainly there will be more -- but there are more already. But
there aren't as many Stupid Tricks then necessary (by the
programmers) as there are in statically typed languages to get
around the silly restrictions of static typing languages.
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.

I don't think so. Consider a method #foo(bar). If bar is an Integer,
then the optimizer will compile #foo to operate optimally on Integer
values. When #foo is called with a String, then there will be a
cache miss and the compiler will recompile the method for String
values. When #foo is called with an Array, there will be a cache
miss, too.

There are further optimisations the compiler could theoretically
look for:

def foo(bar)
bar =3D bar.to_s
:
end

If it sees that bar is never used as anything but a string, then it
could compile the String version and then simply intercept non-
String calls with a call to the String version after an explicit
#to_s call.
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.

I'll be honest; I haven't needed this for the most part in my Ruby
code. Part of this is because Ruby doesn't allow overloading (only
overriding). It's very useful in C++ and Java, but I haven't really
needed it in Ruby.
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.

Actually, the IDE/tool stuff can be handled by discounting
#method_missing. It's a blip, in my experience. Not nonzero, of
course, but a blip nonetheless. I've only played a little with the
JEdit plugin, but it seems to be reasonably successful.

Generally, if someone is using an object that defines a nonstandard
#method_missing, well, you have a problem.
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.

Precisely the big problem. I have some adaptive code that modifies
an object in realtime; if it doesn't find a method it wants on an
object, it adds it. Now, it adds it assuming that the object
received is a String-like object, but it adds it nontheless. (Well,
to be more accurate, it does it as an ordered sequence where
#[offset, length] will work.)

[...]
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.

And that, of course, is the problem. I have seen people want to
define an EnumerableInterface that you *also* have to include to
make a complete enumerable. This would define #each ... to throw an
exception if not overridden.

*sigh*

-austin
--=20
Austin Ziegler * (e-mail address removed)
* Alternate: (e-mail address removed)
 
L

Lothar Scholz

Hello Austin,

Sorry to say this, but instead of writing so much text please
sit down and read the Common Lisp specification (google for it,
it is written by Guy Steel and available on the net). Then come back
and think again if you really want to be more dogmatic then the pope.

Most of your arguments are simply wrong because we are not talking
about type systems (which have a closed model of types). Instead we
are talking about heuristics and a choice of what fits best for a
certain situation. And yes sometimes this is statically typed.
In Lisp you can have both, with the implementation sideeffect that the
later is checked only at runtime and may crash the whole system
because it is only assumed but not checked (speed higher priority then
safety).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top