--- name: Crystal filename: learncrystal.cr contributors: - ["Vitalii Elenhaupt", "http://veelenga.com"] - ["Arnaud Fernandés", "https://github.com/TechMagister/"] - ["Valentin Baca", "https://github.com/valbaca/"] --- ```crystal # This is a comment # Everything is an object nil.class #=> Nil 100.class #=> Int32 true.class #=> Bool # Falsey values are: nil, false and null pointers !nil #=> true : Bool !false #=> true : Bool !0 #=> false : Bool # Integers 1.class #=> Int32 # Five signed integer types 1_i8.class #=> Int8 1_i16.class #=> Int16 1_i32.class #=> Int32 1_i64.class #=> Int64 1_i128.class #=> Int128 # Five unsigned integer types 1_u8.class #=> UInt8 1_u16.class #=> UInt16 1_u32.class #=> UInt32 1_u64.class #=> UInt64 1_u128.class #=> UInt128 2147483648.class #=> Int64 9223372036854775808.class #=> UInt64 # Binary numbers 0b1101 #=> 13 : Int32 # Octal numbers 0o123 #=> 83 : Int32 # Hexadecimal numbers 0xFE012D #=> 16646445 : Int32 0xfe012d #=> 16646445 : Int32 # Floats 1.0.class #=> Float64 # There are two floating point types 1.0_f32.class #=> Float32 1_f32.class #=> Float32 1e10.class #=> Float64 1.5e10.class #=> Float64 1.5e-7.class #=> Float64 # Chars use 'a' pair of single quotes 'a'.class #=> Char # Chars are 32-bit unicode 'あ' #=> 'あ' : Char # Unicode codepoint '\u0041' #=> 'A' : Char # Strings use a "pair" of double quotes "s".class #=> String # Strings are immutable s = "hello, " #=> "hello, " : String s.object_id #=> 134667712 : UInt64 s += "Crystal" s #=> "hello, Crystal" : String s.object_id #=> 142528472 : UInt64 # Supports interpolation "sum = #{1 + 2}" #=> "sum = 3" : String # Multiline string "This is multiline string" #=> "This is\n multiline string" # String with double quotes %(hello "world") #=> "hello \"world\"" # Symbols # Immutable, reusable constants represented internally as Int32 integer value. # They're often used instead of strings to efficiently convey specific, # meaningful values :symbol.class #=> Symbol sentence = :question? # :"question?" : Symbol sentence == :question? #=> true : Bool sentence == :exclamation! #=> false : Bool sentence == "question?" #=> false : Bool # Arrays [1, 2, 3].class #=> Array(Int32) [1, "hello", 'x'].class #=> Array(Char | Int32 | String) # Empty arrays should specify a type [] # Syntax error: for empty arrays use '[] of ElementType' [] of Int32 #=> [] : Array(Int32) Array(Int32).new #=> [] : Array(Int32) # Arrays can be indexed array = [1, 2, 3, 4, 5] #=> [1, 2, 3, 4, 5] : Array(Int32) array[0] #=> 1 : Int32 array[10] # raises IndexError array[-6] # raises IndexError array[10]? #=> nil : (Int32 | Nil) array[-6]? #=> nil : (Int32 | Nil) # From the end array[-1] #=> 5 # With a start index and size array[2, 3] #=> [3, 4, 5] # Or with range array[1..3] #=> [2, 3, 4] # Add to an array array << 6 #=> [1, 2, 3, 4, 5, 6] # Remove from the end of the array array.pop #=> 6 array #=> [1, 2, 3, 4, 5] # Remove from the beginning of the array array.shift #=> 1 array #=> [2, 3, 4, 5] # Check if an item exists in an array array.includes? 3 #=> true # Special syntax for an array of string and an array of symbols %w(one two three) #=> ["one", "two", "three"] : Array(String) %i(one two three) #=> [:one, :two, :three] : Array(Symbol) # There is a special array syntax with other types too, as long as # they define a .new and a #<< method set = Set{1, 2, 3} #=> Set{1, 2, 3} set.class #=> Set(Int32) # The above is equivalent to set = Set(typeof(1, 2, 3)).new #=> Set{} : Set(Int32) set << 1 #=> Set{1} : Set(Int32) set << 2 #=> Set{1, 2} : Set(Int32) set << 3 #=> Set{1, 2, 3} : Set(Int32) # Hashes {1 => 2, 3 => 4}.class #=> Hash(Int32, Int32) {1 => 2, 'a' => 3}.class #=> Hash(Char| Int32, Int32) # Empty hashes must specify a type {} # Syntax Error: for empty hashes use '{} of KeyType => ValueType' {} of Int32 => Int32 # {} : Hash(Int32, Int32) Hash(Int32, Int32).new # {} : Hash(Int32, Int32) # Hashes can be quickly looked up by key hash = {"color" => "green", "number" => 5} hash["color"] #=> "green" hash["no_such_key"] #=> Missing hash key: "no_such_key" (KeyError) hash["no_such_key"]? #=> nil # The type of the returned value is based on all key types hash["number"] #=> 5 : (Int32 | String) # Check existence of keys hash hash.has_key? "color" #=> true # Special notation for symbol and string keys {key1: 'a', key2: 'b'} # {:key1 => 'a', :key2 => 'b'} {"key1": 'a', "key2": 'b'} # {"key1" => 'a', "key2" => 'b'} # Special hash literal syntax with other types too, as long as # they define a .new and a #[]= methods class MyType def []=(key, value) puts "do stuff" end end MyType{"foo" => "bar"} # The above is equivalent to tmp = MyType.new tmp["foo"] = "bar" tmp # Ranges 1..10 #=> Range(Int32, Int32) Range.new(1, 10).class #=> Range(Int32, Int32) # Can be inclusive or exclusive (3..5).to_a #=> [3, 4, 5] (3...5).to_a #=> [3, 4] # Check whether range includes the given value or not (1..8).includes? 2 #=> true # Tuples are a fixed-size, immutable, stack-allocated sequence of values of # possibly different types. {1, "hello", 'x'}.class #=> Tuple(Int32, String, Char) # Access tuple's value by its index tuple = {:key1, :key2} tuple[1] #=> :key2 tuple[2] #=> Error: index out of bounds for Tuple(Symbol, Symbol) (2 not in -2..1) # Can be expanded into multiple variables a, b, c = {:a, 'b', "c"} a #=> :a b #=> 'b' c #=> "c" # Procs represent a function pointer with an optional context (the closure data) # It is typically created with a proc literal proc = ->(x : Int32) { x.to_s } proc.class # Proc(Int32, String) # Or using the new method Proc(Int32, String).new { |x| x.to_s } # Invoke proc with call method proc.call 10 #=> "10" # Control statements if true "if statement" elsif false "else-if, optional" else "else, also optional" end puts "if as a suffix" if true # If as an expression a = if 2 > 1 3 else 4 end a #=> 3 # Ternary if a = 1 > 2 ? 3 : 4 #=> 4 # Case statement cmd = "move" action = case cmd when "create" "Creating..." when "copy" "Copying..." when "move" "Moving..." when "delete" "Deleting..." end action #=> "Moving..." # Loops index = 0 while index <= 3 puts "Index: #{index}" index += 1 end # Index: 0 # Index: 1 # Index: 2 # Index: 3 index = 0 until index > 3 puts "Index: #{index}" index += 1 end # Index: 0 # Index: 1 # Index: 2 # Index: 3 # But the preferable way is to use each (1..3).each do |index| puts "Index: #{index}" end # Index: 1 # Index: 2 # Index: 3 # Variable's type depends on the type of the expression # in control statements if a < 3 a = "hello" else a = true end typeof(a) #=> (Bool | String) if a && b # here both a and b are guaranteed not to be Nil end if a.is_a? String a.class #=> String end # Functions def double(x) x * 2 end # Functions (and all blocks) implicitly return the value of the last statement double(2) #=> 4 # Parentheses are optional where the call is unambiguous double 3 #=> 6 double double 3 #=> 12 def sum(x, y) x + y end # Method arguments are separated by a comma sum 3, 4 #=> 7 sum sum(3, 4), 5 #=> 12 # yield # All methods have an implicit, optional block parameter # it can be called with the 'yield' keyword def surround puts '{' yield puts '}' end surround { puts "hello world" } # { # hello world # } # You can pass a block to a function # "&" marks a reference to a passed block def guests(&block) block.call "some_argument" end # You can pass a list of arguments, which will be converted into an array # That's what splat operator ("*") is for def guests(*array) array.each { |guest| puts guest } end # If a method returns an array, you can use destructuring assignment def foods ["pancake", "sandwich", "quesadilla"] end breakfast, lunch, dinner = foods breakfast #=> "pancake" dinner #=> "quesadilla" # By convention, all methods that return booleans end with a question mark 5.even? # false 5.odd? # true # Also by convention, if a method ends with an exclamation mark, it does # something destructive like mutate the receiver. # Some methods have a ! version to make a change, and # a non-! version to just return a new changed version fruits = ["grapes", "apples", "bananas"] fruits.sort #=> ["apples", "bananas", "grapes"] fruits #=> ["grapes", "apples", "bananas"] fruits.sort! #=> ["apples", "bananas", "grapes"] fruits #=> ["apples", "bananas", "grapes"] # However, some mutating methods do not end in ! fruits.shift #=> "apples" fruits #=> ["bananas", "grapes"] # Define a class with the class keyword class Human # A class variable. It is shared by all instances of this class. @@species = "H. sapiens" # An instance variable. Type of name is String @name : String # Basic initializer # Assign the argument to the "name" instance variable for the instance # If no age given, we will fall back to the default in the arguments list. def initialize(@name, @age = 0) end # Basic setter method def name=(name) @name = name end # Basic getter method def name @name end # The above functionality can be encapsulated using the propery method as follows property :name # Getter/setter methods can also be created individually like this getter :name setter :name # A class method uses self to distinguish from instance methods. # It can only be called on the class, not an instance. def self.say(msg) puts msg end def species @@species end end # Instantiate a class jim = Human.new("Jim Halpert") dwight = Human.new("Dwight K. Schrute") # Let's call a couple of methods jim.species #=> "H. sapiens" jim.name #=> "Jim Halpert" jim.name = "Jim Halpert II" #=> "Jim Halpert II" jim.name #=> "Jim Halpert II" dwight.species #=> "H. sapiens" dwight.name #=> "Dwight K. Schrute" # Call the class method Human.say("Hi") #=> print Hi and returns nil # Variables that start with @ have instance scope class TestClass @var = "I'm an instance var" end # Variables that start with @@ have class scope class TestClass @@var = "I'm a class var" end # Variables that start with a capital letter are constants Var = "I'm a constant" Var = "can't be updated" # Error: already initialized constant Var # Class is also an object in Crystal. So a class can have instance variables. # Class variable is shared among the class and all of its descendants. # base class class Human @@foo = 0 def self.foo @@foo end def self.foo=(value) @@foo = value end end # derived class class Worker < Human end Human.foo #=> 0 Worker.foo #=> 0 Human.foo = 2 #=> 2 Worker.foo #=> 0 Worker.foo = 3 #=> 3 Human.foo #=> 2 Worker.foo #=> 3 module ModuleExample def foo "foo" end end # Including modules binds their methods to the class instances # Extending modules binds their methods to the class itself class Person include ModuleExample end class Book extend ModuleExample end Person.foo # => undefined method 'foo' for Person:Class Person.new.foo # => 'foo' Book.foo # => 'foo' Book.new.foo # => undefined method 'foo' for Book # Exception handling # Define new exception class MyException < Exception end # Define another exception class MyAnotherException < Exception; end ex = begin raise MyException.new rescue ex1 : IndexError "ex1" rescue ex2 : MyException | MyAnotherException "ex2" rescue ex3 : Exception "ex3" rescue ex4 # catch any kind of exception "ex4" end ex #=> "ex2" ``` ## Additional resources - [Official Documentation](https://crystal-lang.org/)