"Humane" programmer interfaces

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Dave Benjamin

    #1

    "Humane" programmer interfaces

    There's been a lot of discussion lately regarding Ruby and the notion of a
    "humane" interface to objects like arrays and maps, as opposed to
    "minimalist " ones. I believe the article that started the debates was this
    one by Martin Fowler:



    And this one was posted in response by Bruce Eckel:



    When I see the term "Humane Interface", I immediately think of the book
    "The Humane Interface" by the late Jef Raskin. Although this book mainly
    focuses on GUI interaction, it is also about the human-computer interface
    in general, and makes some points that apply to the programming experience
    as well.

    The irony, I find, is that Raskin's definition of a humane interface seems
    to better fit what Fowler describes as "minimalist ". Raskin argues that
    two ideals in interface design are what he calls "modelessne ss" and
    "monotony". The two terms essentially relate to causes and effects.

    Modelessness means that the same cause produces the same effect. "Modes"
    are the different states that an application might be in; for example, if
    an app has a "novice" and "expert" mode, the user has to be consciously
    aware of which mode they are in, and performing the same action in
    novice mode may not produce the same result as if it were performed in
    expert mode. This somewhat resembles the notions of referential
    transparency, side-effects, global and shared state in programming.

    The other side of the coin, "monotony", says that one effect should have,
    ideally, only one related cause. In a nutshell, "there's only one way to
    do it". Many of the arguments given in this newsgroup in favor of this
    Python design principle use a similar rationale:

    "I use the term monotonous, tongue partially in cheek, to describe an
    interface having only one way to accomplish a task (See Appendix B;
    Alzofon and Raskin 1985, p. 95). Monotony is the dual of modelessness in
    an interface. In a modeless interface, a given user gesture has one and
    only one result: Gesture g always results in action a. However, there is
    nothing to prevent a second gesture, h, from also resulting in action a. A
    monotonous interface is one in which any desired result has only one means
    by which it may be invoked: Action a is invoked by gesture g and in no
    other way. An interface that is completely modeless and monotonous has a
    one-to-one correspondence between cause (commands) and effect (actions).
    The more monotony an interface has for a given task space, the easier it
    is for the user to develop automaticity, which, after all, is fostered by
    not having to make decisions about what method to use."

    Thus, when I see people claiming that Ruby's array interface is more
    humane because it offers a "last" method as an alternative to the
    equivalent "get" method with a last index, I have trouble reconciling
    these two views. I especially have trouble with the idea that method
    aliases (like "size" and "length" methods that do the same thing) are
    somehow more humane. How is it an advantage that half the programmers
    accomplish a given task with method A, and the other half use method B? It
    doesn't seem much friendlier to me, since I have to look up these aliases
    to make sure my assumption is correct that they are identical. Usually,
    when I see two names for the same thing, I assume there's some (perhaps
    not-so) subtle difference in their purpose.

    On the other hand, absolute minimalism may not be the most user-friendly.
    Python's strings support "startswith " and "endswith" methods, which could
    easily be implemented--outside of the string class--in terms of slices.
    However, I love having these methods available off of the string, not
    in some library of string utilities, because they are so handy and so
    often useful (and they save me from having to think about indexes and
    offsets when, conceptually, they are not relevant to the problem at hand).

    As an example of the minimalist viewpoint, I highly recommend reading
    Bjarne Stroustrup's interview with Artima from a couple of years ago,
    called "The C++ Style Sweet Spot":



    In the article, he explains an approach to class design that I have found
    invaluable: giving the class responsible for maintaining the invariant,
    and moving utility methods that do not bear this responsibility into
    utility libraries. If this approach were taken with Python's built-in data
    types, surely a few methods would have to go elsewhere, but I think that
    Python takes a pretty minimalist approach, and encourages the writing of
    utility functions as opposed to making every useful tool "part of the
    core". I think that, in contrast, Ruby goes a bit overboard.

    Note that Stroustrup is willing to budge on this ideal for the sake of
    efficiency: "you might have five or ten member functions that are there
    because they are logically necessary, or for performance reasons".
    Likewise, Python sometimes includes methods in the core classes that could
    be implemented elsewhere, specifically for the sake of performance.
    "dict.setdefaul t" comes to mind. Another, more debatable reason, is
    convenience, and this is where most disagreement is found. What is
    convenient to one person is added conceptual baggage for another. It is
    difficult to strike a good balance between the two, but erring on the side
    of minimalism is much easier to correct in the long term.

    One last comment I'd like to make is regarding the notion that minimalist
    and humane are mutually exclusive. This is a false dichotomy. According to
    Webster:

    Main Entry: minimalism
    Pronunciation: 'mi-n&-m&-"li-z&m
    Function: noun
    1 : MINIMAL ART
    2 : a style or technique (as in music, literature, or design) that is
    characterized by extreme spareness and simplicity

    Main Entry: humane
    Pronunciation: hy-'mAn, y-
    Function: adjective
    Etymology: Middle English humain
    1 : marked by compassion, sympathy, or consideration for humans or animals
    2 : characterized by or tending to broad humanistic culture : HUMANISTIC
    <humane studies>

    Accepting both of these definitions as (potentially) ideal, we have humane
    minimalism: a style or technique that is characterized by extreme
    spareness and simplicity, marked by compassion and sympathy, in
    consideration of humans. In a world of increasing software complexity,
    this doesn't sound half bad to me.

    --
    .:[ dave benjamin: ramen/[sp00] ]:.
    \\ "who will clean out my Inbox after I'm dead[?]" - charles petzold
  • Kent Johnson

    #2
    Re: &quot;Humane&qu ot; programmer interfaces

    Dave Benjamin wrote:[color=blue]
    > There's been a lot of discussion lately regarding Ruby and the notion of
    > a "humane" interface to objects like arrays and maps, as opposed to
    > "minimalist " ones. I believe the article that started the debates was
    > this one by Martin Fowler:
    >
    > http://www.developertesting.com/arch...Interface.html
    >
    >
    > And this one was posted in response by Bruce Eckel:
    >
    > http://www.artima.com/forums/flat.js...&thread=141312[/color]

    It was actually Elliotte Rusty Harold's response to Martin Fowler that
    kicked of the discussion. The first one is here, there are several
    followups:

    [color=blue]
    > One last comment I'd like to make is regarding the notion that
    > minimalist and humane are mutually exclusive. This is a false dichotomy.
    > According to Webster:
    >
    > Main Entry: minimalism
    > Pronunciation: 'mi-n&-m&-"li-z&m
    > Function: noun
    > 1 : MINIMAL ART
    > 2 : a style or technique (as in music, literature, or design) that is
    > characterized by extreme spareness and simplicity
    >
    > Main Entry: humane
    > Pronunciation: hy-'mAn, y-
    > Function: adjective
    > Etymology: Middle English humain
    > 1 : marked by compassion, sympathy, or consideration for humans or animals
    > 2 : characterized by or tending to broad humanistic culture : HUMANISTIC
    > <humane studies>
    >
    > Accepting both of these definitions as (potentially) ideal, we have
    > humane minimalism: a style or technique that is characterized by extreme
    > spareness and simplicity, marked by compassion and sympathy, in
    > consideration of humans. In a world of increasing software complexity,
    > this doesn't sound half bad to me.[/color]

    ISTM that Python does better than Java or Ruby in finding a sweet spot
    between awkward minimalism (Java) and
    everything-including-the-kitchen-sink generalism (Ruby, at least in the
    List example that is being used so much).

    Access to the last element of a list is a great example. Java forces you
    to use the awkward list.get(list.s ize() - 1). Ruby gives you the
    specialized list.last. Python generalizes to a way to access any index
    from the end, with list[-1] as a concise special case of a general facility.

    I think this is one of the great strengths of Python - that those who
    create the language have been able to discover very powerful, general
    concepts and apply them broadly and consistently to solve a variety of
    problems.

    Kent

    Comment

    • kpd

      #3
      Re: &quot;Humane&qu ot; programmer interfaces

      list[-1] maps very well to my mental concept of list. To me 'List'
      brings to mind a bunch of things in a line. It's intuitive to count
      forward or backward.

      Ruby's 'last' doesn't map as well for me because I don't think of the
      list as having an attribute of 'last.'

      Java just annoys me because I can never remember if it is 'size',
      'length' or something else. Similarly, Java's conversion operators -
      string to int or back - are something I am *always* referring to as
      they just don't fit my mental model.

      I agree with Kent, it's so subjective, but I've also found that Python
      hits the right level.

      Comment

      • colinwb

        #4
        Re: &quot;Humane&qu ot; programmer interfaces

        Apologies if I'm misunderstandin g some points in kpd's post, but:



        [ ]
        arr[anInteger] -> anObject or nil
        arr[start, length] -> aSubArray or nil
        arr[aRange] -> aSubArray or nil

        Element Reference

        Returns the element at index anInteger, or returns a subarray starting
        at index start and continuing for length elements, or

        returns a subarray specified by aRange. Negative indices count backward
        from the end of the array (-1 is the last element).

        Returns nil if any indices are out of range.

        [ ]=
        arr[anInteger] = anObject -> anObject
        arr[start, length] = aSubArray -> aSubArray
        arr[aRange] = aSubArray -> aSubArray




        Interactive ruby ready.[color=blue][color=green]
        >> ck = Array [ 'C', 'K', 'S', 'vvg' ][/color][/color]
        => ["C", "K", "S", "vvg"]
        [color=blue][color=green]
        >> puts ck.first, ck[0], '*', ck.last, ck[-1][/color][/color]
        C C * vvg vvg
        => nil
        [color=blue][color=green]
        >> puts ck[0..2], '*', ck[-4..-2][/color][/color]
        C K S * C K S
        => nil
        [color=blue][color=green]
        >> puts ck.size, ck.length[/color][/color]
        4 4
        => nil

        (The print elements in these example were actually
        printed on separate lines. I've put them on the
        same line for clarity and to save email space.)

        Comment

        • Andrew Durdin

          #5
          Re: &quot;Humane&qu ot; programmer interfaces

          On 29 Dec 2005 09:50:57 -0800, colinwb <colinwb@yahoo. co.uk> wrote:[color=blue]
          >[color=green][color=darkred]
          > >> puts ck.first, ck[0], '*', ck.last, ck[-1][/color][/color][/color]

          One of the points at issue (minimalism/monotony) relates to TOOWTDI,
          which has implications for language/module design and for code
          readability. Ruby supports negative indices in the same way as Python,
          so why add .last as an alias for [-1]? It's just added mental baggage.
          And if you think .last is significantly more readable or convenient
          than [-1], why not add .secondlast ? And .first, and .second ? With
          one simple, obvious, general interface, these special cases only add
          unneeded complexity.

          Non est ponenda pluritas sine necessitate -- William of Ockham.

          Andrew

          Comment

          • Alex Martelli

            #6
            Re: &quot;Humane&qu ot; programmer interfaces

            Andrew Durdin <adurdin@gmail. com> wrote:
            [color=blue]
            > On 29 Dec 2005 09:50:57 -0800, colinwb <colinwb@yahoo. co.uk> wrote:[color=green]
            > >[color=darkred]
            > > >> puts ck.first, ck[0], '*', ck.last, ck[-1][/color][/color]
            >
            > One of the points at issue (minimalism/monotony) relates to TOOWTDI,
            > which has implications for language/module design and for code
            > readability. Ruby supports negative indices in the same way as Python,
            > so why add .last as an alias for [-1]? It's just added mental baggage.[/color]

            In this particular case, it can be opined that it's more readable to
            code goo.last than goo[-1]. For an analogy, consider, in Python,
            somestr.startsw ith('glab') as a more readable equivalent of
            somestr[:4]=='glab' -- the "why add?" question is easily answered,
            although it's of course possible to disagree with the specific decision
            made in each and every single case in which the same functionality is
            provided in two distinct ways.

            Python has a LOT of cases in which you can get the same functionality in
            distinct ways, and not all of them are judged to fall afoul of the
            "there should ideally be only one way" principle. Operations that have
            to do with slicing of sequences are a substantial class of this "added
            mental baggage", and not just for strings' endswith and startswith
            methods. Consider, for example:

            del somelist[start:finish]
            somelist[start:finish] = []

            exactly identical semantics. Or:

            somelist.extend (another)
            somelist[len(somelist):] = another

            or (for suitable values of i, only):

            somelist.insert (i, blah)
            somelist[i-1:i] = [blah]

            or

            acopy = somelist[:]
            acopy = list(somelist)

            In each case, one could argue that the named alternative (del, extend,
            insert, list, ...) is more readable; but the slice-based alternative is
            also provided because slicing is such a powerful and general tool, and
            when you're computing the slice's boundaries at runtime it's precious.

            So, I don't think it would violate the spirit of Python to have
            somelist.last mean the same as somelist[-1], e.g.:

            class listwithlast(li st):
            def getLast(self): return self[-1]
            def setLast(self, value): self[-1] = value
            last = property(getLas t, setLast)

            Such tradeoffs need to be decided case by case, and, in Python, they
            have been -- e.g., there's no "somelist.getod d()" synonym for
            somelist[::2], for example;-). I think the myriad specific decisions
            and trade-offs may be disputed (and it's not impossible that a strong
            enough case can be made for one more such "synonym", so that a PEP
            proposing it might in theory be accepted for a future Python version),
            but essentially only on a case by case basis (it's good to keep in mind
            principles such as "all other things being equal it's better to have one
            way than more than one", of course, while taking such decisions).

            I do draw the line at foo.size being defined as identical to foo.length,
            which I personally classify as a design error (I don't think Python is
            exempt from design errors at this minute level, either -- e.g., the
            method name 'setdefault' is murky, though I can't propose a better one,
            and the existence of both pop and popitem in dictionaries appears to
            cause more confusion than benefit, where it might have sufficed to make
            pop's argument optional if one wanted popitem's functionality).
            [color=blue]
            > And if you think .last is significantly more readable or convenient
            > than [-1], why not add .secondlast ? And .first, and .second ? With
            > one simple, obvious, general interface, these special cases only add
            > unneeded complexity.[/color]

            first and last make sense (though it's debatable whether they should
            exist or not, it's not obvious that they're design errors, not at all);
            second, penultimate, and other variations, would be gilding the lily.
            Just like startswith and endswith may (debatably) make sense, but, say,
            hasrightinthemi ddle would be surely inappropriate;-)


            Alex

            Comment

            • Alex Martelli

              #7
              Re: &quot;Humane&qu ot; programmer interfaces

              Dennis Lee Bieber <wlfraed@ix.net com.com> wrote:
              ...[color=blue][color=green]
              > > code goo.last than goo[-1]. For an analogy, consider, in Python,
              > > somestr.startsw ith('glab') as a more readable equivalent of
              > > somestr[:4]=='glab' -- the "why add?" question is easily answered,[/color]
              >
              > Using constants for the example does obscure things a bit...[/color]

              A little, yes, and I do agree that writing len('glab') rather than the
              "magic number" constant 4 would make things clearer, as in your example:
              [color=blue]
              > if somestr[:len(needle)] == needle:[/color]

              This is definitely more readable, and the same would apply if I tested
              somestr[:len('glab')] == 'glab' -- the key point being that one knows
              where that slice limit comes from, rather than having to guess.

              But that doesn't really change the main point, that Python does offer
              redundant ways to perform several tasks -- a general one (often based on
              slicing) and a specialized one with a readable name -- as in this
              example and several others I brought. Yet another: you could always use
              the more general 'extend' method to add just one element, via
              somelist.extend ([element]), but the more readable synonym
              somelist.append (element) is also offered (and becomes thereby "the
              obvious way" to perform this common task). Having a '.last' property to
              perform just the same task as a [-1] indexing is not a very different
              concept -- I'm not saying it's obviously right, but criticizing it as
              "obviously wrong" because of the [-1]-indexing equivalence is, IMHO, a
              very misplaced viewpoint. Much like I prefer using list(alist) to make
              a copy (more readable), rather than the concise idiom alist[:] based on
              slicing, I might well prefer to use alist.last rather than alist[-1], if
              I had a choice (which doesn't mean I'd rather have a choice -- I think
              that Python made the right tradeoff here, as in many other cases, but by
              a very thin margin in this one case, IMHO).

              That some (not all) of the numerous "general idioms" for which Python
              offers more readable synonyms may require (or at least be best expressed
              with) some len(...) call is hardly a big sub-issue -- some do, like e.g.
              L[len(L):]=... as the equivalent of L.extend(...), some don't, like e.g.
              del L[a:b] as the equivalent of L[a:b]=[], and yet the "alternativ e
              readable synonym" gets offered anyway.


              Alex

              Comment

              • Andrew Durdin

                #8
                Re: &quot;Humane&qu ot; programmer interfaces

                On 1/3/06, Alex Martelli <aleax@mail.com cast.net> wrote:[color=blue]
                > Dennis Lee Bieber <wlfraed@ix.net com.com> wrote:
                >[color=green]
                > > if somestr[:len(needle)] == needle:[/color]
                >
                > This is definitely more readable, and the same would apply if I tested
                > somestr[:len('glab')] == 'glab' -- the key point being that one knows
                > where that slice limit comes from, rather than having to guess.[/color]

                I don't think there'd be much argument that
                somestr.startsw ith('glab') is significantly clearer (in action and
                intent) than somestr[:len('glab')] == 'glab' -- even if you use a
                variable instead of repeating the literal. So this is not an example
                of a purely redundant way of comparing the start of a string, because
                there is a significant gain in clarity. OTOH, that somestr.last is
                *significantly* clearer in action or intent than somestr[-1] is
                questionable. I don't believe that it is -- but I agree with you that
                having it is not necessarily a design error (i.e. "obviously wrong").
                [color=blue]
                > But that doesn't really change the main point, that Python does offer
                > redundant ways to perform several tasks -- a general one (often based on
                > slicing) and a specialized one with a readable name[/color]

                In many cases there can be subtle semantic differences separating the
                "redundant" ways, which makes them non-equivalent.
                [color=blue]
                > Much like I prefer using list(alist) to make
                > a copy (more readable), rather than the concise idiom alist[:] based on
                > slicing,[/color]

                These are identical only for the case where type(alist) == list. If
                alist issome other sequence-like type, then the equivalence no longer
                holds.
                [color=blue]
                > e.g.
                > del L[a:b] as the equivalent of L[a:b]=[], and yet the "alternativ e
                > readable synonym" gets offered anyway.[/color]

                These too may not be equivalent, if the behaviour of __setitem__ and
                __delitem__ differ for L.__class__; although I cannot think of a good
                reason to not have them equivalent for any sequence-like object.

                As I said before "With one simple, obvious, general interface, these
                special cases only add unneeded complexity." If the general
                interface is either not simple or not obvious for a task, then a
                special interface is likely more suitable.

                Andrew

                Comment

                Working...