From: Austin Ziegler Date: 2005-06-02T02:26:39+09:00 Subject: Re: Intellisense and the psychology of typing On 6/1/05, Caleb Clausen wrote: > Austin Ziegler wrote: >> 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. >> 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. 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. >> 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). 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. >> 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. 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. >> 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? You are incorrect here, at least regarding ECMAscript/Javascript. var foo = 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. >> 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? 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. >> 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. 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. >> 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? 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. >> 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. 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. 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. [...] >> 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.) 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. >> 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. 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 = 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. >> 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. 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. >> 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. 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 -- Austin Ziegler * halostatue@gmail.com * Alternate: austin@halostatue.ca