Showing posts with label ruby. Show all posts
Showing posts with label ruby. Show all posts

December 10, 2010

Something nice about every language I've used

Inspired by this post (and more great answers on the reddit comment thread), here's one nice thing about each of the languages I can remember using in any meaningful capacity:

Java: I learned to program in this. It led to the creation of the JVM, and while Java isn't my favorite language, the JVM is a pretty sexy piece of technology that enabled a number of other languages (Scala and Clojure most notable) to flourish.

C: The closest thing imperative programming has to "sparse beauty," a la Scheme. Shows you really don't need many bells and whistles to do a job, and do it well.

C++: Back when everyone was using C, it's kind of a technical miracle that Bjarne could create a proper superset on top of C with the features it has. Further, it's still blazing fast; without it we wouldn't have all the video games we love today ^_^.

PHP: Incredibly easy to learn, and no-hassle to set up on a server. One of the matches that lit the web on fire.

Racket (and Scheme applies here too): The language that never lets up. The most delicious learning curve I've ever tasted. Like Wagner's music is jokingly said to be "better than it sounds," Racket is more fun and fulfilling than it deserves to be.

Erlang: Industry-proven functional programming with more concurrency love than 1000 suns. Also the top language for gaining hipster-programmer cred. When you drop this name, suddenly everyone looks at you like "that guy" (you can decide if this is what you want or not).

Haskell: A wolf in sheeps's clothing, the most modern, practical, and supported language with features I think we'll see as necessary in the future. Another candidate for learning curve that keeps on giving.*

SML: A really sick module system for programming in the large. While not my favorite for "programming in the small," an understanding of SML's module system makes you pity that it never really took off.

Ruby: One of the most beautifully designed, fully-realized languages I know. Shows you can make a language that is simple, with practical value for Herp Derp programmers without sacrificing power and flexibility for the craftsmen as well.

Objective-C: The real "C with objects." Message passing with named parameters (and the much more sensible #import rather than #include), this is one time where I'm highly in favor of Mr. Jobs' stubbornness.

Prolog: 10-second youtube video. I mean this in a good way.

Max/MSP: Probably the most fully-realized and pleasing graphical programming environment I know, as well as an example of a damn fine DSL.

Javascript: I don't have to worry about compiler errors! No seriously, prototypical objects for the masses.

Flapjax: Functional reactive programming! An excellent example of functional languages and concepts attacking problems from the language level. Very innovative workaround for the horrors of client-side programming of the time.

Actionscript: Adobe makes it! Like Java, but better (Flash Platform > Swing/most Java GUI's).

*= Small qualification on the learning curve lines: virtually any language takes years of work over dozens of programs to "master." But many times mastering a language means compensating for its weaknesses, not discovering new strengths. That's what Haskell and Racket have given me more than most other languages.

May 9, 2010

PAUL IS BOTHERED

You can talk all you want about 'practicality,' but one way in which I see functional languages will ALWAYS beat imperative is the built-in love of lists. I simply can't express my distaste for always writing:


import java.util.Collection;
import java.util.Iterator;

// crap elided

Iterator<String> iterator = myCollection.iterator();
while (iterator.hasNext()) {
    doSomethingWithString(iterator.next());
}

over, say:


(map do-something-with-string my-collection)

But Paul! says you, Java gives you static typing! The Scheme example above doesn't, so it can get away with that kind of dynamic-language brevity! Well, so does Haskell:


map doSomethingWithString myCollection

and SML:


map(doSomethingWithString)(myCollection)

For fun, lets just see it in a smattering of other languages I've used. Ruby:


my_collection.each { |x| do_something_with_string x }

Erlang:


lists:foreach(fun do_something_with_string/1, MyCollection),

Common Lisp:


(mapcar #'do-something-with-string my-collection)



Naturally, this isn't even the whole picture. The example assumes you're iterating for side effects; if you needed the list with its elements transformed, that would be another 5-7 lines of Java. And the list can only exist because it was constructed to begin with, which if you did it in another class, requires 3-6 more import declarations there.

I know its trendy to hate on Java, and I've mentioned before that PL hating like this is unproductive. Still, we use and work with variable-sized lists all the time, and what functional designers figured out (long, long ago) is that it's quite nice to have them built-in.

Bleh, sorry. Android programming is fun, despite the Java.

I did say "imperative" above, not just Java. Kind of a misnomer, as CL and Ruby, above, provide one-line map and are arguably imperative. In that case, I let my biases shine through and used "imperative" interchangeably with "luddite." But let's never forget our buddy C++:


#include <list>
using namespace std;

list<string>::iterator iter;
for (iter = my_collection->begin(); iter != my_collection->end(); ++iter) {
    do_something_with_string(*iter);
}

PUKE

February 24, 2010

GC, Continuations, Ruby Internals

When I was about 6, I told my mom I wanted to be a garbageman when I grew up. I mostly just liked the truck, and hanging from the side of it while it drove from house to house.

Garbage collection still fascinates me. I found this talk on Ruby's garbage collector of great interest. While we program with abstractions to make application development easier, the sad fact is that it still helps you avoid shooting yourself in the foot to know how your languages features are being implemented. Even if it didn't, wouldn't you be curious?

This is also a great example of a poor (or simply inflexible) design choice having major consequences down the road. In the talk they explain how Ruby's conservative, stop-the-world mark and sweep collector can't really be replaced by other, more efficient collectors due to the representation of objects. Personally, I'm partial to generational stop-and-copy's; but it only showed up in the talk as an impossibility. The best they could do was curb their lame mark-and-sweep (also Python uses reference counting lol).

I dropped a lot of jargon there; for any curious folk, I can explain what they mean in a later post ^_^ I'll spend the remainder of this one talking about another Ruby feature whose underlying implementation destroyed my algorithm.

---

Ruby is one of the few languages to support continuations out-of-the-box (in Ruby 1.8 anyways, in 1.9 you have to 'require continuation'). The presence of continuations are a sign of flair: when a designer has worked to put it in the language (Matz called it the hardest language feature of Ruby to implement) they are pretty much telling you they are committed to writing a flexible, powerful language that lets the programmer do whatever they want. Naturally, when I see a use for them, I use them.

Kent Dybvig shows us that continuations can be implemented to be very efficient. Unfortunately in Ruby, they aren't: like the GC, continuations are implemented in about the most bare-bones way possible. In Ruby, they implement a continuation by copying the entire program stack in its current state and storing it elsewhere. When you call the continuation, they copy back the old stack over the current one.

Here's the problem: I wrote a program to push the current continuation onto a stack before every call to the recursion. The idea was to use the continuations to keep track of backtracking over several parameters, and the stack meant you would only call as many as you needed.

The problem was, every recursive call increased the size of the stack, since Ruby doesn't support tail-call optimization. So at every recursive call, you would copy over the entire stack somewhere in memory, augment it, and recur. DEATH!

Needless to say, I found another solution. But this was another example of how the implementation of a feature can make the feature usable or not. Had I implemented the same algorithm in Dybvig's Chez Scheme, with both tail-calls and efficient continuations, this algo would have sailed.
---
Coming back full circle, there was a time when I considered peeking into the Ruby source and forking it to support Dybvig's stack frame model (gutting the whole language primarily to support... continuations?). Looking at the object representation from the GC talk though, it's probably much harder than I imagined ^_^.