├── SUMMARY.md
├── book
├── chapter-9-macros-and-metaprogramming.md
├── chapter-10-c-bindings.md
├── chapter-2-installing-crystal.md
├── chapter-3-writing-your-first-crystal-program.md
├── chapter-1-why-crystal.md
├── chapter-8-concurrency-and-channels.md
├── chapter-5-testing.md
├── chapter-7-types-and-method-overloading.md
├── chapter-4-creating-a-new-project.md
└── chapter-6-fizzbuzz.md
└── README.md
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Table of contents
2 |
3 | * [Know Ruby? Enter Crystal!](README.md)
4 |
5 | ## book
6 |
7 | * [Chapter 1: Why Crystal?](book/chapter-1-why-crystal.md)
8 | * [Chapter 2: Installing Crystal](book/chapter-2-installing-crystal.md)
9 | * [Chapter 3: Writing Your First Crystal Program](book/chapter-3-writing-your-first-crystal-program.md)
10 | * [Chapter 4: Creating a new project](book/chapter-4-creating-a-new-project.md)
11 | * [Chapter 5: Testing](book/chapter-5-testing.md)
12 | * [Chapter 6: FizzBuzz](book/chapter-6-fizzbuzz.md)
13 | * [Chapter 7: Types and Method Overloading](book/chapter-7-types-and-method-overloading.md)
14 | * [Chapter 8: Concurrency and Channels](book/chapter-8-concurrency-and-channels.md)
15 | * [Chapter 9: Macros and Metaprogramming](book/chapter-9-macros-and-metaprogramming.md)
16 | * [Chapter 10: C Bindings](book/chapter-10-c-bindings.md)
17 |
18 |
--------------------------------------------------------------------------------
/book/chapter-9-macros-and-metaprogramming.md:
--------------------------------------------------------------------------------
1 | # Chapter 9: Macros and Metaprogramming
2 |
3 | We love Ruby because of its’ dynamic nature and metaprogramming! Unlike Ruby, Crystal is a compiled language. That’s why there are some key differences.
4 |
5 | * There’s no `eval`.
6 | * There’s no `send`.
7 |
8 | In Crystal we use `Macro`s to achieve this kind of behaviour and metaprogramming. You can think of `Macro`s as ‘Code that writes/modifies code’.
9 |
10 | P.S: `Macro`s are expanded into code at compile-time.
11 |
12 | Check this.
13 |
14 | ```ruby
15 | macro define_method(name, content)
16 | def {{name}}
17 | {{content}}
18 | end
19 | end
20 |
21 | define_method foo, 1
22 | # This generates:
23 | #
24 | # def foo
25 | # 1
26 | # end
27 |
28 | foo # => 1
29 | ```
30 |
31 | In the example we created a macro named `define_method` and we just called that macro like a normal method. That macro expanded into
32 |
33 | ```ruby
34 | def foo
35 | 1
36 | end
37 | ```
38 |
39 | Pretty cool! We got `eval` behaviour at compile-time.
40 |
41 | Macros are really powerful but there’s one rule that you can’t break.
42 |
43 | _**A macro should expand into a valid Crystal program**_
44 |
45 |
--------------------------------------------------------------------------------
/book/chapter-10-c-bindings.md:
--------------------------------------------------------------------------------
1 | # Chapter 10: C Bindings
2 |
3 | There are lots of useful C libraries out there. It’s important that we make use of them instead of rewriting every single of them.
4 |
5 | In Crystal, It’s super easy to use existing C libraries with bindings. Even Crystal itself uses C libraries.
6 |
7 | For example Crystal uses `libpcre` for it’s `Regex` implementation.
8 |
9 | Like I said it’s super easy to write bindings for C. Crystal itself links to `libpcre` like this
10 |
11 | ```ruby
12 | @[Link("pcre")]
13 | lib LibPCRE
14 | ...
15 | end
16 | ```
17 |
18 | With just 3 lines of code you we’re linked to `libpcre` :\) We use `lib` keyword to group functions and types that belong to a library. And it’s a good convetion to start with `Lib` for your C library declarations.
19 |
20 | Next we bind to C functions with the `fun` keyword.
21 |
22 | ```ruby
23 | @[Link("pcre")]
24 | lib LibPCRE
25 | type Pcre = Void*
26 | fun compile = pcre_compile(pattern : UInt8*, options : Int, errptr : UInt8**, erroffset : Int*, tableptr : Void*) : Pcre
27 | end
28 | ```
29 |
30 | Here we binded to `libpcre`s compile function with the matching types. Now we can easily access this function in our Crystal code.
31 |
32 | ```ruby
33 | LibPCRE.compile(..)
34 | ```
35 |
36 |
--------------------------------------------------------------------------------
/book/chapter-2-installing-crystal.md:
--------------------------------------------------------------------------------
1 | # Chapter 2: Installing Crystal
2 |
3 | ### Binary installers
4 |
5 | The Crystal project provides official binary installers. You can get both releases and nightlies. Binary installers are the fastest and easiest way to get going with Crystal. Because Crystal is written in Crystal, compiling the Crystal compiler actually entails compiling it three times! This means it’s quite slow. But a binary install should be snappy!
6 |
7 | Crystal has a [lovely installation page](https://crystal-lang.org/install), so I recommend you just go check that out and download the proper version.
8 |
9 | Note that this book has been tested with Crystal 1.5.0, and so if you use the latest nightly, something may have changed.
10 |
11 | ### From Source
12 |
13 | You will probably build the nightly version if you build from source, so be ready for some bugs in the code samples. This book was written for 1.5.0.
14 |
15 | The [Crystal README](https://crystal-lang.org/install/from_sources/) has great instructions for building from source. Just got follow their instructions!
16 |
17 | #### Future Proofing
18 |
19 | The version this book is written for is 1.5.0
20 |
21 | If you run
22 |
23 | ```text
24 | $ crystal
25 | ```
26 |
27 | and it spits out a bunch of help information, you’re good to go with Crystal.
28 |
29 |
--------------------------------------------------------------------------------
/book/chapter-3-writing-your-first-crystal-program.md:
--------------------------------------------------------------------------------
1 | # Chapter 3: Writing Your First Crystal Program
2 |
3 | ## Writing Your First Crystal Program
4 |
5 | Okay! Let’s get down to it: in order to call yourself an “X Programmer,” you must write “Hello, world” in X. So let’s do it. Open up a text file: I’ll use `vim` because I’m that kind of guy, but use whatever you want. Crystal programs end in `.cr`:
6 |
7 | ```text
8 | $ vim hello.cr
9 | ```
10 |
11 | Put this in it:
12 |
13 | ```ruby
14 | puts "Hello World!"
15 | ```
16 |
17 | And run it with `crystal` command:
18 |
19 | ```text
20 | $ crystal hello.cr
21 | Hello World!
22 | ```
23 |
24 | It should run and print the output without error. If you get one, double check that you have the double quotation marks. Errors look like this:
25 |
26 | ```text
27 | $ crystal hello.cr
28 | Syntax error in ./hello.cr:1: unterminated char literal, use double quotes for strings
29 |
30 | puts 'Hello World!'
31 | ```
32 |
33 | By the way `crystal` command is great for quickly running your code but it’s slow. Each time it compiles and runs your program Let’s see how much does it takes to run that program.
34 |
35 | ```text
36 | $ time crystal hello.cr
37 | crystal hello.cr 0.30s user 0.20s system 154% cpu 0.326 total
38 | ```
39 |
40 | 0.326 seconds for a `Hello World`? Now that’s slow.
41 |
42 | Instead of compiling and running again you can compile it to native code with the `build`command.
43 |
44 | ```text
45 | $ crystal build hello.cr
46 | ```
47 |
48 | And to run your program, do the Usual UNIX Thing:
49 |
50 | ```text
51 | $ time ./hello
52 | ./hello 0.00s user 0.00s system 87% cpu 0.006 total
53 | ```
54 |
55 | You should see “Hello, world.” print to the screen 50x faster :\) Congrats!
56 |
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Know Ruby? Enter Crystal!
2 |
3 | [Crystal](http://crystal-lang.org/) programming language is super interesting. I've spent the time checking out Crystal and digging through what is available, and wrote it all down as I learned.
4 |
5 | #### Frequently Asked Questions
6 |
7 | **Who are you?**
8 |
9 | I'm [**Serdar Dogruyol**](https://github.com/sdogruyol), and I've been a Crystalist for years now. I'm the author of [Kemal](http://kemalcr.com/), curator of [Crystal Weekly](http://www.crystalweekly.com/) and a [Crystal Core Member](https://github.com/orgs/crystal-lang/people).
10 |
11 | I love new programming languages, and while I've been using Ruby for the past years, I've coded in Java, PHP, Crystal, Elixir and a host of others. I also love writing, and good documentation, which is hard to find. Crystal doesn't have a lot of that yet, so I'm pitching in!
12 |
13 | **Why Crystal?**
14 |
15 | I've written [a blog post](http://serdardogruyol.com/why-crystal) dedicated to this, but basically, Crystal is good at everything Ruby is not good at...plus as a Rubyist you already know some Crystal.
16 |
17 | **What am I getting?**
18 |
19 | Basically, you can read the whole thing online for free!
20 |
21 | **Do I have to know Ruby?**
22 |
23 | Really, I love alliteration: the only thing the **'for Rubyists'** really means is that I assume you don't know about types, pointers, concurrency, or similar things. That's okay, you've never had to think about them before! I explain this stuff in extra depth. If you program in another dynamically typed language, you'll be just fine. If you program in another typed language, you'll be more than fine.
24 |
25 | **Where can I find some Crystal Libraries (Shards)?**
26 |
27 | * [awesome-crystal](https://github.com/veelenga/awesome-crystal) has a curated list of Crystal shards.
28 | * [crystalshards.org](https://crystalshards.org/) lists all of the available Crystal shards on GitHub (similar to rubygems.org)
29 | * [Here is a list of some popular Ruby gems that have been ported to Crystal](https://github.com/crystal-lang/crystal/wiki/Crystal-Shards-for-Ruby-Gems).
30 |
--------------------------------------------------------------------------------
/book/chapter-1-why-crystal.md:
--------------------------------------------------------------------------------
1 | # Chapter 1: Why Crystal?
2 |
3 | You already write software in Ruby. It pays your bills. You enjoy it. Why should you care about Crystal?
4 |
5 | Let’s think about Ruby for a minute: what’s its biggest weakness? For me, it’s these things:
6 |
7 | * Concurrency
8 | * Speed
9 | * Documentation
10 |
11 | What’s awesome about Ruby?
12 |
13 | * Blocks
14 | * Vaguely functional
15 | * Syntax is pretty easy
16 | * Focus on developer happiness
17 | * Get up and running quickly
18 | * Dynamically typed
19 |
20 | So we could learn a lot from a language that’s easy as Ruby, handles concurrency well, and is fast. We don’t want to sacrifice anonymous functions, pretty syntax, or not making `AbstractFactoryFactoryImpls` just to get work done.
21 |
22 | I think that language is _Crystal_.
23 |
24 | Now: Crystal is not perfect. It is getting better. But the point is to _learn_. and using a language that’s very familiar, yet very different, can teach us a lot.
25 |
26 | Here’s “Hello World” in Crystal:
27 |
28 | ```ruby
29 | puts "Hello, world!"
30 | ```
31 |
32 | Here’s a concurrent “Hello World” in Crystal:
33 |
34 | ```ruby
35 | channel = Channel(String).new
36 |
37 | 10.times do
38 | spawn {
39 | channel.send "Hello?"
40 | }
41 | puts channel.receive
42 | end
43 | ```
44 |
45 | Here’s a rough port to Ruby:
46 |
47 | ```ruby
48 | 10.times.map do
49 | Thread.new do
50 | puts "Hello?"
51 | end
52 | end.each(&:join)
53 | ```
54 |
55 | That’s it. Note the stuff that’s _similar_ to Ruby:
56 |
57 | * Pretty same syntax.
58 | * Variables, while statically typed, have inference, so we don’t need to declare types
59 |
60 | Here’s some stuff that’s _different_:
61 |
62 | * Being compiled and statically typed the compiler will yell at us if we mess up.
63 |
64 | Oh, and:
65 |
66 | ```text
67 | $ time ./hello
68 | ./hello 0.00s user 0.00s system 73% cpu 0.008 total
69 |
70 | $ time ruby hello.rb
71 | ruby hello.rb 0.03s user 0.01s system 94% cpu 0.038 total
72 | ```
73 |
74 | Five times faster. Yay irrelevant microbenchmarks!
75 |
76 | Anyway, I hope you get my point: There’s lots of things about Crystal that make it syntactically vaguely similar enough to Ruby that you can feel at home. And its strengths are some of Ruby’s greatest weaknesses. That’s why I think you can learn a lot from playing with Crystal, even if you don’t do it as your day job.
77 |
78 |
--------------------------------------------------------------------------------
/book/chapter-8-concurrency-and-channels.md:
--------------------------------------------------------------------------------
1 | # Chapter 8: Concurrency and Channels
2 |
3 | Did you remember Chapter 1? We did a concurrent Hello World!
4 |
5 | Here’s a quick reminder.
6 |
7 | ```ruby
8 | channel = Channel(String).new
9 |
10 | 10.times do
11 | spawn {
12 | channel.send "Hello?"
13 | }
14 | puts channel.receive
15 | end
16 | ```
17 |
18 | In Crystal we use the keyword `spawn` to make something work in the background without blocking the main execution.
19 |
20 | To achieve this `spawn` creates a lightweight thread called `Fiber`. `Fiber`s are very cheap to create and you can easily create tens of thousands of `Fiber`s on a single core.
21 |
22 | Okay, that’s really cool! We can use `spawn` to make stuff work in the background but how do we get something back from a `Fiber`.
23 |
24 | Now that’s where `Channel`s come to play.
25 |
26 | ## Channel
27 |
28 | As the name stands a `Channel` is a channel between a sender and the receiver. Therefore a `Channel` lets each other communicate with `send` and `receive` methods.
29 |
30 | Let’s take a line by line look at our previous example.
31 |
32 | ```ruby
33 | channel = Channel(String).new
34 | ```
35 |
36 | We create a `Channel` with `Channel(String).new`. Note that we are creating a `Channel` which will `send` and `receive` messages with type of `String`.
37 |
38 | ```ruby
39 | 10.times do
40 | spawn {
41 | channel.send "Hello?"
42 | }
43 | puts channel.receive
44 | end
45 | ```
46 |
47 | Leaving the loop aside, we are sending a message to our channel inside `spawn`. You might ask ‘Why are we sending message in the background?’ Well, `send` is a blocking operation and if we do that in the main program we gonna block the program forever.
48 |
49 | Consider this:
50 |
51 | ```ruby
52 | channel = Channel(String).new
53 | channel.send "Hello?" # This blocks the program execution
54 | puts channel.receive
55 | ```
56 |
57 | What’s the output of this program? Actually this program won’t ever finish because it gets blocked by `channel.send "Hello?"`. Now that we know why we use `spawn` to send a message let’s continue.
58 |
59 | ```ruby
60 | spawn {
61 | channel.send "Hello?"
62 | }
63 | puts channel.receive
64 | ```
65 |
66 | We just sent a message through our channel in the background with `spawn`. Then we receive it back with `channel.receive`. In this example the message is `Hello?` so this program prints `Hello?`and then finishes.
67 |
68 |
--------------------------------------------------------------------------------
/book/chapter-5-testing.md:
--------------------------------------------------------------------------------
1 | # Chapter 5: Testing
2 |
3 | Rubyists love testing, so before we go any farther, let’s talk about testing. In Crystal, there is a testing framework built in, and it’s named `spec`. It’s pretty similar to `RSpec`.
4 |
5 | Let’s continue with the project we created in Chapter 04.
6 |
7 | As you remember `crystal` created this project structure for us.
8 |
9 | ```text
10 | $ cd sample && tree
11 | -- LICENSE
12 | -- README.md
13 | -- shard.yml
14 | -- spec
15 | -- sample_spec.cr
16 | -- spec_helper.cr
17 | -- src
18 | -- sample.cr
19 | ```
20 |
21 | Did you see that `spec` folder? Yes, as you guess Crystal created this folder and the first spec for us. In Crystal a file is tested with corresponding `_spec` file. Since we named our project as `sample`it created a file named `sample.cr` and the corresponding spec with `spec/sample_spec.cr`.
22 |
23 | By the way, in this context `spec` and `unit test` means the same so we can use them interchangeably.
24 |
25 | Without further ado lets open up `spec/sample_spec.cr`
26 |
27 | ```ruby
28 | require "./spec_helper"
29 |
30 | describe Sample do
31 | # TODO: Write tests
32 |
33 | it "works" do
34 | false.should eq(true)
35 | end
36 | end
37 | ```
38 |
39 | Now this file is pretty interesting. There a three important keywords, `describe`, `it` and `should`.
40 |
41 | Those keywords are only used in `spec`s with the following purposes.
42 |
43 | * `describe` lets you group related specs.
44 | * `it` is used for defining a spec with the given title in between “”.
45 | * `should` is used for making assumptions about the spec.
46 |
47 | As you can see this file has a group `describe`d as `Sample` and `it` has one spec with the title of `works` which makes the assumption that false `should` equal true.
48 |
49 | You might be asking ‘How do we run these tests?’. Well `crystal` command to the rescue.
50 |
51 | ```ruby
52 | $ KEMAL_ENV=test crystal spec
53 | F
54 |
55 | Failures:
56 |
57 | 1) Sample works
58 | Failure/Error: false.should eq(true)
59 |
60 | Expected: true
61 | got: false
62 |
63 | # spec/sample_spec.cr:7
64 |
65 | Finished in 420 microseconds
66 | 1 examples, 1 failures, 0 errors, 0 pending
67 |
68 | Failed examples:
69 |
70 | crystal spec spec/sample_spec.cr:6 # Sample works
71 | ```
72 |
73 | Yay! We got a failing\(red\) test. Reading the output we can easily find which spec failed. Here it’s the spec within the group of `Sample` titled `works` a.k.a `Sample works`. Let’s make it pass\(green\).
74 |
75 | ```ruby
76 | require "./spec_helper"
77 |
78 | describe Sample do
79 | # TODO: Write tests
80 |
81 | it "works" do
82 | true.should eq(true)
83 | end
84 | end
85 | ```
86 |
87 | Rerun the specs.
88 |
89 | ```ruby
90 | $ KEMAL_ENV=test crystal spec
91 |
92 | .
93 |
94 | Finished in 383 microseconds
95 | 1 examples, 0 failures, 0 errors, 0 pending
96 | ```
97 |
98 | Green! That’s all you need to know to get started. Next up: FizzBuzz.
99 |
100 |
--------------------------------------------------------------------------------
/book/chapter-7-types-and-method-overloading.md:
--------------------------------------------------------------------------------
1 | # Chapter 7: Types and Method Overloading
2 |
3 | Crystal is like Ruby, but it’s not Ruby!
4 |
5 | Unlike Ruby, Crystal is a statically typed and compiled language. Most of the time you don’t have to specify the types and the compiler is smart enough to do `type inference`.
6 |
7 | So why do we need types? Let’s start with something simple.
8 |
9 | ```ruby
10 | def add(x, y)
11 | x + y
12 | end
13 |
14 | add 3, 5 # 8
15 | ```
16 |
17 | This is the same in Ruby! We just defined a method that adds two numbers. What if we try to add a number to a string?
18 |
19 | ```ruby
20 | add 3, "Serdar"
21 | ```
22 |
23 | First let’s do that in Ruby.
24 |
25 | ```text
26 | types.cr:2:in `+': String can't be coerced into Fixnum (TypeError)
27 | from types.cr:2:in `add'
28 | from types.cr:5:in `'
29 | ```
30 |
31 | What??? We just got a `TypeError` but we don’t have to care about types in Ruby \( or not :\)\). This is also a `runtime error` meaning that your program just crashed at runtime \(definitely not good\).
32 |
33 | Now let’s do the same in Crystal.
34 |
35 | ```text
36 | Error in ./types.cr:5: instantiating 'add(Int32, String)'
37 |
38 | add 3, "Serdar"
39 | ^~~
40 |
41 | in ./types.cr:2: no overload matches 'Int32#+' with types String
42 | Overloads are:
43 | - Int32#+(other : Int8)
44 | - Int32#+(other : Int16)
45 | - Int32#+(other : Int32)
46 | - Int32#+(other : Int64)
47 | - Int32#+(other : UInt8)
48 | - Int32#+(other : UInt16)
49 | - Int32#+(other : UInt32)
50 | - Int32#+(other : UInt64)
51 | - Int32#+(other : Float32)
52 | - Int32#+(other : Float64)
53 | - Int32#+()
54 |
55 | x + y
56 | ^
57 | ```
58 |
59 | Okay, that’s quite a scary output but actually it’s great. Our Crystal code didn’t compile and also told us that there’s no overload for `Int32#+` and showed us the possible overloads. This is a `compile time error` meaning that our code didn’t compile and we catch the error before running the program. Lovely!
60 |
61 | Now let’s add some types and restrict that method to only accept `Number`s.
62 |
63 | ```ruby
64 | def add(x : Number, y : Number)
65 | x + y
66 | end
67 |
68 | puts add 3, "Serdar"
69 | ```
70 |
71 | Run it.
72 |
73 | ```ruby
74 | Error in ./types.cr:5: no overload matches 'add' with types Int32, String
75 | Overloads are:
76 | - add(x : Number, y : Number)
77 |
78 | puts add 3, "Serdar"
79 | ^~~
80 | ```
81 |
82 | Awesome! Our program didn’t compile again. And this time with shorter and more accurate error output. We just used `type restriction` on `x` and `y`. We restricted them to be `Number` and Crystal is smart enough to stop us from using the method with a `String`.
83 |
84 | ## Method Overloading
85 |
86 | We just saw a lot of overloads. Let’s talk about `Method Overloading`.
87 |
88 | Method overloading is having different methods with the same name and different number of arguments. They all have the same name but actually they are all different methods.
89 |
90 | Let’s overload our `add` method and make it work with a String.
91 |
92 | ```ruby
93 | def add(x : Number, y : Number)
94 | x + y
95 | end
96 |
97 | def add(x: Number, y: String)
98 | x.to_s + y
99 | end
100 |
101 | puts add 3, 5
102 |
103 | puts add 3, "Serdar"
104 | ```
105 |
106 | Let’s run it.
107 |
108 | ```text
109 | $ crystal types.cr
110 | 8
111 | 3Serdar
112 | ```
113 |
114 | Now, that’s method overloading in action. It figured out that we are calling the method with a Number and String and called the appropriate method. You can define as many overload methods as you wish.
115 |
116 |
--------------------------------------------------------------------------------
/book/chapter-4-creating-a-new-project.md:
--------------------------------------------------------------------------------
1 | # Chapter 4: Creating a new project
2 |
3 | Up until now we’ve used the `crystal` command to only run our code.
4 |
5 | Actually `crystal` command is pretty useful and does lot more than that. \(check `crystal --help`for more\)
6 |
7 | For example we can use it to create a new Crystal project.
8 |
9 | ```text
10 | $ crystal init app sample
11 | create sample/.gitignore
12 | create sample/.editorconfig
13 | create sample/LICENSE
14 | create sample/README.md
15 | create sample/shard.yml
16 | create sample/src/sample.cr
17 | create sample/spec/spec_helper.cr
18 | create sample/spec/sample_spec.cr
19 | Initialized empty Git repository in /Users/serdar/crystal_for_rubyists/code/04/sample/.git/
20 | ```
21 |
22 | Awesome. `crystal` helped us create a new project. Let’s see what it did for us.
23 |
24 | * Created a new folder named sample
25 | * Created a LICENSE
26 | * Created `shard.yml` for dependency management.
27 | * Initialized an empty Git repository
28 | * Created a README for our project
29 | * Created `src` and `spec` folders to put our code and tests\(ssh..we’ll talk about it soon\) in it.
30 |
31 | Let’s run it.
32 |
33 | ```text
34 | $ cd sample
35 | $ crystal src/sample.cr
36 | ```
37 |
38 | Nothing! Yay :\)
39 |
40 | Now that we create our first project. Let’s use some external libraries.
41 |
42 | ## Using Shards for dependency management
43 |
44 | To manage dependencies of a project we use `shards`. `shards` is like `bundler` and `shard.yml` is like `Gemfile`.
45 |
46 | * [awesome-crystal](https://github.com/veelenga/awesome-crystal) has a curated list of Crystal shards.
47 | * [crystalshards.org](https://crystalshards.org/) lists all of the available Crystal shards on GitHub (similar to rubygems.org)
48 | * [Here is a list of some popular Ruby gems that have been ported to Crystal](https://github.com/crystal-lang/crystal/wiki/Crystal-Shards-for-Ruby-Gems).
49 |
50 | Let’s open up `shard.yml`.
51 |
52 | ```text
53 | name: sample
54 | version: 0.1.0
55 |
56 | authors:
57 | - sdogruyol
58 |
59 | targets:
60 | sample:
61 | main: src/sample.cr
62 |
63 | crystal: 1.5.0
64 |
65 | license: MIT
66 | ```
67 |
68 | This is a default `shard.yml` and it contains the minimal necessary information about our project. Those are
69 |
70 | * `name` specifies the name of the project
71 | * `version` specifies the version of the project. Crystal itself uses [semver](http://semver.org/) for version management so it’s a good convention for you to follow.
72 | * `authors` section specifies the authors of the project. By default this is taken from your global `git` configuration.
73 | * `crystal` specifies the version of Crystal that the project is using.
74 | * `license` specifies the type of your project license. By default this is `MIT`.
75 |
76 | Okay. That’s great but what can we do with this `shard.yml`? Well we can use this file to add external libraries\(we call it dependency\) and manage them without even worrying about any folders / paths e.g.. Sweet isn’t it?
77 |
78 | Now that we know the true power of `shards` let’s add [Kemal](https://github.com/kemalcr/kemal) to our `shard.yml` and build a simple web application :\)
79 |
80 | Open up `shard.yml`. First we need to add `Kemal` as a dependency to our project. We do this by including
81 |
82 | ```text
83 | dependencies:
84 | kemal:
85 | github: kemalcr/kemal
86 | version: 1.2.0
87 | ```
88 |
89 | That’s great! Now we added `Kemal` to our project. First, we need to install it.
90 |
91 | ```text
92 | $ shards install
93 | Resolving dependencies
94 | Fetching https://github.com/kemalcr/kemal.git
95 | Fetching https://github.com/luislavena/radix.git
96 | Fetching https://github.com/crystal-loot/exception_page.git
97 | Fetching https://github.com/sija/backtracer.cr.git
98 | Installing radix (0.4.1)
99 | Installing backtracer (1.2.1)
100 | Installing exception_page (0.2.2)
101 | Installing kemal (1.2.0)
102 | Writing shard.lock
103 | ```
104 |
105 | Okay now we are ready to use `Kemal` in our project. Open up `src/sample.cr`
106 |
107 | ```ruby
108 | require "kemal"
109 |
110 | module Sample
111 |
112 | get "/" do
113 | "Hello World!"
114 | end
115 |
116 | end
117 |
118 | Kemal.run
119 | ```
120 |
121 | Look how we used `require` to access `Kemal` in our program.
122 |
123 | Let’s run.
124 |
125 | ```text
126 | $ crystal src/sample.cr
127 | [development] Kemal is ready to lead at http://0.0.0.0:3000
128 | ```
129 |
130 | Go to `localhost:3000` and see it in action!
131 |
132 | Now you know how to add dependencies and use others’ `shard`s :\)
133 |
--------------------------------------------------------------------------------
/book/chapter-6-fizzbuzz.md:
--------------------------------------------------------------------------------
1 | # Chapter 6: FizzBuzz
2 |
3 | Of course, the first thing that your job interview for that cushy new Crystal job will task you with is building FizzBuzz. Let’s do it!
4 |
5 | If you’re not familiar, FizzBuzz is a simple programming problem:
6 |
7 | > “Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.”
8 |
9 | This will give us a good excuse to go over some basics of Crystal: Looping, tests, printing to standard output, and a host of other simple things.
10 |
11 | First, let’s create our project.
12 |
13 | ```text
14 | $ crystal init app fizzbuzz
15 | ```
16 |
17 | Let’s write our first failing test. Open up `/spec/fizzbuzz_spec.cr`
18 |
19 | ```ruby
20 | require "./spec_helper"
21 |
22 | describe Fizzbuzz do
23 | it "shouldn't divide 1 by 3" do
24 | div_by_three(1).should eq(false)
25 | end
26 | end
27 | ```
28 |
29 | And run it:
30 |
31 | ```text
32 | $ crystal spec
33 | Error in ./spec/fizzbuzz_spec.cr:7: undefined method 'div_by_three'
34 |
35 | div_by_three(1).should eq(false)
36 | ```
37 |
38 | This makes sense: We haven’t defined any method yet. Let’s define one:
39 |
40 | ```ruby
41 | require "./fizzbuzz/*"
42 |
43 | def div_by_three(n)
44 | false
45 | end
46 | ```
47 |
48 | Akin to Ruby, the value of the last expression gets returned.
49 |
50 | TDD means do the simplest thing! Now that we’ve defined our method, let’s compile and run our tests:
51 |
52 | ```text
53 | $ crystal spec
54 | .
55 |
56 | Finished in 0.82 milliseconds
57 | 1 examples, 0 failures, 0 errors, 0 pending
58 | ```
59 |
60 | Awesome! We pass! Let’s write another test, and see what happens:
61 |
62 | ```ruby
63 | require "./spec_helper"
64 |
65 | describe Fizzbuzz do
66 | it "shouldn't divide 1 by 3" do
67 | div_by_three(1).should eq(false)
68 | end
69 |
70 | it "should divide 3 by 3" do
71 | div_by_three(3).should eq(true)
72 | end
73 | end
74 | ```
75 |
76 | Run it!
77 |
78 | ```ruby
79 | $ crystal spec
80 |
81 | .F
82 |
83 | Failures:
84 |
85 | 1) Fizzbuzz should divide 3 by 3
86 | Failure/Error: div_by_three(3).should eq(true)
87 |
88 | expected: true
89 | got: false
90 |
91 | # spec/fizzbuzz_spec.cr:9
92 |
93 | Finished in 0.83 milliseconds
94 | 2 examples, 1 failures, 0 errors, 0 pending
95 |
96 | Failed examples:
97 |
98 | crystal spec ./spec/fizzbuzz_spec.cr:8 # Fizzbuzz should divide 3 by 3
99 | ```
100 |
101 | We have 1 failure. Let’s make this pass.
102 |
103 | ```ruby
104 | require "./fizzbuzz/*"
105 |
106 | def div_by_three(n)
107 | if n % 3 == 0
108 | true
109 | else
110 | false
111 | end
112 | end
113 | ```
114 |
115 | Run it.
116 |
117 | ```ruby
118 | $ crystal spec
119 |
120 | ..
121 |
122 | Finished in 0.61 milliseconds
123 | 2 examples, 0 failures, 0 errors, 0 pending
124 | ```
125 |
126 | Awesome! This shows off how `else` work, as well. It’s probably what you expected. Go ahead and try to refactor this into a one-liner.
127 |
128 | Done? How’d you do? Here’s mine:
129 |
130 | ```ruby
131 | def div_by_three(n)
132 | n % 3 == 0
133 | end
134 | ```
135 |
136 | Remember, the value of the last expression gets returned.
137 |
138 | Okay, now try to TDD out the `div_by_five` and `div_by_fifteen` methods. They should work the same way, but this will let you get practice actually writing it out. Once you see this, you’re ready to advance:
139 |
140 | ```text
141 | $ crystal spec -v
142 |
143 | Fizzbuzz
144 | shouldn't divide 1 by 3
145 | should divide 3 by 3
146 | shouldn't divide 8 by 5
147 | should divide 5 by 5
148 | shouldn't divide 13 by 15
149 | should divide 15 by 15
150 |
151 | Finished in 0.61 milliseconds
152 | 6 examples, 0 failures, 0 errors, 0 pending
153 | ```
154 |
155 | Okay! Let’s talk about the main program now. We’ve got the tools to build FizzBuzz, let’s make it work. First thing we need to do is print out all the numbers from one to 100. It’s easy!
156 |
157 | ```ruby
158 | 100.times do |num|
159 | puts num
160 | end
161 | ```
162 |
163 | Step one: print **something** 100 times. If you run this via `crystal build src/fizzbuzz.cr && ./fizzbuzz` you should see `num` printed 100 times. Note that our tests didn’t actually run. Not only are they not run, they’re actually not even in the executable:
164 |
165 | Now we can put the two together:
166 |
167 | ```ruby
168 | 100.times do |num|
169 | answer = ""
170 |
171 | if div_by_fifteen num
172 | answer = "FizzBuzz"
173 | elsif div_by_three num
174 | answer = "Fizz"
175 | elsif div_by_five num
176 | answer = "Buzz"
177 | else
178 | answer = num
179 | end
180 |
181 | puts answer
182 | end
183 | ```
184 |
185 | Because the `if` returns a value, we could also do something like this:
186 |
187 | ```ruby
188 | (1..100).each do |num|
189 | answer = if div_by_fifteen num
190 | "FizzBuzz"
191 | elsif div_by_three num
192 | "Fizz"
193 | elsif div_by_five num
194 | "Buzz"
195 | else
196 | num
197 | end
198 |
199 | puts answer
200 | end
201 | ```
202 |
203 | Notice that we also changed `100.times` to `(1..100).each`, to make `num` go from 1 to 100 instead of from 0 to 99.
204 |
205 | Try running it.
206 |
207 | Awesome! We’ve conquered FizzBuzz.
208 |
209 |
--------------------------------------------------------------------------------