Hacker News new | past | comments | ask | show | jobs | submit login
Why I love Smalltalk (pupeno.com)
86 points by mordaroso on July 29, 2011 | hide | past | web | favorite | 37 comments



It covers some of the minimal syntax of Smalltalk but didn't touch on Smalltalk images, which is a major signature of Smalltalk.

A Smalltalk image contains every Smalltalk object. This includes classes (which are Smalltalk objects), instances of the classes, source code, the current running state, everything. You work within the image and interact with it.

Think of the image as a copy of your IDE, along with the compiler, your current project's source code, the compiled classes, methods, everything. You can save your image (state) at any time, not unlike the sleep feature of Windows and Mac. It's a live environment. During a programming session, you edit and save the code of class (it's automatically compiled) and every instance of that class is updated. This sounds like what you commonly have in many dynamic languages, but Smalltalk takes it to an extreme. You can re-configure your "IDE" as you go along since they are running in the same image, all the classes and objects are accessible. Or you can set breakpoints and step through code, changing code on the fly and resuming, all without stopping the "program" and starting again. Not unlike a graphical REPL.

Does anyone know if there's a similar tool utilizing the same concept other than Self?


I consider the image an implementation detail of Squeak or the original Smalltalk-implementation. You can implement Smalltalk the language without the image: http://smalltalk.gnu.org/


The syntax is quite unique to Smalltalk. The message, otherwise known as “method call” in other languages, is called show: (including the colon) and it takes an argument.

self also uses this method call syntax. However this syntax can get quite confusing, see this example from http://en.wikipedia.org/wiki/Self_(programming_language)

  valid: base bottom between: ligature bottom + height and: base top / scale factor.
Smalltalk version provided gets even more verbose:

  valid := self base bottom between: self ligature bottom + self height and: self base top / self scale factor.
Personally I prefer Io message syntax because it has far less ambiguity. See Why not use an infix message syntax like Smalltalk? in the FAQ - http://iolanguage.com/about/faq/


Just for comparison's sake, here's the version in everyone's favorite bastard child of Smalltalk and C, Objective-C:

  valid = [self baseBottomBetween:[self ligatureBottom]+[self height] and:[self base]];


So, what would the syntax be in Io? Like this?

  valid := base bottom between(ligature bottom + height, base top / scale factor)


Yes, unless I've totally misunderstood that Self/Smalltalk code!

Also instead of between method with two args you could also create an and message/method:

  valid := base bottom between(ligature bottom + height) and(base top / scale factor)


As far as I know both Self and Slate are very strongly inspired by Smalltalk.


Thank you, I wasn't familiar with Io before


Your very welcome. Like Smalltalk, its a beautifully designed language.

Some quick examples:

  10 repeat("Hello world" println)

  (y < 10) ifTrue(x := y) ifFalse(x := 2)

  if (y < 10, x := y, x := 2)
More example Io code can be found here: http://iolanguage.com/about/samplecode/


Copy of the text, since it's hard to reach the site itself [any layout errors should be assumed to be mine]:

This post was extracted from a small talk I gave at Simplificator, where I work, titled “Why I love Smalltalk and Lisp”. There should be another post, “Why I love Lisp” following this one.

After I learned my basic coding skill in more or less traditional languages, like C, C++, Python, there were four languages that really tought me something new. Those languages changed my way of thinking and even if I never use them, they were worth learning. They are:

* Smalltalk

* Lisp

* Erlang

* Haskell

You can probably add Prolog to that list, but I never learned Prolog. This post is about Smalltalk.

My goal is not to teach Smalltalk but to show things that you can do with Smalltalk that you can’t do with any other language (disclaimer: surely other languages can do it, and we’ll call them Smalltalk dialects). Nevertheless I need to show you some basics of the language to be able to show you the good stuff, so here we go, a first program:

    1 + 1
That of course, evaluates to 2. If we want to store it in a variable:

    m := 1 + 1
Statements are finished by a period, like this:

    m := 1.
    m := m + 1
In Squeak, a Smalltalk implementation, there’s an object called Transcript and you can send messages to it to be displayed on the screen. It’s more or less like a log window. It works like this:

    Transcript show: 'Hello world'
and it looks like this:

[Squeak transcript showing the result of Transcript show: 'Hello World']

The syntax is quite unique to Smalltalk. The message, otherwise known as “method call” in other languages, is called show: (including the colon) and it takes an argument. We can run it 10 times in a row with the following snippet:

    10 timesRepeat: [
      Transcript show: 'Hello world'
    ]
There you can start to see how Smalltalk is special. I’m sending the message timesRepeat: to the object 10, an Integer. Doing something N times repeatedly is handled by the Integer class, which if you think about it, makes sense.

The second interesting part, is the block. The part inside squared brackets. You might thing that’s the equivalent of other language’s block syntax, like in this Java example:

    for(int i=1; i<11; i++) {
      System.out.println("Hello world");
    }
but Smalltalk version’s is a bit more powerful. It’s a real closure. Look at this:

    t := [
      Transcript show: 'Hello world'
    ]
Now I have a variable named t, of type BlockClosure, and I can do anything I want with that variable. If I send it the class message it’ll return it’s class:

    t class
and if I sed it the value message, it’ll execute and leave a “Hello World” in the transcript:

    t value
Let’s see some more code. A message without any arguments:

    10 printString
a message with one argument:

    10 printStringBase: 2
and a message with two arguments:

    10 printStringBase: 2 nDigits: 10
Isn’t it cute? That method is called printStringBase:nDigits:. I never seen that syntax anywhere else; well, except in Objective-C, which copied it from Smalltalk.

Enough toying around, let’s start building serious stuff. Let’s create a class:

    Object subclass: #MyClass
           instanceVariableNames: ''
           classVariableNames: ''
           poolDictionaries: ''
           category: 'Pupeno'
Notice that a class is created by sending a message to another class telling it to subclass itself with the name and a few other arguments. It’s a message, a method call like any other. Object is a class, classes are objects. The object model of Smalltalk is a beauty but that’s a subject for another post.

Now that we have a class, let’s create a method called greet: in that class.

    greet: name
      "Greets the user named name"
     
      | message |
     
      message := 'Hello ', name.
      Transcript show: message.
In that method definition first we have a comment for the method, then the list of local variables within pipes (“|”), and then the implementation, which sets the variable message to contain “Hello ” and the comma concatenates name to it. Then we just send it to the transcript.

It looks like this:

    MyClass greet method
Ok, let’s use it:

    m := MyClass new.
    m greet: 'Pupeno'
To create an object of class MyClass, we send the new message to that class. There’s no new keyword like in Java. new is just a method. You can read it’s code, override it, etc. Don’t mess with it unless you really know what you are doing.

Actually, if you think about it, we haven’t seen a single keyword. Look all the code we wrote without having to memorize any keywords! What’s even more important is that by now you essentially now Smalltalk. That’s all there is, but like LEGO bricks, this simple and small building blocks allow you to build whatever you want.

Yes, that’s it, that’s all there is to it. We already saw that Smalltalk doesn’t need loops, it has integers and that class implements the timesRepeat: message which allows you to do something N times. There are many other looping methods here and there.

What about the if keyword you ask? Surely Smalltalk has an if? Well, no, it doesn’t. What you can recognize as an if is actually implemented in Smalltalk using the same mechanism of classes and message passing you saw already. Just for fun let’s re-implement it.

We starte by creating the class PBoolean and then two classes inheriting from it, PTrue and PFalse.

    Object subclass: #PBoolean
           instanceVariableNames: ''
           classVariableNames: ''
           poolDictionaries: ''
           category: 'Pupeno'
 
    PBoolean subclass: #PTrue
           instanceVariableNames: ''
           classVariableNames: ''
           poolDictionaries: ''
           category: 'Pupeno'
 
    PBoolean subclass: #PTrue
           instanceVariableNames: ''
           classVariableNames: ''
           poolDictionaries: ''
           category: 'Pupeno'
For the class we created before, MyClass, we define a equals: method that will return either true or false, or rather, PTrue or PFalse.

    equals: other
      ^ PTrue new
The little hat, ^, means return. For now, just a hardcoded true. Now we can do this in the workspace:

    m1 := MyClass new.
    m2 := MyClass new.
    m1 equals: m2
and get true, that is PTrue, as a result. We are getting close but no if yet. How should if look like? It’ll look like something like this:

    m1 := MyClass new.
    m2 := MyClass new.
    (m1 equals: m2) ifTrue: [
      Transcript show: 'They are equal'; cr
    ] else: [
      Transcript show: 'They are false'; cr
    ]
and you can start to imagine how to implement it. In PTrue we add the method:

    ifTrue: do else: notdo
      ^ do value
That method basically takes two parameters, evaluates the first one and ignores the second one. For PFalse we create the oposite:

    ifTrue: notdo else: do
      ^ do value
and that’s it. A working if! If you ask me, I think this is truly amazing. And if you check Squeak itself, you’ll find the if is actually implemented this way:

    True's ifTrue:ifFalse:
If your programming language allows you to create something as basic as the if conditional, then it allows you to create anything you want.


Forth can be used to create 'if' statements from scratch, too. Many Forth implementations start up by loading a set of definitions for things like comments, conditionals and flow control.

  : IF    COMPILE 0BRANCH HERE 0 ,                 ; IMMEDIATE
  : ELSE  COMPILE BRANCH HERE SWAP 0 , HERE SWAP ! ; IMMEDIATE
  : THEN  HERE SWAP !                              ; IMMEDIATE


Appreciated :)

I'm unable to access the site at all right now. HN traffic might have brought it down.


Indeed it has! My server is smoking :P


Yeah, you guys killed my server... it's appreciated anyway.


  equals: other
  ^ PTrue new
The implementation of ifTrue isn't done. The branching was just deferred to the equals function, which has to somehow choose between PTrue and PFalse.

Will it take advantage of the ifTrue function you're building? Will it resort to some nasty arithmetic? Will it call some baked-in feature of smalltalk? Without an answer here, the "implementation" of if is just an illusion.


I don't think that's fair. Being able to implement if doesn't mean that the entire remainder of the system is built without any conditional instructions anywhere in its implementation. (The point, after all, isn't some sort of mystical purity; it's that if you can implement if then you can probably also implement whatever other control structures you happen to fancy. Which you can.)


To implement a usable if, you need to tie it to some form of logical branching. Haskell can tie it to pattern matches. C compiles them to conditional jumps in asm. The asm jump instructions are implemented in hardware. The meat of the if definition lies in how you tie it to whatever form of conditional behavior your language has. This is missing from the article.

Smalltalk (presumably) has a form of logical branching in it somewhere. To finish the if, just pick one.

For a satisfying conclusion, you want to use one that doesn't already look like an if statement.


> Smalltalk (presumably) has a form of logical branching in it somewhere. To finish the if, just pick one.

It uses dynamic binding.

In Java it would look like something this (but it's less useful without proper closures):

  abstract class Bool {
    void ifTrue(Runnable f, Runnable g); 
  }

  class True extends Bool {
    void ifTrue(Function f, Function g) {
      f.run();
    }
  }

  class False extends Bool {
    void ifTrue(Function f, Function g) {
      g.run();
    }
  }
Then, given a variable

  Bool b;
the if-then-else becomes

  b.ifTrue(new Runnable() {
      public void run() {
         System.out.println("It's true!");
      }
    }
  , new Runnable() {
      public void run() {
         System.out.println("Not true!");
      }
    }
  );
It's a bit more verbose, though...


s/Function/Runnable/g


The problem is that reading this seems very bait-and-switch-y. Basically, author is doing:

(define (my-if cond t f) (if cond (t) (f)))

(my-if #t (lambda () ....) (lambda () ....))

The trickyness about if lies in the order of argument evaluation. If you ignore that, then it is relatively easy to implement if in any reasonable language. Even C (I had to leave out the function pointer types, as I haven't worked with c in a few years, and forget the syntax):

void my_if(bool cond, t, f) { if(cond) { t(); } else { f(); } }

void if_true() { ... } void if_false() {

}

my_if(true, if_true, if_false);

Point being, I think redxaxder may have been coming from a scheme background, realizing that if needs to be a builtin. It would indeed be fancy to be able to implement an if that doesn't rely upon other logical branching, though!


How is it "bait-and-switch-y"? Where's the switch? That is actually how if works in Smalltalk. The true block is evaluated when ifTrue:ifFalse: is sent to True and the else block is evaluated when ifTrue:ifFalse: is sent to False. It's just normal message dispatch.


Actually, that really is how Smalltalk does if.


In the blog post I include a screenshot of it... but my server is dying at the moment.


In a class like MyClass you would probably use the same ifTrue to compare the different members of the class. Eventually, the comparison boils down to numbers. To answer your question, we may need to look at the implementation of = for Integers or something like that.

If you go deep enough, the comparison and/or branching is built in in the microchip and you use that.


But equals: can use conditionals to decide that as well. The system shown there is just as complete as one with a syntactic if-statement. At some point your branching logic must terminate either way.


It seems my server is back alive, so if you check the article now, you can see the screenshot from Squeak and it's implemented like that.


I like Smalltalk, but like Forth and Lisp it's just not practical in most of my computing tasks, career or hobby-wise. It's a shame that Smalltalk didn't evolve enough to challenge Java in the '90s. But my buds that knew it well and also had some C chops are in demand as Objective C coders.


The word you were looking for was "popular", not "practical". Our industry is largely internally rife with fads, marketing, and anecdotal discussion, rather than objective analysis and scientific measurement.

And then, this statement is just blaming the tools rather than the developers. "C chops" means less than "has good logical thinking skills and exposure to many programming approaches/patterns".


I disagree. Every time I tried to put together an app with any of Common Lisp, Scheme or Smalltalk I found myself fighting idiosyncrasies unrelated to my app. Obviously it can be done, but the advantage of using those languages start to diminish. It's not about popularity, it's about practicality. I found making Java web applications unpractical in a similar way although I haven't tried enough.


This is an example of an anecdotal piece of evidence, and without a close examination of the "idiosyncrasies" and your definition of "practical", it is difficult to have a discussion. I recognize, though, that people have different opinions on what language is most familiar/useful to them, and at the end of the day, people get work done and make money, whatever they use.


Yes, it's only anecdotal, but it's not that someone didn't allow me to use the language because it's name didn't start with "J" and ended with "ava". I tried and failed. Other people had similar experience. There are some things to point out that many people mention as problematic, like Squeak's image.


Since you are hung up on "objective analysis," what objective evidence do you have that this claim is true?

In any case, here is an objective problem: Common Lisp does not include a standard for regular expressions.

That isn't anecdotal and it isn't misplaced blame and it isn't an unnamed idiosyncrasy. It's a failure within the CL standard to include one of the most powerful tools around for text processing--a tool that pretty much every dynamic language includes.

I've tried using lisp before for text processing and found it brutal--practically impossible--compared to other dynamic languages. That isn't due to popularity, it's because python and perl and awk have built-in facilities for manipulating the hell out of text that sit right on the surface, were easy to find, and work well. Despite having lots of functions for chars and strings, common lisp never felt anywhere as easy for those tasks.

If I'm wrong, then I will look forward to being educated, but I honestly believe that practical limits have everything to do with why people don't turn to CL for scripting.

Thoughts?


I am no Lisp expert by a longshot, but when I want regular expressions, I use this library: http://www.cliki.net/CL-PPCRE (available for easy install via quicklisp) Since I like to pick on Perl... This library's logic is, as it happens, allegedly faster than Perl's regular expressions. That probably is not difficult to do, since Perl is interpreted, whereas most CL implementations are a both interpreted and compiled: you get the extra advantage of the expression being preprocessed.

CL has a nice variety of libraries that extend the language. It is certainly debatable what constitutes good "core language" features vs what should be in good "language extensions" or libraries, but I should make one comment on what I have seen of Lisp libraries: they have high quality, and wherever there is lack, it is documented. There is even a humility about it, a distinct lack of attempt at showmanship or marketing, as if the community cares more about really good theory than the next buck. I suspect this faithfulness to the reality of the various algorithms is part of what turns away people who are on the hunt for shinies.

As a counter example, Perl is well-known for having a huge base of libraries via CPAN, but the signal to noise ratio is very low. There are few libraries that have any quality and a general lack of consistency and interoperability between them with the notable exception of packages like Moose.

From what I understand in reading about this lack of built-in regexp support, it has something to do with regexes being a theoretically weak approach towards parsing. I have some ideas on what that means, but I have no strong participation in such discussions, so I am cautious about proceeding into that territory.

It is possible what allowed me to get to the point where I was confident I could solve any problem I wanted in Lisp (web, database, random scripting, 3D game application, etc) was the fact that I spent a lot of time playing with Lisp and shifting my mental model several times. And I still feel like an egg compared to what some of the folks are doing on some of the Lisp forums. It seems like the people who are breaking new ground in programming theory (and not just rehashing the same old concepts in different syntax approaches) are in the lisp communities, although I should give a good nod to Haskell's continuation of the ML line.

Anyway, I am a little hung up on this definition of "practical". Maybe I am lucky and happen to look in the right places? I did start off on the wrong foot and dig deeply into ASDF stuff a while back when it was messier, but that kind of thing is outdated now with the introduction of the QuickLisp library.

One last note: when you said "scripting", did you really mean "programming"? There is a connotation that scripts are more for one-offs, for hacking stuff together, and Lisp is more oriented towards serious, large applications, for managing huge and complex problems.


I'm sorry, but this doesn't address what I said.

I know the lisp community stands behind Edi Weitz and I respect him, but compared to Python's "Batteries Included" or Perl's standard regular expression library, your solution is problematic. Consider: Weitz's library is just one of five possible regular expression libraries listed on Cliki! Why did you choose his? Weitz's library isn't even the top choice! Are the others broken? Unreliable? Do I have to try them all?

Now, I realize such simple questions as these may not be "breaking new ground in programming theory," but a lot us just want a turn-key solution that works everywhere; the sort that sysadmins use everyday to keep companies humming and the internet buzzing.

That's the definition of "practical" that I'm hung up on.

If I want to do command line text processing with pipes--and many do--how does lisp help me more than Awk? Awk is brilliant in its problem space; it's fairly standardized; it's guaranteed to be everywhere. Choosing Awk or Perl or Python is practical--not being a slave to fashion (trapped in the popularity contest you allude to)

I think you know this, and I think you know just how practical Python/Perl/Awk/Ruby are, which is why you subtly changed your argument from "practical" without qualification to "managing huge and complex problems" by the end of your response, even though the parent specifically includes hobby programming in his classification of "practical" problems.

I think it's cool that you've done "web, database, scripting, 3D game programming, etc" in lisp and can even think in lisp. And I admire your willingness to battle past CL's 1000+ page spec and then the sea of competing libraries to find the one you like and are willing to debug yourself if something is broken.

But that doesn't invalidate all the other tools--it just means there can be more than one way to do it.


The original post mentioned C and career use as well. I have no problem with using awk and so forth for quick one-offs, but that was not the issue. I suppose the real question is "practical for what?". You exaggerate the complexity. ;)


As a counter example, Perl is well-known for having a huge base of libraries via CPAN, but the signal to noise ratio is very low.

Sturgeon's Law applies so its no good picking on just Perl libraries here!

There are few libraries that have any quality and a general lack of consistency and interoperability between them with the notable exception of packages like Moose.

Please see Task::Kensho which is a list of recommended CPAN modules: http://search.cpan.org/dist/Task-Kensho/lib/Task/Kensho.pm


I agree with you about Smalltalk and most Lisps not being practical. I think Clojure has the power to change that though.




Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: