DISQUS

David R. MacIver: Writing things right

  • Basu · 11 months ago
    Interesting argument. But I think there is something to be said from a software engineering standpoint. If we do obj.func(), then there is an interpretation that func() is some function that should naturally happen to obj (let's say trim a string). Hence the language/class/library designer chose to place func as part of the class definition itself.
    On the other hand func(obj) means that func is something that could technically be done to obj, but it's not placed inside obj's class def because it's a domain specific application (treating a string as a pathname) or otherwise limited enough in scope that making in part of the class def would only be bloat.
    Of course, this doesn't really help the issue of syntactic clutter for users. Going back to software engineering, there are two things you can do if you need to do something on a string that doesn't come packaged. Using your string example, you could write a StringUtils class containing a bunch of functions that work on strings, but most languages would give you the problems you described. But you can also subclass String to have SuperString objects on which you can use the postfic notation.
    I'll be the first to admit that using subclassing too much in OO is not a good idea, but I'll also wager that it's justified when we're talking about performing natural actions on objects.
  • david · 11 months ago
    No, subclassing isn't the right solution to the problem at all. The problem is that all the existing methods on String return String, not MyString. e.g. suppose I wanted to do

    "foo".substring(1).rtrim

    I'd now have to do

    (new MyString("foo".substring(1))).rtrim

    And it wouldn't compose.

    e.g. (new MyString((new MyString("foo")).rtrim.concat("stuff"))).rtrim

    You need to add wrappers for MyString all over the place.

    In Scala you could do this with an implicit conversion to remove the baggage (although in both Scala and Java String is final so you can't do this at all), but then we're back to the previous non-optimal solution.

    The nice thing about the Haskell style solution is that you get to make the choice about position. If you feel that this is somehow "intrinsic" you can write it on the right. If you don't, you can write it on the left. e.g. print x and x.print are both valid ways of writing it.

    Ultimately I don't think your notion of intrinsic operations on an object is at all useful, and usage patterns from people with extension methods, implicit conversions and open classes seem to agree. I would much rather have the flexibility and low impact of being able to write it either way as I choose.
  • Onne · 11 months ago
    Quite useful I'd argue:

    length(nodups(toLower(trim(" some String "))))

    is quite annoying, compared to:

    " some String ".trim().toLower().nodups().length()

    The latter is more like a recipe:

    mix the eggs and the flour, add the milk, stir until smooth, add the sugar, set aside for 20 minutes ... etc.

    While the first is like this, and rather hard to read:

    set aside for 20 minutes( add the sugar( stir until smooth( add the milk( mix the( eggs and flour )))))

    right? first things first, basically; that is why people (at least me) prefer functions after the object. (I'm not a native English speaker, so my recipe example might sound a bit off.)
  • david · 11 months ago
    I agree it's annoying to write and read things in that order. That's somewhat the point of the article. :-)

    But the trick I mention at the end is not all that useful in most languages because of the difficulty of implementing it in a non-noisy manner.
  • Ricky Clarkson · 11 months ago
    Onne:

    The difference in your case is the amount of nesting of parentheses. If you had to write your second example as (((("some String").trim()).toLower()).noDups()).length() you'd feel just as bad about it as the first. Normal Haskell code:

    length . nodups . toLower . trim $ "some String"

    or:

    trim >>> toLower >>> noDups >>> length $ "some String"
  • Henrik Huttunen · 11 months ago
    Hi David.

    How come "each of these solutions requires effort at each definition site in order to make something available that you always want at the use site". Did I miss something?


    object Test{
    object Library{

    def sum(array: Array[Int]): Int = array.foldLeft(0)(_ + _)

    def filter(array: Array[Int]) = array filter(_ % 2 == 0)
    }

    import Library._

    class Extended[t](x: t){
    def >>[s](lambda: t=>s):s = {
    lambda(x)
    }
    }

    implicit def any2lambda[t](x : t) = {
    new Extended(x)
    }


    println( Array(4, 3, 10, 5) >> filter _ >> sum _ ) // 14
    }

    I think this is fairly useful.
  • jherber · 11 months ago
    can always view through the lens of hidden intent:

    send operation to subject
    "string".send :trim
    // when you want to emphasize the invocation of an operation and de-emphasize the declaration between the object and a verb property?

    perform verb on object
    trim("string")
    // when you want to emphasize verb?

    object has a property that is a verb
    "string".trim
    // OO conformance - move along, nothing exciting here!

    i would consider order of execution of multiple operations on a subject as yet another dimension. do we want to "pipe" between lazy functions or are we chaining methods or monads?

    the bottom may be that flexibility gives us expressiveness, and some languages have chosen to let us assign operators to invocation operations, while others have not :/