├── 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 | --------------------------------------------------------------------------------