Controlling the Flow and Types

So far, types have mostly been about keeping our programs out of trouble. While that safety is useful, Crystal also offers many approaches so you can write code whose flow is determined by the types of data it’s processing. This simplifies the challenge of keeping code concise while accepting many different kinds of information.

Controlling the Flow showed you how to work with flow constructs, and you saw that a variable is never nil or false in the if-branch. Also, if you have an if var1 && var2, both var1 and var2 are guaranteed not to be nil. Those basics are useful, but Crystal offers many more possibilities.

Instead of using explicit ifs, you can use a more compact ternary form, as shown here:

 var1 = ​if​ 1 > 2 3 ​else​ 4 ​end​ ​# => 4
 # stated more concisely
 var2 = 1 > 2 ? 3 : 4 ​#=> 4

What is the compile-time type of var1 in?

 # random choice between number and string
 var1 = rand < 0.5 ? 42 : ​"Crystal"​ ​#=> 42 or "Crystal"

You guessed it: the union type Int32 | String. This means that calling the abs method on var1 won’t work:

 typeof(var1) ​# => (Int32 | String)
 var1.​abs​ ​# => Error: undefined method 'abs' for String

If you could be certain that var1 is an Int32, you could also make the compiler believe that by using as to restrict the type:

 ivar1 = var1.​as​(Int32)

In this particular case, you can’t be certain because of rand, and if var1 turns out to be of type String, the as casting would give a runtime error:

 cast from String to Int32 failed (TypeCastError)

To prevent that, use the as? variant, which returns nil instead of an exception, so you can test it in an if:

 ivar1 = var1.​as?​(Int32) ​# => 42 or nil
 # or retaining only the value 42 with if:
 if​ ivar1 = var1.​as?​(Int32)
  p ivar1.​abs​ ​# => 42
 end

Explicitly testing that var1 is nil can be done with the following:

 if​ var1.​nil?
 # here var1 is nil
 end

But this should almost never be necessary. Simply use if var1 with the else branch to handle the nil case.

You can test the type of an object with the is_a? method. Inside such an if, the object is guaranteed to be of that type:

 var1 = 42
 if​ var1.​is_a?​(Number)
 # here var1 is a Number, which can be integer or floating point
 end

If you have to test on a number of types, it’s better to use case:

 case​ var1
 when​ Number
  p var1 + 42
 when​ String
  p ​"we have a string"
 else
  p ​"var1 is not a number or a string"
 end
 # => 84

case is a very versatile construct: it can use all kinds of variables, including symbols, enums, and tuples. Not only can it test on the type of the variable, but it can also invoke a method on it, as in this snippet:

 num = 42
 case​ num
 when​ .​even?
  puts ​"you have an even number"
 when​ .​odd?
  puts ​"you have an odd number"
 end
 # => you have an even number

Using a tuple variable together with case makes some pattern matching possible. FizzBuzz is a common programming question in which you’re asked to print out “FizzBuzz” if a number is divisible by 3 and 5, “Fizz” if divisible by 3, and “Buzz” if divisible by 5, counting from 1 to 100. case enables you to write a compact version:

 (1..100).​each​ ​do​ |i|
 case​ {i % 3, i % 5}
 when​ {0, 0}
  puts ​"FizzBuzz"
 when​ {0, _}
  puts ​"Fizz"
 when​ {_, 0}
  puts ​"Buzz"
 else
  puts i
 end
 end

% is the modulo operation, which returns the remainder from integer division. Note that you can use one (or more) _ when testing a tuple if that value doesn’t matter.

Another way to deal with types is to see if the contents of a variable can reply to a given method call. Safely testing that is done with responds_to?:

 var1 = ​"Crystal"
 if​ var1.​responds_to?​(​:abs​) ​# false in this case
  var1.​abs
 end

Your Turn 6

a. What is the value of var1 after this if?

 if​ var1 = 1
  puts ​"inside if"
  var1 = 2
  puts var1
 end

This behavior is different from many other programming languages!

b. What is the output of the following program?

 if​ (num = 9) < 0
  puts ​"​​#{​num​}​​, is negative"
 elsif​ num < 10
  puts ​"​​#{​num​}​​, has 1 digit"
 else
  puts ​"​​#{​num​}​​, has multiple digits"
 end

c. Test this code snippet and explain its behavior. Do you see a way to improve this?

 begin
  a = 4 + 6
 rescue
  puts ​"an ex occurred"
 ensure
  puts a + 42
 end

Hint: The compiler takes the possibility of an exception into account.

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

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