Ruby from the ground up

Values, and the names you give them

Ruby has no primitives: everything is an object you can ask questions of. Numbers, text, symbols, nil, and the conversions between them.

Part 2 of 13

Beginner15 min readBack to Courses

Everything is an object

In Ruby, 42 is not a special piece of hardware — it is an Integer object with methods. 42.even? asks it a question, 42.class says what it is, and 42.to_s turns it into text. The same is true of every other value, which is why the language feels so even: there is one set of rules, and everything follows them.

That makes small programs pleasant and large ones predictable in a way that mixed languages are not. A number knows arithmetic, text knows about letters, and nil — Ruby's name for the absence of a value — is an object too, with methods of its own.

Numbers and names

Integers are exact and unbounded, so 2 ** 100 is a perfectly ordinary thing to ask for. Floats are the fastest approximate type and behave the way floats do everywhere. There is no separate character type — a one-letter string is a string, which removes a whole category of confusion.

A variable is a name bound to a value: score = 10, then score = score + 5. There is no declaration and no type attached to the name — the value carries its own type, and the name can point at anything. A name that begins with an uppercase letter is a constant, and Ruby warns if you reassign one; that warning is the entire enforcement, so constants are a promise rather than a lock.

Symbols, and asking what a value is

A symbol looks like :name and is a small immutable label. Where other languages reach for an enum or a string key, Ruby uses a symbol: it compares by identity, it reads like a name, and it is the natural key of a hash — chapter seven is where that starts to matter. In the output, a symbol prints with its colon, and a string prints with quotes if you ask for its raw form with p.

Conversion is explicit: to_i turns text into an integer, to_f into a float, to_s into a string. Ruby almost never converts behind your back, which is why '40'.to_i + 2 needs the to_i and why this is a feature — you can see every step where a value changed shape.

Names point at objects

Assignment does not copy a value into a box; it points a name at an object. score = 10 makes score refer to the Integer 10, and score = score + 5 builds 15 and points the name at that instead. The old object is unchanged, which matters once two names refer to the same mutable object — the arrays in a later chapter will make that concrete.

Because the name carries no type, the useful questions are asked of the value: score.class, score.is_a?(Numeric), score.respond_to?(:even?). The last one is duck typing in a sentence — if the object answers the method, the code can call it. Ruby checks at the call rather than at the declaration, so a wrong value fails where it is used instead of where it was named.

Conversion is forgiving in a way that can hide a problem: '12abc'.to_i is 12, and 'abc'.to_i is 0, because to_i reads the digits it finds and stops. Integer('12abc') raises instead, which is the honest choice for input you do not trust. And nil.to_s is the empty string, so a missing value can look like blank text rather than a failure.

Asking values what they are

puts 42.class
puts 3.14.class
puts 'text'.class
puts :label.class
puts nil.class

puts 2 ** 100
puts (1 + 2) * 3

puts 'ruby'.upcase
puts 'ruby'.length
puts 42.even?

puts '40'.to_i + 2
puts 42.to_s + '!'
puts '3.5'.to_f * 2

score = 10
score = score + 5
puts "score: #{score}"

FULL_MARKS = 100
puts "out of #{FULL_MARKS}"

missing = nil
p missing
puts missing.to_s

A small profile

Run it and read the class names. Then add a constant for the year you were born, compute an age from it with plain arithmetic, and print a sentence with that number interpolated.

Output
Click "Run" to execute your code

A name is not a type

Because a name can point at any object, the same name can hold a string on one line and a number on the next. Ruby allows it, and the program only notices when a method is missing: calling upcase on a number raises NoMethodError, which names the class and the method it could not find.

That error is more useful than a type declaration in a small program, because it arrives with the value in hand. Read it as a question: what did this object turn out to be, and which line gave it that value? The answer is usually a conversion that was skipped.

nil is a value, not an error. It answers nil.class with NilClass, prints as an empty string under puts, and prints as nil under p — so when output looks blank where you expected something, p is how you find out whether you got an empty string or nothing at all.

Try these next

  • Ask three values for .class, then ask one of them .respond_to?(:upcase).
  • Convert '3.99' to a float, double it, and print the class of the result.
  • Turn a number into text with to_s, add an exclamation mark, and check the class of the result.

A price tag that adds up

Run it and check each conversion. Then change the quantity, add a discount constant, and print the final price with two decimal places.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does 42.class return?