From ee92e437a201438f081fcacc80b0f1c7d537fa62 Mon Sep 17 00:00:00 2001 From: Franciscello Date: Wed, 28 Dec 2022 23:34:35 -0300 Subject: [PATCH] Blocks section rewrite --- docs/SUMMARY.md | 1 + docs/tutorials/basics/70_blocks.md | 295 +++++++++++++++++++++++++++++ docs/tutorials/basics/README.md | 1 + 3 files changed, 297 insertions(+) create mode 100644 docs/tutorials/basics/70_blocks.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 29bbcc9b3..2bbd9f2f5 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -143,6 +143,7 @@ * [Strings](tutorials/basics/40_strings.md) * [Control Flow](tutorials/basics/50_control_flow.md) * [Methods](tutorials/basics/60_methods.md) + * [Blocks](tutorials/basics/70_blocks.md) * [Manuals](man/README.md) * [Using the Compiler](man/crystal/README.md) * [The Shards Command](man/shards/README.md) diff --git a/docs/tutorials/basics/70_blocks.md b/docs/tutorials/basics/70_blocks.md new file mode 100644 index 000000000..cac184a35 --- /dev/null +++ b/docs/tutorials/basics/70_blocks.md @@ -0,0 +1,295 @@ +# Blocks + +As we already mentioned in the [methods](./60_methods.html#blocks) section, _blocks_ is an interesting and important topic. So let's start defining them: + +A _block of code_ or _block_ for short, is a way to abstract a sequence of interactions between objects. They are commonly used as a method parameter, as a way to parameterize (forgive the redundancy) part of its behavior. + + +# Blocks and Methods + +Methods may receive a _block_ as an argument. And using the keyword `yield` indicates the place in the method's body where we want to "place" said `block`: + +```crystal-play +def with_42 + yield +end + +with_42 do + puts 42 +end +``` + +Or the **places** where we want to execute the _block_: + +```crystal-play +def three_times + yield + yield + yield +end + +three_times do + puts 42 +end + +Let's see another example with a common use scenario for _blocks_: collections. + +Here are two examples: + +```crystal-play +# Example 1 +arr = [1, 2, 3] +index = 0 + +while index < arr.size + elem = arr[index] + + puts elem + 42 + + index = index + 1 +end +``` + +```crystal-play +# Example 2 +arr = ["John", "Paul", "George","Ringo"] +index = 0 + +while index < arr.size + elem = arr[index] + + puts "Hello #{elem}" + + index = index + 1 +end +``` + +As we can see in the above examples, the structures are the same: we want to traverse the array and do _something_ with the current element at each step. And here is where _blocks_ come handy: we can parameterize that _something_ we want to execute. + +So let's rewrite the examples. We are going to declare a new method that will abstract the structure duplicated in both examples: It will receive the array and what we want to do at each step using a _block_: + +```crystal-play +def with_array(arr) + index = 0 + + while index < arr.size + elem = arr[index] + + yield elem # yes! we can pass an argument to a block. + + index = index + 1 + end +end + +# Example 1 +with_array [1, 2, 3] do |current_elem| + puts elem + 42 +end + +# Example 2 +with_array ["John", "Paul", "George","Ringo"] do |current_elem| + puts "Hello #{current_elem}" +end +``` + +The abstracted structure declared in the `with_array` method is very common and that's why it's already implemented in [Indexable#each](https://crystal-lang.org/api/latest/Indexable.html#each%28%26%3AT-%3E%29-instance-method). + +**Note:** both blocks declare a parameter (the current element while traversing the array) which we will see next. + +## Blocks with parameters + +Like methods, _blocks_ may receive parameters: + +```crystal +some_method do |param1, param2, param3| +end +``` + +Let's see an example: + +```crystal-play +def other_method + yield 42, "Hello", [1, "Crystal", 3] +end + +other_method do |n, s, arr| + puts "#{s} #{arr[1]}" + puts n +end +``` + +### Underscore + +What happens if the _block_ we are providing does not use all the passed arguments? In that case we can use an underscore just to indicate that an expected parameter it's not used by the block (so no name needed): + +```crystal-play +def other_method + yield 42, "Hello", [1, "Crystal", 3] +end + +other_method do |_, _, arr| + puts arr[1] +end +``` + +### Unpacking parameters + +Let's suppose we have an array of arrays and our _block_ prints each element in each array. One way to implement this would be: + +```crystal-play +arr = [["one", 42], ["two", 24]] +arr.each do |arg| + puts "#{arg[0]} - #{arg[1]}" +end +``` + +This works, but there is a concise way of writing the same but using parameter unpacking: + +```crystal-play +arr = [["one", 42], ["two", 24]] +arr.each do |(word, number)| + puts "#{word} - #{number}" +end +``` + +**Note:** we use parentheses to unpack the argument in the different block parameters. + +Unpacking arguments into parameters works only if the argument's type responds to `[i]` (with `i` an `integer`). In our example `Array` inherits [Indexable#[ ]](https://crystal-lang.org/api/latest/Indexable.html#%5B%5D%28index%3AInt%29-instance-method) + +### Splats + +When the expected _block_ argument is a [Tuple](../syntax_and_semantics/literals/tuple.html) we can use auto-splatting (see [Splats](../syntax_and_semantics/operators.html#splats)) as a way of destructuring the `tuple` in block parameters (and without the need of parentheses). + + +```crystal-play +arr = [{"one", 42}, {"two", 24}] +arr.each do |word, number| + puts "#{word} - #{number}" +end +``` + +**Note:** `Tuples` also implements [Tuple#[ ]](https://crystal-lang.org/api/latest/Tuple.html#%5B%5D%28index%3AInt%29-instance-method) meaning that we also can use _unpacking_. + +### Block's returned value + +A _block_, by default, returns the value of the last expression (the same as a method). + +```crystal-play +def with_number(n : Int32) + block_result = yield n + puts block_result +end + +with_number(41) do |number| + number + 1 +end +``` + +#### Returning keywords + +We can use the `return` keyword ... but, let's see the following example: + +```crystal-play +def with_number(n : Int32) + block_result = yield n + puts block_result +end + +def test_number(n) + with_number(n) do |number| + return number if number.negative? + number + 1 + end + + puts "Inside `#test_number` method after `#with_number`" +end + +test_number(42) +``` + +Outputs: + +```console + +43 +Inside `#test_number` method after `#with_number` +``` + +And if we want to `test_number(-1)` we would expect: + +```console + +-1 +Inside `#test_number` method after `#with_number` +``` + +Let's see ... + +```crystal-play +def with_number(n : Int32) + block_result = yield n + puts block_result +end + +def test_number(n) + with_number(n) do |number| + return number if number.negative? + number + 1 + end + + puts "Inside `#test_number` method after `#with_number`" +end + +test_number(-1) +``` + +The output is empty! This is because Crystal implements *full closures*, meaning that using `return` inside the block will return, not only from the _block_ itself but, from the method where the _block_ is defined (i.e. `#test_number` in the above example). + +If we want to return only from the _block_ then we need to use the keyword `next`: + +```crystal-play +def with_number(n : Int32) + block_result = yield n + puts block_result +end + +def test_number(n) + with_number(n) do |number| + next number if number.negative? + number + 1 + end + + puts "Inside `#test_number` method after `#with_number`" +end + +test_number(-1) +``` + +The last keyword for returning from a _block_ is `break`. Let's see how it behaves: + +```crystal-play +def with_number(n : Int32) + block_result = yield n + puts block_result +end + +def test_number(n) + with_number(n) do |number| + break number if number.negative? + number + 1 + end + + puts "Inside `#test_number` method after `#with_number`" +end + +test_number(-1) +``` + +The ouput is + +```console + +Inside `#test_number` method after `#with_number` +``` + +As we can see the behaviour is something between using `return` and `next`. With `break` we return from the _block_ and from the method yielding the _block_ (`#with_number` in this example) but not from the method where the `block` is defined. diff --git a/docs/tutorials/basics/README.md b/docs/tutorials/basics/README.md index a887be27e..9b673b423 100644 --- a/docs/tutorials/basics/README.md +++ b/docs/tutorials/basics/README.md @@ -13,3 +13,4 @@ Contents: * [Strings](40_strings.md) – Examples on how to modify strings * [Control Flow](50_control_flow.md) – How to structure a program with conditionals and loops * [Methods](60_methods.md) – Introduction to methods, arguments and type restrictions +* [Blocks](70_blocks.md) – Introduction to blocks