Applying Built-In Modules

When inspecting the standard library code,[36] you can see that lots of the building blocks in there are actually modules. Let’s take a quick look at some of them.

Using Comparable

Suppose you want to be able to order the objects of a type, using operators < and >=, but also ==. Would you define all these comparison operators yourself? Of course not, because the generic module Comparable(T) has already done this for you. By including this module, you get the operators for free. There’s only one catch: Comparable has one abstract method that your class has to implement—the so-called “spaceship operator”: abstract def <=>(other : T).

All other comparisons are defined using <=>. Your code must work like this: compare the current object’s property against the other’s property, and then return -1, 0, or 1, depending on whether the current object’s property is less than, equal to, or greater than the other’s property. Let’s look at a concrete example where we compare Person objects on their age:

 class​ Person
 include​ Comparable(Person)
 
  getter name, age
 
 def​ ​initialize​(@name : String, @age : Int32)
 end
 
 def​ ​<​=>(other : self) ​# other must be of type self, which is Person
 if​ self.​age​ < other.​age​ ​# here self is the current Person object
  -1
 elsif​ self.​age​ > other.​age
  1
 else​ ​# == case
  0
 end
 end
 end

Because <=> is also defined for numbers, we can shorten our code as follows:

 def​ ​<​=>(other : self) ​# other must be of type self, which is Person
  self.​age​ <=> other.​age
 end

Be sure to look at the code in comparable.cr[37]—it’s written entirely in Crystal itself! See the T? It says the module is generic: it works for any type T that implements <=>. That’s why we had to write include Comparable(Person).

Using Enumerable

This module, when mixed in, gives collection types a large set of methods to query, search, and filter data. The only requirement is that the type implements an each method that returns the subsequent items in the collection using yield. This code snippet defines a class Sequence that contains all integers from 0 to a certain number @top. The class Sequence includes the module Enumerable:

 class​ Sequence
 include​ Enumerable(Int32)
 
 def​ ​initialize​(@top : Int32)
 end
 
 def​ ​each
  0.​upto​(@top) ​do​ |num|
 yield​ num
 end
 end
 end
 
 seq = Sequence.​new​(7)
 # using some methods of module Enumerable:
 seq.​to_a​ ​# => [0, 1, 2, 3, 4, 5, 6, 7]
 seq.​select​ &.​even?​ ​# => [0, 2, 4, 6]
 seq.​map​ { |x| x ** 2 } ​# => [0, 1, 4, 9, 16, 25, 36, 49]

Working with Iterators

Most of the Enumerable methods return an array: they process the collection eagerly. That works well for small volumes of data but creates performance problems as the volume of information grows.

If the number of items in the collection is large and you don’t need to process them all at once, you need a lazy alternative that doesn’t get all the items at once. This is precisely what Iterator was made for. It includes Enumerable, but it redefines a lot of its methods. To implement Iterator, a class needs to code a next method. But you can also use the of class method with next, as in these examples:

 n = 0
 inc = Iterator.​of​ ​do
  n += 1
  n
 end
 
 inc.​next​ ​# => 1
 inc.​next​ ​# => 2
 inc.​next​ ​# => 3
 
 n = 0
 m = 1
 fib = Iterator.​of​ { ret = n; n = m; m += ret; ret }
 
 fib
  .​select​ { |x| x.​even?​ }
  .​first​(10)
  .​to_a​ ​# => [0, 2, 8, 34, 144, 610, 2584, 10946, 46368, 196418]

Although the Fibonacci series is infinite, only the items you need (the first 10) are calculated.

Your Turn 2

How do you change the example comparable.cr if you want to compare persons on the alphabetical order of their names?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.9.197