Getting Input

You can make bad choices with data while you’re programming, but it’s even more likely that users will put incorrect data into your programs when asked for input. Creating resilient code means taking special care with user input. In the following section, your program reads input, and you’ll learn to make it withstand runtime errors.

To gain a better understanding of how Crystal works with types, let’s build a program to read in currencies and their relative exchange rates. Let’s start with reading in integer numbers into an array. You can find the complete source code in getting_input.cr.

The console lets you collect input, but the Playground doesn’t. To make this work, you have to open up a terminal and type: crystal getting_input.cr.

The user provides the numbers, and you expect that they will be smaller than 256, so they’re of type Int8.

 puts ​"Enter the numbers one by one, and end with an empty line:"
 arr = [] of Int8

You’ll start with an empty array so you have to indicate the type [] of Int8. There are other ways to indicate the type of the contents of an array, like providing data when you initialize the array:

 arr1 = [75, 42, 126]
 typeof(arr1) ​# => Array(Int32)

Why did the type come up as Int32 when all of those numbers are below 127? Int32 is the default integer type. If you want to force Int8, perhaps for performance reasons, you have to write that explicitly, like so:

 arr1 = [75_i8, 42_i8, 126_i8]
 typeof(arr1) ​# => Array(Int8)

(Crystal offers i8, i16, i32, and i64 suffixes for signed integers, and u8, u16, u32, and u64 for unsigned integers.)

You read from the console with gets, which return everything it reads in as a String. You can display the input with string interpolation.

 puts ​"Enter a number:"
 num = gets
 p ​"You entered ​​#{​num​}​​"​ ​# => "You entered 42"

Let’s add the number to our array:

 arr << num

Crystal won’t let you do this:

 Error: no overload matches 'Array(Int8)#<<' with type (String | Nil).

By now, this should be getting familiar. The best way to find out what went wrong is to examine the type of num (first comment out the previous faulty line). You can do this in two ways:

 p typeof(num) ​# => (String | Nil)
 p num.​class​ ​# => String

You see that Crystal distinguishes between:

  • The compile-time type: which is the type the compiler sees, given by typeof
  • The run-time type: which is the type the object has while the program is running, given by its class

nil can’t be appended to an array of integers. But why does the compiler think the input could be nil? Well, instead of entering a number, enter CTRL+D and see what happens: The class of num is Nil! The method << can’t be applied to nil. You have to guard against that input. There are several ways to do this. Because nil is falsy, the simplest is to test with if:

 if​ num
  arr << num
 end

We aren’t finished yet because now we get another error:

 no overload matches 'Array(Int8)#<<' with type String.

The compiler now knows that num is a String, but the array can only contain Int8. Time to convert the input with to_i8:

 if​ num
  arr << num.​to_i8
  p arr ​# => for example: [42]
 end

Now, Crystal will convert the string to an 8-bit signed integer.

Even if the string is a number, a few things can happen along the way. Including a decimal point will cause an error, as will integers outside of the range of -127 to 128. Non-numeric characters will break this conversion, which you’ll address later in the chapter.

Also, the if works for one input. We don’t know how many numbers will be provided, so we need a loop. while fits perfectly: gets itself returns a value that can be tested as the while condition. If this value isn’t nil or false, while is okay with it and adds it to the array.

 while​ num = gets
  arr << num.​to_i8
  p arr ​# => for example: [2, 3, 3, 5]
 end

Remember: Only nil, false, or null pointers are considered false by Crystal in any logical value or if, unless, while, and until expression. Any other value—including the string "false" and the number 0—works as true while testing expressions.

Assign Shortcut

images/aside-icons/tip.png

Because of the rules on falsiness, a ||= b, which is a shortcut for a || (a = b), is used to assign b to a only when a is nil:

 mem = ​nil
 mem ||= 1
 mem ​# 1
 mem ||= ​"Crystal"
 mem ​# 1

This is commonly used in memoization: return the value of a when a is not nil, but otherwise return b.

Returning to our input, let’s remove any whitespace characters with strip, just in case. To end the loop, test whether the user enters “stop” or just ENTER, and then break from the loop:

 while​ num = gets
  num = num.​strip​ ​# removes whitespace
 if​ num == ​""​ || num == ​"stop"
 break
 end
  arr << num.​to_i8
 end
 p arr ​# => for example: [78_i8, 56_i8, 12_i8]

read_line

images/aside-icons/tip.png

If testing after gets that the input isn’t nil doesn’t seem elegant, you can use the read_line method instead.

Try it out—the exercise read_line.cr shows you how to do this.

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

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