If-statements in Smalltalk (2008)

8 years ago (pozorvlak.livejournal.com)

The explanation is almost correct, but misses a tiny, but interesting detail. Subclasses of Boolean do implement #ifTrue: and #ifFalse:, wich each take a block (a no-argument function) as an argument. But they also implement #ifTrue:ifFalse:, which takes two blocks as arguments. In

    a < b:
        ifTrue: [...]
        ifFalse: [...]

a single message (#ifTrue:ifFalse:) is sent to the boolean that results from evaluating 'a < b'. A true, being the singleton instance of the True subclass of Boolean, evaluations the first argument and ignores the second. False has a singleton instance, false, that does the opposite.

Coming from Pascal and C, seeing that IF/ELSE could be implement without special syntax was a real mind warper.

Anyone interested in playing around with Smalltalk can use https://pharo.org - I recently tried it and was pretty amazed with the quality of documentation available. It is quite a different experience working in an interactive live environment.

  • I briefly looked into it, but I can't seem to figure out what to make with it. It looked like you couldn't write an application with any portability, and any user would basically have to run a smalltalk vm to use your application. Am I mistaken?

    • This is my understanding, yes - http://wiki.c2.com/?ImageBasedLanguage . Some Lisp systems and the Factor language also work like this (though Factor does a lot of work to ensure that the contents of your image matches the code on disk). I believe it's possible to strip down your image for deployment so it only includes code required by your application.

      Then again, we live in the era of Docker, in which it's commonplace to ship an entire Linux filesystem along with your web app. So shipping a Smalltalk VM (2.6MB for Pharo 5.0) shouldn't be too much of a stretch.

    • I'm no expert (not even close), but I believe they'd have to run Pharo itself, yes. So you could redistribute the Pharo runtime along with your app code (similar to, say, Python).

      You may be able to build web applications too (http://seaside.st/).

This applies to loops as well. Numbers are objects that accept the `timesRepeat:` message:

    10 timesRepeat: [ n := n*2 ].

Not only that, you can send messages to blocks, to implement loops.

    [ n < 10 ] whileTrue: [ n := n + 1 ]

  • Compare to Ruby:

        10.times { n = n * 2 }
    

    The latter isn't possible "out of the box", but easy enough to implement:

        class Proc
            def whileTrue; yield while self.call; end
        end
    
        # And use:
        n = 1
        ->{ n < 10 }.whileTrue { n = n + 1 } 
    
        puts n
    

    Ruby syntax does get in the way of some of the more succint ways of defining control structures in Smalltalk, but the above shows some of the Smalltalk heritage of Ruby quite well, I think (though, while "10.times" is fairly idiomatic Ruby, implementing constructs like the one below certainly is not).

Since if-statements are built out of blocks and message sending, you can easily do some cool things. One of them is building up an abstract syntax tree (AST) of an expression without parsing that expression.

E.g. suppose you want to build up an AST of the statement

  (x < 0) ifTrue:[-x] ifFalse:[x]

Instead of starting with an x of type Number, you would start with an object of type ASTVariable, that responds to the message < by creating and returning an object ASTLessThan instead of the Boolean True or False. When that object is sent the ifTrue:ifFalse: message it evaluates both branches to create the ASTs for both branches and then creates an ASTIfStatement.

As an sexp:

  (ASTIfStatement
    :condition (ASTLessThan #1=(ASTVariable :name x) (ASTConstant :value 0))
    :consequent (ASTUnaryMinus #1#)
    :alternative #1#)

I've seen it used in a number of embedded languages in Smalltalk, such as SQL.

  • Something quite similar is used in some RethinkDB drivers, especially around lambdas. For example:

        r.table('users').filter(lambda user:
            user["age"] == 30
        ).run(conn)
    

    The lambda there is actually only ever called once, on the client-side, with an equivalent of your ASTVariable, to generate an AST of the comparison which is sent to the server and processed there.

  • This technique is limited by the fact that it only works if all the messages are sent to x, rather than x being sent some message.

    • You mean "rather than x being sent to some message"? E.g. 0 > x wouldn't work, because the message #> is sent to 0 with x as an argument? In a case like that you can still make it work by exploiting the fact that arithmetic and comparison operators implement double dispatch. The implementation of > for an Integer would send a message to x:

        Integer::> aNumber
          ^x adaptToInteger: self andCompare: #<
      

      Even if x is passed as an argument, to interact with it at some point somebody has to send a message to it. I agree that it can become unwieldy to intercept all possible messages, but for a well-defined subset in your DSL, this can usually be done.

      1 reply →

  • Is this a bit like a runtime macro? Does it have performance disadvantages?

    • > Is this a bit like a runtime macro?

      You can indeed accomplish some similar things with it.

      > Does it have performance disadvantages?

      A Common Lisp macro is evaluated once at compile time. In Smalltalk this is evaluated at runtime, so that adds some overhead. Usually you only have to build the AST only once though, and can use the result many times, so that overhead isn't really relevant.

First, I did not know this, it's pretty cool.

But, for all the fangled cool things that the more powerful languages of yesteryears (like lisp), what seems to actually precipitate adoption is simply history, not necessarily expressiveness/powerful abstractions, etc. See C, sh and friends, javascript. Arguably, python is one where its ease made it popular and it continues to develop language wise, albeit not without controversy.

  • > Arguably, python is one where its ease made it popular and it continues to develop language wise, albeit not without controversy.

    Currently about 7K lines into a python project and I'm getting more used to it but 'leaky abstractions' is a term that springs to mind at least 10 times per day if not more.

    I'm busy with tricky array type conversions to feed stuff from one library into another a lot more than I would expect. Rather than that there is 'one way to do it' it seems there are about 50 of them, all incompatible with each other. Very messy at times.

    • I never said python continues to be a language of ease for all use cases, I am talking about what precipitated adoption, not whether it is the best language it could be. To be fair, its abstractions make things which are downright difficult in C easier.

      My last statement referred to with python 3 and above, there is active development of the language's fundamental constructs. I guess there is active ES6 etc. C's newer standards aren't groundshakingly different from its original implementation.

  • I was drawn to python because of the culture (simple, documentation) and I stay because of the libraries (machine learning, scientific). The language just needs to be better than Matlab or R (not Scheme, Clojure, Scala or pick your favorite one). As in nature the language that thrives is the fittest for its environment, not the most powerful.

    • > As in nature the language that thrives is the fittest for its environment, not the most powerful.

      Now, this is an important observation, but it needs one caveat - "fittest for its environment" != "fittest for the stated purpose". I.e. an objectively better tool may lose to a tool that's barely good enough, but e.g. provides a better CYA for managers, in a self-reinforcing loop of popularity.

      That's the essence of Worse is Better - shit that's barely good enough will outcompete proper solutions.

      (For many, that essay seems to be an ideal to follow; for me personally, it's just the description of the sad state of reality that we need to learn to work around.)

    • > As in nature the language that thrives is the fittest for its environment, not the most powerful.

      Now hold on. The most successful sprog of nature is humanity[citation needed] and we're a case study in the fact that raw intelligence is more effective than fitness for any particular niche. So: as in nature, there may be a bunch of niche languages, but in time they'll find themselves with a conservation status while a smart language rules the earth.

      2 replies →

  • FWIW, this is also the standard way of implementing if/then in the lambda calculus:

    true = \x.\y.x

    false = \x.\y.y

Fascinating. An amazing point about design patterns, and yet...

Declarative statements are a pattern in ruby - you see them everywhere (`attr_accessor`). They're library code - you're intended to write them. I can't imagine trying to write ruby without that pattern, and I can't imagine them not being a design pattern.

Similarly - the MVC of Rails. The lines get way fuzzier, but you still might try to draw similar lines to separate the core framework (badly analogous to the language) from, I guess, all the gems you end up using (badly analogous to the library). Is MVC a pattern? Or under this bad analogy, a language construct...?

  • I was really trying to make a narrower point here - that there is a reasonable way to distinguish "part of the language" from "part of the (standard) library" - but I'll bite :-)

    attr_accessor, as it happens (http://ruby-doc.org/core-2.0.0/Module.html#method-i-attr_acc...) is implemented in C code, but one could implement it in Ruby, so I'd count it as part of the (standard) library, and creating accessors is just using a library method. In, say, Java setters and getters have to be written by hand (or autogenerated by some external system like an IDE), so they're a pattern. On the other hand, the way Ruby translates `foo.bar = 3` into a call to `foo`'s `bar=` method is not alterable by user code, so it's part of the language.

    MVC in Rails is an interesting case. You have to create new classes according to the pattern (either by hand or using `rails generate`), but there's also some library support in the form of Model and Controller classes you're expected to inherit from, and which handle much of the View/Controller plumbing for you. I'd say it's still a pattern, though.

    • Oh, neat! It's always cool when the authors of content pop onto HN (I see from you karma you're new here, welcome!)

      Yeah - I heard your point when I first read your post, but it took some more time and reading to understand it. It's a good distinction.

      What I'm not getting is... So you've got a really precise and elegant line between "language" and "library", but it's not clear what the difference is between patterns and.... maybe idioms?

      attr_accessor, has_many, and carrierwave's mount_uploader (to provide many examples) are all examples of a "pattern" (in the sense of a repeated thing, not a design pattern) in Ruby, but I can't determine what's then in need of "fixing" or what abstraction features could be added to remove the "pattern".

      3 replies →

I wish some of these ideas would bleed into the mainstream. A small simple powerful core language and the minimum possible special syntax.

  • Clojure? It certainly has more syntax than many lisps, but still quite small. (And used in the mainstream world).

  • Smalltalk has been very influential in other ways, especially on Ruby. But yes, it would be great if more languages borrowed the "small simple core" bit (possibly adding some syntactic sugar, like ML does).

    On the other hand, it's possible to make (core language + standard library) too small. A particularly bad thing to leave out is a module/separate compilation system, ensuring that a zoo of incompatible module systems will grow up.

    You may enjoy Guy Steele's talk "Growing a Language": https://www.youtube.com/watch?v=_ahvzDzKdB0

Date: 2008

  • Do you think it's now invalid? Do you think anything has changed? I'd be interested to hear of any news that you think would update this.

    • Not at all, and that's why I upvoted it.

      I mentioned the date because it is traditional in titles. Here it might put comparisons to ES9 and complaints about display on the iPhone 10s in context (I'm optimistic). Pessimistically, it might give fair warning that hyperlinks from the page 404.

      1 reply →