Some Nice Tricks

Now that you know the foundations of how Crystal’s classes and structs work, it’s time to explore some things that make it easier to use them.

to_s(io)

A convenient class should have a method that converts its objects to a string in order to describe itself. A Java developer will know this as the toString() method. A Rubyist will know to_s, which is inherited from Object. Crystal also knows to_s.

However, it’s best not to use this form, but rather to override the to_s(IO) method. From the previous section, you know that a String is created in heap memory: if you need to create lots of them, that will damage your program’s performance because they all have to be garbage collected. You should avoid creating too many temporary strings or objects in general. Instead, append your objects with << immediately to an IO object, without creating intermediate strings through interpolation, using to_s or concatenation, as in the first of the to_s methods that follow:

 class​ Mineral
  getter name, hardness
 
 def​ ​initialize​(@name : String, @hardness : Float64)
 end
 
 # Good
 def​ ​to_s​(io)
  io << name << ​", "​ << hardness
 end
 
 end
 
 min1 = Mineral.​new​(​"gold"​, 42.0)
 io = IO::Memory.​new
 # To see what io contains, use to_s:
 min1.​to_s​(io).​to_s​ ​# => "gold, 42.0"

IO is a module for input and output in Crystal, supporting many different media: in memory, on a file, on a socket, and so on.

Using Exception as a Class

Knowing that Exception is a class, you can infer that it has a lot of subclasses, such as IndexError, TypeCastError, IO::Error, and others. In addition, you can also define your own subclasses:

 class​ CoolException < Exception
 end
 
 raise​ CoolException.​new​(​"Somebody pushed the red button"​)
 # => Somebody pushed the red button (CoolException)

Better rescue this! You can use multiple rescue branches, which each accept a certain type of Exception, putting a catchall rescue branch at the end:

 ex = ​begin
 raise​ CoolException.​new
 rescue​ ex1 : IndexError
  ex1.​message
 rescue​ ex2 : CoolException | KeyError
  ex2.​message
 rescue​ ex3 : Exception
  ex3.​message
 rescue​ ​# catch any kind of exception
 "an unknown exception"
 end​ ​# => "ex2"

Here’s a more realistic example of exception handling when reading a file, trying to parse it in JSON format, and then writing it back to another file:

 require ​"json"
 path = ​"path/to/file"
 
 begin
 if​ File.​exists?​(path)
  raw_file = File.​read​(path)
  map = JSON.​parse​(raw_file)
  File.​write​(path, ​"ok"​)
 :ok
 end
 rescue​ JSON::ParseException ​# Parsing error
 raise​ ​"Could not parse file"
 rescue​ ex
 raise​ ​"Other error: ​​#{​ex.​message​​}​​"
 end

Defining Callbacks

Using the concepts from Working with Yield, Procs, and Blocks about procs, here’s a nice way to define a series of callbacks. A callback is a method that has to be called when a certain event happens. In this case, each after_save call adds a new callback, and when the save event finally occurs, each one of the callbacks in turn is called:

 class​ MineralC
 def​ ​initialize
  @callbacks = [] of ->
 end
 
 def​ ​after_save​(&block)
  @callbacks << block
 end
 
 # save in database, then execute callbacks
 def​ ​save
 # save
 
 
 rescue​ ex
  p ​"Exception occurred: ​​#{​ex.​message​​}​​"
 else
  @callbacks.​each​ &.​call
 end
 end
 min = MineralC.​new
 min.​after_save​ { puts ​"Save in DB successful"​ }
 min.​after_save​ { puts ​"Logging save"​ }
 min.​after_save​ { puts ​"Replicate save to failover node"​ }
 min.​save​ ​# =>
 # Save in DB successful
 # Logging save
 # Replicate save to failover node

Your Turn 4

a. Reopen a Class:

By now, you know why the following code gives an exception:

 x = rand < 0.0001 ? 1 : ​"hello"
 x - 1 ​# => Error: undefined method '-' for String

Define a new method for String by overloading the - operator. It should take a number and cut off as many characters from the string as the number indicates.

b. Reopen a Method:

Predict what the p statements in the following code will show, first in Crystal and then in Ruby. Try it out and explain the difference:

 class​ A
 def​ ​b
  41
 end
 end
 # this can also be written on 1 line as: class A; def b; 41; end; end;
 
 p A.​new​.​b
 
 class​ A
 def​ ​b
  42
 end
 end
 
 p A.​new​.​b

A method can be redefined and effectively overwritten, but you can invoke the earlier version inside the redefinition with previous_def.

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

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