The Well Grounded Rubyist - Part 7

Scalar objects are atomic objects, like strings, numbers, and symbols. Each scalar object represents one value; scalars don't contain other objects.

Collection objects contain other objects; the major collection classes in Ruby are arrays and hashes. The collection survey will also include ranges, which are hybrid objects that can (but don't always) serve to represent collections of objects.

Built-in Ruby essentials

  • Literal constructors — Ways to create certain objects with syntax, rather than with a call to new
  • Syntactic sugar — Things Ruby lets you do to make your code look nicer
  • “Dangerous” and/or destructive methods — Methods that alter their receivers permanently, and other “danger” considerations
  • The to_* family of conversion methods — Methods that produce a conversion from an object to an object of a different class, and the syntactic features that hook into those methods
  • Boolean states and objects, and nil — A close look at true and false and related concepts in Ruby
  • Object-comparison techniques — Ruby-wide techniques, both default and customisable, for object-to-object comparison
  • Runtime inspection of objects’ capabilities — An important set of techniques for runtime reflection on the capabilities of an object

Ruby's literal constructors

  • you can't create a new instance of the class Integer, but for the most part, you can create new instances of the built-in classes
  • there is no new constructor for the Symbol object, the only way to generate an object is through the literal constructor
  • literal constructors are not the only way to instantiate an object of a given class, but they're very commonly used
Class
Literal constructor
Examples
String quotation marks

  "new string"
  'new string'

Symbol leading colon

  :symbol
  :"symbol with spaces"

Array square brackets

 [1, 2, 3, 4, 5]

Hash curly braces

  { "New York" => "NY", "Oregon" => "OR" }

Range two or three dots

  0..9
  0...10

Regexp forward slashes

  /([a-z]+)/

Proc (lambda) dash, arrow, parentheses, braces

 ->(x, y) {x * y}

Recurrent syntactic sugar

  • Ruby sometimes lets you use sugary notation in place of the usual object.method(args) method calling syntax.
Defining operators by defining methods
  • if you define a + method for your class, then objects of your class can use the sugared syntax for addition
  • also, there is no such thing as defining the meaning of that syntax separately from defining the method. The operator is the method. It just looks nicer as an operator
  • Ruby doesn't know that + means addition. Nothing but good judgement stops you from writing completely nonaddition-like + methods
  • here's a list of automatically sugared methods:
Category
Name
Definition example
Calling example
Sugared notation
arithmetic method/operators + def +(x) obj.+(x) obj + x
- def -(x) obj.+(x) obj + x
* def *(x) obj.*(x) obj * x
/ def /(x) obj./(x) obj / x
% def %(x) obj.%(x) obj % x
** (exponent) def **(x) obj.**(x) obj ** x
get/set/append data [] def [](x) obj.[](x) obj[x]
[]= def []=(x, y) obj.[](x, y) obj[x] = y
<< def <<(x) obj.<<(x) obj << x
comparison methods/operators <=> def <=>(x) obj.<=>(x) obj <=> x
== def ==(x) obj.==(x) obj == x
> def >(x) obj.>(x) obj > x
< def <(x) obj.<(x) obj < x
>= def >=(x) obj.>=(x) obj >= x
<= def <=(x) obj.<=(x) obj <= x
case equality operators === def ===(x) obj.===(x) obj === x
bitwise operators | (OR) def |(x) obj.|(x) obj | x
& (AND) def &(x) obj.&(x) obj & x
^ (XOR) def ^(x) obj.^(x) obj ^ x
Customizing unary operators
  • you can specify the behaviour of the expressions +obj and -obj for your own objects and classes. You do so by defining the methods +@ and -@
  • you can also customise the unary negation (!) method/operator

Bang (!) methods and "danger"

  • the bang has no significance to Ruby, internally
  • the exclamation mark isn't only used for unary negation
  • by convention, the bang labels a method as "dangerous" - specifically, as the dangerous equivalent of a method with the same name but without the bang. So def hello! is the dangerous version of the method def hello
  • dangerous can mean whatever the person writing the method wants it to mean
  • in the case of built-in classes, it usually means "this method, unlike its nonbang equivalent, permanently modifies its receiver". It doesn't always mean this, though. Like in the case of exit!
  • every bang method should occur in a pair with a nonbang equivalent
Destructive (receiver-changing) effects as danger
  • destructive here means that the method changes the object on which it is called
  • example: calling upcase on a string gives you a new string consisting of the original in uppercase. But upcase! turns the original string into its own uppercase equivalent, in place
  • you should always be aware of whether the method you're calling changes its receiver
  • don't assume a direct correlation between bang methods and destructive methods. They often coincide, but they're not the same thing

Best practices for bang-terminated method names

  1. Don't use ! except for m/m! method pairs
  2. the ! notation method should only be used when there's a method of the same name without the !. When they do substantially the same thing, and when the bang version also has side effects that are different from the return value or other behaviour that diverges from the non-bang counterpart
  3. the ! is a warning that there may be more going on than the name suggests

  4. Don't equate the ! notation with destructive behaviour

  5. bang doesn't mean destructive, it means dangerous, possibly unexpected, behaviour

to_* (conversion) methods

Ruby has a number of built-in methods that convert objects to another type of class. A few examples are:
- to_s (to String) - to_sym (to Symbol) - to_a (to Array) - to_i (to Integer) - to_f (to Float)

String converstion: to_s
  • every Ruby object(except BasicObject) responds to to_s
  • puts calls to_s on its arguments
  • so if you redefine a to_s method of your own, puts will reflect the new definition of to_s
inspect
  • irb uses inspect on every value it prints out
  • inspect andto_s` are two different things
display
  • is a specialised output method
  • display takes an argument in the form of a Ruby I/O object
  • by default, it uses STDOUT, the standard output stream
  • like print, it doesn't automatically insert a newline character
Array conversion: to_a and the * operator
  • if defined, the to_a method provides an array-like representation of objects
  • it automatically ties in the * operator. The * operator is pronounced "star", "unarray" or "splat"
  • the * operator does a kind of unwrapping of its operand into its components, those components being the elements of its array representation
  • in general cases (when not in a method parameter list), the splat turns any array (or any object that responds to to_a) into the equivalent of a bare list
  • bare list means several identifiers, or literal objects, separated by commas
  • you can use the splat in front of a method argument to turn it from an array into a list. A use case is where you have objects in an array that you need to send to a method that's expecting a broken up list of arguments
  • if you don't use the unarraying splat, you'll send just one argument - an array - to the method, and the method won't be happy
Numerical conversion: to_i and to_f
  • unlike some languages (such as Perl and JavaScript), Ruby doesn't automatically convert from strings to numbers or numbers to strings
  • in Ruby, this is not possible: 1 + "2" because Ruby doesn't know how to add strings and an integer together
Strict conversions with Integer and Float
  • Ruby provides methods called Integer and `Float. They look like constants, but they're methods with names that coincide to those of the classes to which they convert
  • these methods are similar to to_i and to_f, but are stricter. If you feed them anything that doesn't confirm to the conversion target type, they raise an exception
  • so "123abc".t0_i works, but Integer("123abc") raises an exception
  • Float("3") converts to 3.0, but `Float("-3xyz") raises an exception
  • its the letters that cause problems
Conversion vs. typecasting
  • when you call a method like to_s, the result is a new object. It's not quite the same as typecasting, you're not using the object as a string. You're asking the object to provide a second object that corresponds to its idea of itself in one of those forms
  • the closest Ruby gets to traditional typecasting is the role-playing conversion methods
Role-playing to_* methods

In Ruby, you should not worry too much about what class an object belongs to. All that matters is what the object can do - what methods it can execute

String role-playing, to_str
  • if you want to print an object, you can define a to_s method for it
  • but if you need an object to be a string, you define a to_str method
  • the to_str on an object enters the picture when you call a core method that requires that its argument be a string
  • Example: string addition
  • if an object responds to to_str, its to_str representation is used when the object is used as an argument to String#+ method
Array role-playing, to_ary
  • to_ary provides a way for an object to step into the role of an Array. Just like how to_str lets an object role-play as a String

Boolean states, Boolean objects, and nil

  • Every expression in Ruby evaluates to an object, and every object has a Boolean value of either true or false
  • true and false are also objects
true and false as states
  • Every expression in Ruby is either true or false, in a logical or Boolean sense
  • in many cases where you want to get a truth/falsehood value, such as an if statement or a comparison between two numbers, you can think of truth and falsehood as states rather than objects
true and false as objects
  • Boolean objects true and false are special objects, each being the only instance of a class especially created for it: TrueClass and FalseClass, respectively
  • true and false are keywords, so you can't use them as variable or method names
  • sometimes true and false will be used as method arguments. For example, if you want a class to show all of its instance methods but exclude those defined in ancestral classes, you can provide the argument false to your request, like so: String.instance_methods(false)
  • the problem with Boolean arguments is that its very hard to remember what they do, so its best to avoid them in your own code
true/false: state vs. values
  • the double usage of the true/false terminology is sometimes a source of confusion: when you say something is true, its not always clear whether you mean it has a Boolean truth value or that its the object true
  • remember that every expression has a Boolean value, including the expression true and the expression false

obj.&(x)

The special object nil
  • the special object nil is the only instance of the NilClass
  • nil is an object, but it is also kind of a non-object
  • The Boolean value of nil is false
  • nil and false are the only two objects that have a Boolean value of false
  • nil denotes an absence of anything. You can see this when you inquire into the value of an instance variable you haven't initialised
  • local variables aren't automatically initialized to anything, not even nil
  • nil is also the default value for nonexistent elements of a container or a collection object. For instance, if you create an array with three elements and then try to access the tenth element, you'll find that its nil
  • the to_s conversion of nil is an empty string
  • the to_i conversion of nil is 0
  • the object_id for nil is 8

Equality tests

  • if you define == method on your objects, you will automatically have the != method
The difference between ==, eql? and equal?

Example
obj & x

  • the equal? method is used to check whether two obj5ects are exactly the same object
Comparisons and the Comparable module
  • the most commonly redefined equality-test method is ==
  • it is part of a family of comparison methods that includes ==, !=, >, <, >= and <=
  • not every class of object needs, or should have, all these methods
  • but for classes that need full comparison functionality, you can do the following two things:

    • mix in module called Comparable into your class
    • define a comparison method with the name <=> as an instance method in your class
  • <=> is called the spaceship operator or spaceship method

  • in the spaceship method, you define what you mean by less than, equal to and greater than
  • Example: ^
  • all Ruby numerical classes include Comparable and have a definition for <=>
  • the String class also includes the Comparable module

Inspecting object capabilities

  • inspection and reflection collectively refer to the various ways in which you can get Ruby objects to tell you about themselves during their lifetimes
Listing an object's methods
  • when you want to know what messages an object understands i.e. what methods you can call on it, you can use methods, like so: "hello world".methods
  • then you can sort them to make it easier to visually parse "hello world".methods.sort
  • the methods method works with class and module objects too
  • you can ask for singleton_methods like so: obj.singleton_methods
  • even if you include a module after the instance already exists, the instance will still have the methods in the included module. This is because of the path lookup. The object really doesn't care how or when the module got inserted into the lookup path
Querying class and module objects
  • you can use the method instance_methods to see the instance methods a class is endowed with, like so: String.instance_methods.sort
  • you can use instance_methods on a module too, like so: Enumerable.instance_methods.sort
Filtered and selected method lists
  • You can view a class’s instance methods without those of the class’s ancestors by providing the argument false to the instance_methods method. like so: String.instance_methods(false).sort
  • other method-listing methods include:

    • obj.private_methods
    • obj.public_methods
    • obj.protected_methods
    • obj.singleton_methods
  • classes and modules let you examine instance methods:

    • MyClass.privateinstancemethods
    • MyClass.protectedinstancemethods
    • MyClass.publicinstancemethods
  • public_instance_methods is a synonym for instance_methods