Ask a Question related to Ruby, Design and Development.

  1. #1

    Default Interface checking

    I've been skimming over the thousands of emails about "stereotyping" for
    the last few days, and I thought I'd step in now and say something.

    First of all, please Austin and Sean, keep the personal attacks off the
    list. There's no need for that. You both have strong views, and that's
    great, but there's no need to expose the rest of us to your personal
    attacks. (And Sean, that does include complaining about Austin's off-list
    personal attacks as well). Just keep it professional on the list, please.

    Now for the actual issue.

    If I try to compile the following C program, I'll get some compile-time errors:

    float convert(float temperature) {
    return (temperature - 32) * 0.6;
    }

    int main(int argc, char **argv) {
    printf("Temp is %d\n", convert(32.0));
    printf("Temp is %d\n", convert(32));
    printf("Temp is %d\n", convert("hot"));
    return 0;
    }

    It will tell me that "hot" is an incompatible argument to "convert". In
    this case, that's true. The C compiler does a great job of catching this
    error. It is helpful because it identifies the function If "convert" were
    declared in an external library and the documentation of it were somehow
    inaccessible this is just the sort of information you'd want, before you
    actually tried to use the program.

    If you try a brain-dead simple Ruby implementation of the same thing, you
    get an error, but it isn't nearly as informative:

    def convert(temp)
    (temp - 32.0) / 0.6
    end

    puts "Temp is " + convert(32.0).to_s
    puts "Temp is " + convert(32).to_s
    puts "Temp is " + convert("hot").to_s

    foo.rb:2:in `convert': undefined method `-' for "hot":String (NoMethodError)
    from foo.rb:7


    If you use "duck typing", in the sense that you trust whatever is passed to
    the "convert" method can be converted to a float, you get no errors. On
    the other hand, the result probably isn't what you want.

    def convert(temp)
    (temp.to_f - 32.0) / 0.6
    end
    ...

    Temp is 0.0
    Temp is 0.0
    Temp is -53.3333333333333


    Things are actually worse if you assume whatever is passed to the program
    can be converted to farenheit:


    def convert(temp)
    (temp.to_farenheit - 32.0) / 0.6
    end
    ...

    foo.rb:2:in `convert': undefined method `to_farenheit' for 32.0:Float
    (NoMethodError)
    from foo.rb:5

    The problem I see with all these errors is twofold:

    1) There's no way to spot them until you actually call the "convert" method
    2) The error message isn't very informative compared to the C error message
    which tells you the name of the function and that you're passing an
    inappropriate type.

    Now, a lot of these problems can be overcome by making a less brain-dead
    "convert" method, but IMHO that isn't the best strategy. That approach
    requires the person writing the library method to be able to anticipate all
    the ways in which someone might misuse the methods they provide, but at the
    same time, not fail when the method is used in an unexpected way that would
    actually have worked. Most library writers are either not smart enough
    (me) or too lazy (me) to do that. I also don't think that testing is the
    answer either, laziness and difficulty in writing complete tests being the
    reason there too.

    Obviously, for Ruby the solution isn't to implement static type checking.
    The general solution isn't to check the class of the argument inside the
    method either. In a dynamic language like Ruby that's either too
    restrictive or prone to error. I don't think checking "respond_to?" is a
    solution either. Take the case above where I ended up doing "hot".to_f.
    It responds to that method, but it is unlikely that the person passing
    "hot" in as an argument wanted it interpreted as 0 farenheit.

    If we want to get from Ruby what we get from static type checking in C, we
    need a way to get the two things above: the name of the method that failed
    and the reason it failed.

    Consider the error message you get in the first example:

    foo.rb:2:in `convert': undefined method `-' for "cold":String (NoMethodError)

    The problem I have with this error message is that it isn't clear that
    "cold" is the argument to the method, and it isn't clear why exactly it
    failed. The error message says that '-' is an undefined message, but (at
    least to me) that doesn't translate to "you passed me a String but I was
    expecting something like a number".

    Now maybe it is possible to modify Ruby so that when you define a method,
    it automatically wraps it in a begin/end block that catches NoMethodErrors
    and, if it determines that the object that caused the NoMethodError is an
    argument to the method it modifies the error to say something like:

    foo.rb:2:in `convert': undefined method `-' for "cold":String; Possibly a
    an illegal argument to convert? (NoMethodError)

    What would make things even more interesting is if somehow it were possible
    to find out what methods an object is expected to implement in the course
    of a method, so given a method like:

    def convert(temp)
    if temp.respond_to?(:to_farenheit)
    (temp.to_farenheit - 32.0) / 0.6
    else
    (temp.to_f - 32.0) / 0.6
    end
    end

    It might know that temp needs to implement "respond_to?" and one of
    "to_farenheit" or "to_f". Then you could get an error message like:

    foo.rb:2:in `convert': undefined method '-' for "cold":String; Possibly a
    an illegal argument to convert? Required methods: [:respond_to?,
    :to_farenheit] or [:respond_to?, :to_f] (NoMethodError)

    I think that would be incredibly useful, but most likely very, very
    difficult to implement.

    Anyhow, there's still the other problem: C finds argument-type errors at
    compile time, Ruby finds them at runtime. Is there a way to have Ruby find
    them immediately rather than when the method is called? Because of the
    dynamic nature of Ruby, the only possible answer to this is 'no'. But,
    having said that, there are other possibilities.

    When you run ruby with the '-d' flag set, it prints to stderr all
    exceptions, whether caught or not. This means that it often prints things
    to stderr that don't need to be there. Maybe it is possible to come up
    with a similar type checking mechanism. At "compile time" (whatever that
    means in an interpreted language) it would try to look for type
    inconsistencies, meaning arguments to methods that don't implement the
    required methods. It could print those out as warnings, then continue,
    hoping that by the time it actually calls those methods, things will have
    been resolved. How this would be done? I have *no* idea. It sure sounds
    difficult, but it would sure be useful.

    Now even of all the above could be done, there would still be errors. Just
    because a method exists doesn't mean it does the right thing. A method
    named 'read' might be a verb telling an IO-type object to read something,
    or it might represent a boolean flag indicating a message-type object has
    been read.

    Maybe the easy solution for the short term is to do like Python and emacs-lisp:

    def foo(bar)
    "Foo takes a 'Bar' as a parameter. It will try to do X, Y and Z with it"
    ...
    end

    irb> foo("hot")
    in `convert': undefined method `-' for "hot":String (NoMethodError)
    irb> foo.docstring
    "Foo takes a 'Bar' as a parameter. It will try to do X, Y and Z with it"
    irb> foo(25.0)
    5.0
    irb>

    This docstring type functionality wouldn't help much when running programs
    on the commandline, but it would be really useful when debugging or poking
    at things with irb.

    Comments?

    Ben



    Ben Giddings Guest

  2. Similar Questions and Discussions

    1. #39403 [NEW]: A class can't implements interface implemented by implemented interface/classes
      From: baldurien at bbnwn dot eu Operating system: Irrelevant PHP version: 5.2.0 PHP Bug Type: Feature/Change Request Bug...
    2. Web DB Interface
      Hello, I am receiving a 500 internal error after creating a database wizard connection in FP. I can get to the login page and login. When the...
    3. Just checking out the new look...
      ....it's definitely different! Will take some getting used to. :-) Barb
    4. Does Interface Size Matter? Is my interface too complex?
      Hi all, Does Interface Size Matter? Is my interface too complex? I volunteered to design a couple of web pages, but since we don't have a...
    5. URL checking
      mandy100@ihug.com.au (Mandy) wrote in news:6522b540.0306281641.4bbf5b23 @posting.google.com: see...
  3. #2

    Default Re: Interface checking

    On Friday 21 November 2003 11:28 am, Ben Giddings wrote:
    > I've been skimming over the thousands of emails about "stereotyping" for
    > the last few days, and I thought I'd step in now and say something.
    >
    > First of all, please Austin and Sean, keep the personal attacks off the
    > list. There's no need for that. You both have strong views, and that's
    > great, but there's no need to expose the rest of us to your personal
    > attacks. (And Sean, that does include complaining about Austin's off-list
    > personal attacks as well). Just keep it professional on the list, please.
    Appeal to the negativity I've received while trying to help work through this
    idea. I'm a perfectly civil person, but I don't allow people the freedom to
    continually harry me. I will grant everyone this respite, though: instead of
    responding with "whatever you say Ziegler" I will simply ignore him.

    The rest of your comments are terrific, and I hope they generate some change
    ideas!

    Sean O'Dell



    Sean O'Dell Guest

Posting Permissions

  • You may not post new threads
  • You may post replies
  • You may not post attachments
  • You may not edit your posts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139