Slide 1

Slide 1 text

I have a confession to make: I love Ruby. Something just clicks, it feels expressive, powerful, flexible and I suspect many of you feel the same way Ruby: A Family History Geoffrey Litt @geoffreylitt Panorama Education

Slide 2

Slide 2 text

* But recently I've been thinking about: why? This isn't just a matter of intellectual curiosity. * When we see successful systems like Ruby/Rails, and we want to make successful systems ourselves, it can be useful to deeply understand WHY other systems are successful. To some extent, this is a personal question, but there are still ways to systematically analyze this question. One technique is to compare Ruby with other programming languages. Why do I Ruby?

Slide 3

Slide 3 text

One approach is to compare ruby with modern programming languages: Python, or Rust, or Java. To me, this is like comparing a person to their friends and peers, which is OK... But if you really want to understand someone... By learning other languages, we can broaden our horizons and also more deeply understand Ruby. — matz

Slide 4

Slide 4 text

You gotta meet their parents. Today we're going to meet Ruby's parents! !

Slide 5

Slide 5 text

Today we're going to explore 3 prominent languages that contributed heavily to Ruby: Lisp, Smalltalk, Perl. We'll approach these languages through 3 lenses: 1) Understand the core ideas of these languages, to deepen our understanding of key principles in Ruby. 2) Rediscover lost and forgotten ideas from these languages 3) Most importantly: Not just explore these languages in isolation, but see how Matz combined them into a single coherent language that we love. We'll see a lot of subtle balance in his design decisions, that not only can help us answer "why do we love Ruby?", but can also teach us valuable lessons designing any system, whether a language, framework, or product.

Slide 6

Slide 6 text

* Let's start with LISP. LISP

Slide 7

Slide 7 text

According to Matz, Ruby actually originated as a Lisp! Ruby was a Lisp originally, in theory. Let's call it MatzLisp from now on. ;-) — matz

Slide 8

Slide 8 text

* Created in 1958-1960 by computer scientist John McCarthy. * His goal was NOT to create a programming language. He wanted to create a set of fundamental axioms for computing, a model of describing algorithms * Out of that work came Lisp: List Processing language. * Had a data structure of lists, and a small number of simple operators on lists: chopping them up, joining back together. Plus a keyword lambda for defining functions.

Slide 9

Slide 9 text

* Out of that small set of axioms, we can define arbitrary computation!!! * One example: given those primitives, we can define eval, a Lisp interpreter. Only about 30 lines! * to really appreciate this, let's place it in context An interpreter (defun eval (e a) (cond ((atom e) (assoc. e a)) ((atom (car e)) (cond ((eq (car e) 'quote) (cadr e)) ((eq (car e) 'atom) (atom (eval (cadr e) a))) ((eq (car e) 'eq) (eq (eval (cadr e) a) (eval (caddr e) a))) ((eq (car e) 'car) (car (eval (cadr e) a))) ((eq (car e) 'cdr) (cdr (eval (cadr e) a))) ((eq (car e) 'cons) (cons (eval (cadr e) a) (eval (caddr e) a))) ((eq (car e) 'cond) (evcon. (cdr e) a)) ;...

Slide 10

Slide 10 text

We can think of programming languages on a spectrum from low-level to high-level.

Slide 11

Slide 11 text

* Previously we were adding layers of abstraction on top of machine code.

Slide 12

Slide 12 text

* Lisp wasn't just another layer. It came from a totally different direction! * McCarthy was creating abstract description of computation, and how computers work is decoupled from that project. * When people say "Lisp was discovered, not invented", this is what they mean. * We still see this lineage in Ruby and Rails: human-centered tools that prioritize the way we think. * Optimizing for programmer productivity, not machine productivity. * To put in terms of David's keynote yesterday: these tools do conceptual compression Because Lisp was the first high-level language, Ruby got many many things from it: conditionals, dynamic typing, garbage collection, symbol types But one area that's particularly instructive: how Ruby incorporated Lisp's handling of functions

Slide 13

Slide 13 text

Lisp introduced idea of functions as values, created and manipulated at runtime. ^ Here's how you define an anonymous function in Lisp, using the lambda keyword. Functions (lambda (x) (* x 2))

Slide 14

Slide 14 text

* Lisp also introduced higher-order functions like map that take a function as an argument. * As Ruby developers, you probably understand how powerful things like map are. * In Ruby, these ideas of function values and higher-order functions are directly inherited from Lisp. Functions (map (lambda (x) (* x 2)) (1 2 3))

Slide 15

Slide 15 text

* We can translate this Lisp code directly into Ruby using Ruby's lambda keyword * But that's not how we normally do things in ruby, right? We have blocks. * We use blocks every day, but have you ever asked, why do they exist? And what's the point? Functions (map (lambda (x) (* x 2)) (1 2 3)) [1, 2, 3].map( &lambda { |x| x * 2 } )

Slide 16

Slide 16 text

We say "everything is an object" in ruby, but a block isn't a valid object Functions { |x| x * 2 } # => SyntaxError

Slide 17

Slide 17 text

You can only pass a block as an implicit argument. ^ This adds quite a lot of complexity. In Lisp, only need to know about lambdas. In Ruby, procs, lambdas, blocks... ^ What do we gain from this added complexity? What this gives us: more concise syntax for special cases where function takes just one anonymous function as an argument. Turns out this is useful in many different situations! Functions { |x| x * 2 } # => SyntaxError def takes_a_block yield 2 end takes_a_block { |x| x * 2 } # => 4

Slide 18

Slide 18 text

Lisp had the idea of pipelines of functional transformations... ^ but Ruby arguably offers a cleaner way to write them than Lisp itself, with blocks. Functions (remove-if (lambda (n) (< n 4)) (map (lambda (x) (* x 2)) (1 2 3))) [1, 2, 3]. map { |x| x * 2 }. reject{ |n| n < 4 }

Slide 19

Slide 19 text

Here I have some fake Rspec syntax in lisp. Internal DSLs are more reasonable to write in Ruby. We see this in Rails migrations, rake tasks, and more Now what if you want to pass in two anonymous functions? Can't do it with blocks, you have to resort to a totally different system. Blocks arguably add inconsistency and complexity to the language. Functions (describe "my machine" (lambda () ( (it "produces widgets" (lambda () ( ;... )))))) describe "my machine" do it "produces widgets" do #... end end

Slide 20

Slide 20 text

* Often people say "consistency is really important in design" * But, in this case: inconsistency has a payoff: helps make our code easier to read/write in a very common "special case" * You see this in Rails too! Idea of convention over configuration: introduce implementation complexity to make the common case easy * Different users will want different tradeoffs here, but the point is that consistency can be sacrificed in service of other goals. By the way, fun to see concise lambdas catching on... In last 10 years: Stabby lambda syntax in Ruby, fat arrow syntax in ES6. Language designers realizing this concise lambda definition is a valuable feature. Thoughtful inconsistency

Slide 21

Slide 21 text

* LISP's syntax is called "s-expressions" Fun tidbit of computing history: Originally an accident, s-expressions were intended as the internal representation. McCarthy had a plan for a whole other syntax called m-expressions, with fewer parens. But a researcher in his lab wrote an interpreter for s- expressions, that got popular, and the rest was history. McCarthy has a great quote reflecting on the m- expressions project: Syntax ( (let a 1) (let b 2) (+ a b))

Slide 22

Slide 22 text

The project...was neither finalized nor explicitly abandoned. It just receded into the indefinite future... — John McCarthy

Slide 23

Slide 23 text

* Lisp syntax mirrors the abstract syntax tree and has tons of beautiful properties: * very little parsing * macros for manipulating code as data * and Lisp hackers will say you can totally get used to the parentheses LISP Syntax ( (let a 1) (let b 2) (+ a b))

Slide 24

Slide 24 text

* This is syntax of a different language, FORTRAN * Matz moved from s-expression syntax to Fortran style. Kinda looks like Ruby. * Requires much more complex parsing, infix operators, etc...so if Ruby is a Lisp, why use Fortran syntax? * I can't read Matz's mind. Maybe he preferred this. But either way, it was the right thing to do. * This syntax had already won and was familiar to everybody. It was a necessary choice to drive adoption. FORTRAN Syntax a = 1 b = 2 result = a + b

Slide 25

Slide 25 text

* If you want widespread usage, sometimes you have to go along with the popular trend to remain relevant. * Ruby went with the prevailing trend in syntax. * Rails has a similar approach to the modern Javascript ecosystem with webpacker, going along with trends to remain relevant * Fun question: Lisp lets you manipulate your code as data. What if we want to do that in ruby? Pick your battles.

Slide 26

Slide 26 text

Using the parser gem, we can take a string of code and parse it... Ruby parsing/unparsing code = "2 + 3 * 4" ast = Parser::CurrentRuby.parse(code)

Slide 27

Slide 27 text

and we get access to that code as a tree of data! Ruby parsing/unparsing code = "2 + 3 * 4" ast = Parser::CurrentRuby.parse(code) # => [s(:send, # s(:int, 2), :+, # s(:send, # s(:int, 3), :*, # s(:int, 4))), []]

Slide 28

Slide 28 text

and we can then unparse it back into text. ^ You might be thinking, this is a terrible idea, and you're mostly right :) ^ But some interesting real world use cases of this. Gem called "mutant" that does mutation testing, where it randomly changes your code and makes sure your tests fail when your code changes. Uses this gem under the hood. But of course we don't need to resort to this often. ^ Reason: most of the time in Lisp, code manipulation is used for metaprogramming: code that creates code. In Ruby we don't need to use this approach because we have a variety of powerful tools, which come from another language: Ruby parsing/unparsing code = "2 + 3 * 4" ast = Parser::CurrentRuby.parse(code) # => [s(:send, # s(:int, 2), :+, # s(:send, # s(:int, 3), :*, # s(:int, 4))), []] 2.1.5 :005 > Unparser.unparse(ast) # => "2 + (3 * 4)"

Slide 29

Slide 29 text

* Smalltalk! * The first OO language * Huge influence on Ruby Smalltalk

Slide 30

Slide 30 text

* Early 70s, led by Alan Kay, lots of work from Adele Goldberg and Dan Ingalls at legendary Xerox PARC lab

Slide 31

Slide 31 text

Fundamental goal: expand computing to everyone, let anyone make their own programs. Architects making architecture software, artists making drawing software... Smalltalk also had a focus on kids This resonates with DHH's keynote from yesterday. Widening the group that has access. This is part of why it's named Smalltalk: focus on kids Also a joke: Kay was tired of systems with big names like Zeus/Thor doing nothing, so he made a system with a small, cute name but lots of power

Slide 32

Slide 32 text

Not just the first OO language, also an editing environment for that language. They invented the modern GUI and overlapping windows Model-View-Controller was also invented there for Smalltalk Basically everything we do today invented at this one lab! By the way, other labs down the hall were inventing things like Ethernet... Great book called Dealers of Lightning that recounts the history

Slide 33

Slide 33 text

We talk a lot about OO, and sometimes this question comes up, "what is OO?" What is "object-oriented programming"?

Slide 34

Slide 34 text

Maybe there's not a single answer to that question, but we CAN ask, what was the intention of the creator of OO when it was invented? This is a question that can yield some insight. To answer it, we look to Smalltalk, the first OO language Similarly to McCarthy, Alan Kay was looking for a consistent model of computation. What is "object-oriented programming"? What was the original intention of object- oriented programming?

Slide 35

Slide 35 text

* He was a fan of Lisp... but had problems with it * Amazingly, thought lisp wasn't consistent enough! * example: "lambda" isn't a function. Too much of lisp isn't implemented with functions. * He was looking for a more purely recursive approach to designing a model of computation. I could hardly believe how beautiful and wonderful the idea of LISP was...but there were deep flaws in its logical foundations. — Alan Kay

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

* People had been splitting up a computer into functions and data separately.

Slide 38

Slide 38 text

* What if we split into little computers instead?

Slide 39

Slide 39 text

* Each has state and process encapsulated. Like millions of little computers In computer terms, Smalltalk is a recursion on the notion of computer itself. — Alan Kay

Slide 40

Slide 40 text

Let's see what that looks like in practice. Message passing 3 + 4 "=> 7

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

* almost like making a remote API call, a post request to 3.com * Revolutionary idea: give RECEIVER so much control, extremely DYNAMIC behavior * Very very different from how functions work in C Much of Ruby's OO side comes very directly from Smalltalk:

Slide 44

Slide 44 text

Message passing similarities Smalltalk: 3 perform: '+' asSymbol with: 4 Ruby: 3.send(:+, 4)

Slide 45

Slide 45 text

Message passing similarities Smalltalk: 3 isKindOf: Integer Ruby: 3.is_a? Integer

Slide 46

Slide 46 text

classes are just objects Message passing similarities Smalltalk: Integer isKindOf: Class Ruby: Integer.is_a? Class

Slide 47

Slide 47 text

* have you ever thought about WHY ruby pushes us to do iteration over arrays this way? * having more things be message-based makes the language more CONSISTENT Control flow with message sending Smalltalk: array do: [ :element | Transcript show: element ] Ruby: array.each { |element| puts element }

Slide 48

Slide 48 text

In this example: changing what happens when an integer receives a method it doesn't understand. The Smalltalk and Ruby look nearly identical. Smalltalk has precedents for 2 key ideas in Ruby: * Opening classes, including built-ins * Handling missing methods * This all flows from the core mental model: extreme encapsulation. Rails uses these metaprogramming tools extensively ^ Opening up classes, dynamically defining/undefining methods, handling missing methods ^ Fair to debate how to use these powerful tools wisely, but at least we see their origins Metaprogramming Integer extend [ doesNotUnderstand: msg [ 'method not defined' printNl ] ] class Integer def method_missing(msg) puts 'method not defined' end end

Slide 49

Slide 49 text

* Obviously we got a lot * But how did Ruby DIVERGE from Smalltalk? There are no conditionals in Smalltalk. You saw Kay was committed to the purity of the recursion. Conditional logic in Smalltalk: Just a message send Message passing purity (2 + 2 == 5) ifTrue: [ Transcript show: 'true'. ] ifFalse: [ Transcript show: 'false'. ].

Slide 50

Slide 50 text

You can implement similar things in Ruby, but instead we got conditionals as a language feature. not sure why Matz chose this, but one theory: if statements just seem more straightforward to use. And he was willing to add complexity/ inconsistency to the language to accommodate that Thoughtful inconsistency

Slide 51

Slide 51 text

* Smalltalk had a different approach to code organization * This is a forgotten idea: a super highly integrated IDE * Smalltalk: no text files, always operating on a live image

Slide 52

Slide 52 text

* In this example, you can provide example input output and it'll find the corresponding method for you.

Slide 53

Slide 53 text

* Ruby didn't do image-based editing, too radical. Almost certainly a good idea for adoption. * Ruby/Rails is a text editor + terminal community, but we have rich editor heritage. Bring it back? Pick your battles.

Slide 54

Slide 54 text

* Perl: the "Swiss army chainsaw" of programming languages Perl

Slide 55

Slide 55 text

* You can tell this one is going to be a little different * Developed in late 80s by Larry Wall * linguist working as sysadmin for NASA * Larry Wall wasn't solving a deep theoretical problem. * He was trying to do sysadmin stuff. * Wanted to combine text processing (sed, awk) w/ Real Programming (C)

Slide 56

Slide 56 text

Perl provided many mantras that Ruby adopted A focus on pragmatism, productivity, just getting the job done somehow, getting out of your way. "A program is correct if it gets the job done before you get fired."

Slide 57

Slide 57 text

* One of Ruby's core principles! * in contrast w/ Python, in contrast with more academic communities. * Larry Wall thinks of programming as human language more than a mathematical system. "There's more than one way to do it"

Slide 58

Slide 58 text

* This brings us back to introducing thoughtful inconsistency, making special cases when it helps. "Easy things should be easy and hard things should be possible"

Slide 59

Slide 59 text

A good car has a solid chassis + transmission + airbags... but it also has to have a nice interior. The seats and interior details matter. Academic languages often focus on getting the chassis + transmission right, but often don't pay as much attention to this stuff Ruby is a polished language, feels nice to use. Like a car with a nice interior. And we got a lot of those things from Perl.

Slide 60

Slide 60 text

String interpolation is super super easy in Perl. ^ Ruby we don't have it quite this easy, but still have a good syntax for it ^ Javascript had nothing good until very recently, many languages have nothing ^ Wouldn't be surprised if we have Perl to thank Effortless string interpolation $x = 5; $msg = "The value is $x now.";

Slide 61

Slide 61 text

A lot of what makes Ruby feel delightful is this detailed polish work. ^ Rails has this too: 2.days.ago is the same type of thing More little details —native regex syntax —%w array syntax —heredocs —1_000_000 number syntax

Slide 62

Slide 62 text

Also got some things that might be more controversial. global variables: not common in many langs now, but came from Perl. Bad idea most of the time, but sometimes pragmatic and useful for hacking stuff together Globals $global_variable = 1

Slide 63

Slide 63 text

not only that, we got weird built-in global variables Globals $$

Slide 64

Slide 64 text

Cryptic and probably bad for production code... ^ but maybe useful if you're just hacking together a script ^ Very pragmatic! Globals $$ # => 19936 Process ID!

Slide 65

Slide 65 text

Some parts of Perl are too far in this direction so Ruby doesn't incorporate ^ In Perl, guess what this produces? Weak typing print "8" + "1";

Slide 66

Slide 66 text

Yuck. Contrast with Ruby, which has a more strongly typed core. Weak typing print "8" + "1"; # => 9

Slide 67

Slide 67 text

Weak typing @cities = qw( Berlin Tokyo London Boston ); # Assign the array to a scalar $cities_count = @cities;

Slide 68

Slide 68 text

* Perl tilts heavily towards implicit type coercion * Reminiscent of some of most frustrating parts of Javascript * Ruby has a more consistent core. * We got the elegance and ergonomics of Perl, without the core Weak typing @cities = qw( Berlin Tokyo London Boston ); # Assign the array to a scalar $cities_count = @cities; # => 4

Slide 69

Slide 69 text

Alright, brings us back to this question: why do we love Ruby? Why do I Ruby?

Slide 70

Slide 70 text

Ruby Achieves a delicate balance on many dimensions, by picking best parts of different solutions Consistency of Smalltalk/Lisp computation models. But willing to add exceptions to the rule for usability. ^ Academic flavor of Smalltalk/Lisp, layers on nice leather detailing from Perl ^ Takes some stances, like being very object-oriented. But, shies away from radical departures like Lisp's syntax or Smalltalk's editing paradigm. This is hard to pin down because Ruby isn't the most anything. Not most OO or most functional. Not the fastest, or the highest level. When we're designing systems, there's often pressure to stand out on some dimension. You see the cell networks competing for "most coverage" or "fastest network" Even Apple, a company that understands design, plays the game of making the "thinnest laptop ever" But sometimes, a beautiful system doesn't need to be the most anything. It can just be a perfect, well-balanced combination of many different ideas. Maybe next time you're designing a system, you can look to Ruby for inspiration for designing a well-balanced system.

Slide 71

Slide 71 text

Thanks! Twitter: @geoffreylitt