Crystal's Channels and the While Loop
Tags: crystal, todayilearned
Published: 2020-01-04 08:32:22

Like most, I like to learn a new programming language by doing. At the moment, I'm enjoying the catharsis of completing [unimplemented Rosetta Code tasks](https://rosettacode.org/wiki/Category:Unimplemented_tasks_by_language) in [Crystal](https://crystal-lang.org) and [vlang](https://vlang.io).

During some exploration of the Crystal programming language, I took on the [Synchronous Concurrency](https://rosettacode.org/wiki/Synchronous_concurrency#Crystal) task to pour some of my knowledge of the [CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes) pattern from Go, into Crystal.

The task asks you to communicate between multiple threads of execution within a process:

> One of the concurrent units will read from a file named "input.txt" and send the contents of that file, one line at a time, to the other concurrent unit, which will print the line it receives to standard output. The printing unit must count the number of lines it prints. After the concurrent unit reading the file sends its last line to the printing unit, the reading unit will request the number of lines printed by the printing unit. The reading unit will then print the number of lines printed by the printing unit.<br /><br />
> This task requires two-way communication between the concurrent units. All concurrent units must cleanly terminate at the end of the program.

My original solution to the task was as follows:

``` ruby
File.write("input.txt", "a\nb\nc")
 
lines = Channel(String).new
 
spawn do
  File.each_line("input.txt") do |line|
    lines.send(line)
  end
  lines.close
end
 
begin
  while
    line = lines.receive
    puts line
  end
rescue ex : Channel::ClosedError
end
 
File.delete("input.txt")
```

```
$ crystal run example.cr
a
b
c
```

The Crystal docs [here](https://crystal-lang.org/api/0.32.1/Channel.html) and [here](https://crystal-lang.org/reference/guides/concurrency.html) are fairly light on Channels, so there was an element of trial and error involved. I knew that a call to `receive` against a closed channel would result in a raised `Channel::ClosedError` so I made use of this in a try/catch (begin/rescue) block.

I'm a big fan of [Go](https://golang.org), so much prefer to handle errors over catching exceptions. I wasn't satisfied with this solution and wanted to see if I could make Crystal's type system work to my advantage.

Crystal's type system makes use of [Union](https://crystal-lang.org/api/0.32.1/Union.html) types, allowing a variable to be one or more types at compile time. For example, because `a` is initialised in both arms of the if statement in the following example, it can be either a `String` or an `Int32`:

``` ruby
if true
  a = "string"
else
  a = 1
end

puts typeof(a)
# => (Int32 | String)
```

What's interesting about Union types in the context of Channels, is `Nil`. There's another version of the `receive` method, that instead of raising an exception when a channel is closed, it returns nil. It's called `receive?` and its definition can be found [here](https://github.com/crystal-lang/crystal/blob/master/src/channel.cr#L271).

Initially, I simply swapped out the called to `receive` with `receive?` and re-ran my code:

``` ruby
File.write("input.txt", "a\nb\nc")
 
lines = Channel(String).new
 
spawn do
  File.each_line("input.txt") do |line|
    lines.send(line)
  end
  lines.close
end
 
begin
  while
    line = lines.receive?
    puts line
  end
rescue ex : Channel::ClosedError
end
 
File.delete("input.txt")
```

I was expecting the call to `receive?` to block indefinitely after reading the last line from "input.txt" but was surprised to see the program output exactly what it had done in the original example:

```
$ crystal run example.cr
a
b
c
```

Confused, I added a log line into the `rescue` arm to see if a `Channel::ClosedError` exception had been thrown anyway (despite what the Crystal source told me) and re-ran:

``` ruby
rescue ex : Channel::ClosedError
  puts ex
```

```
$ crystal run example.cr
a
b
c
```

No exception!?

Then it dawned on my that the `while` loop must be working to [truthy](https://crystal-lang.org/reference/syntax_and_semantics/truthy_and_falsey_values.html) values and ignoring the line break!

I rewrote my code to this new expectation, removing the begin/rescue block and the line break, inlining the declaration of `line`, and relying on nil from the fourth call to `receive?`:

``` ruby
File.write("input.txt", "a\nb\nc")
 
lines = Channel(String).new
 
spawn do
  File.each_line("input.txt") do |line|
    lines.send(line)
  end
  lines.close
end
 
while line = lines.receive?
  puts line
end
 
File.delete("input.txt")
```

```
$ crystal run example.cr
a
b
c
```

Success! I've learned something new about Crystal by fumbling around in the dark.

The following demonstrates this succinctly by reading from the array elements until nil:

``` ruby
a = [1, 2, 3, nil, 4, 5]

while b = a.shift
  puts b
end
```