From 919e44f843b4496448078cae8c1184da336c197f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Fernand=C3=A9s?= Date: Sun, 18 Dec 2016 13:40:42 +0100 Subject: [PATCH] [crystal/en] It would be greate to add crystal language :) (#2600) * Add Crystal language * Add Type informations and fix issues * Add missing examples * Add name in contributor list * Update code according to feedback received --- crystal.html.markdown | 553 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 553 insertions(+) create mode 100644 crystal.html.markdown diff --git a/crystal.html.markdown b/crystal.html.markdown new file mode 100644 index 00000000..97375f9b --- /dev/null +++ b/crystal.html.markdown @@ -0,0 +1,553 @@ +--- +language: crystal +filename: learncrystal.cr +contributors: + - ["Vitalii Elenhaupt", "http://veelenga.com"] + - ["Arnaud Fernandés", "https://github.com/TechMagister/"] + +--- + +```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 + +# Four signed integer types +1_i8.class #=> Int8 +1_i16.class #=> Int16 +1_i32.class #=> Int32 +1_i64.class #=> Int64 + +# Four unsigned integer types +1_u8.class #=> UInt8 +1_u16.class #=> UInt16 +1_u32.class #=> UInt32 +1_u64.class #=> UInt64 + +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 + +'a'.class #=> Char + +# Octal codepoint +'\101' #=> 'A' : Char + +# Unicode codepoint +'\u0041' #=> 'A' : Char + +# Strings + +"s".class #=> String + +# Strings are immutable +s = "hello, " #=> "hello, " : String +s.object_id #=> 134667712 : UInt64 +s += "Crystal" #=> "hello, Crystal" : String +s.object_id #=> 142528472 : UInt64 + +# Supports interpolation +"sum = #{1 + 2}" #=> "sum = 3" : String + +# Multiline string +"This is + 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(Int32 | String | Char) + +# Empty arrays should define 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} #=> [1, 2, 3] +set.class #=> Set(Int32) + +# The above is equivalent to +set = Set(typeof(1, 2, 3)).new +set << 1 +set << 2 +set << 3 + +# Hashes + +{1 => 2, 3 => 4}.class #=> Hash(Int32, Int32) +{1 => 2, 'a' => 3}.class #=> Hash(Int32 | Char, Int32) + +# Empty hashes should specify a type +{} # Syntax error +{} of Int32 => Int32 # {} +Hash(Int32, Int32).new # {} + +# 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 + +# 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) + +# Acces tuple's value by its index +tuple = {:key1, :key2} +tuple[1] #=> :key2 +tuple[2] #=> syntax error : Index out of bound + +# 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 litteral +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: 0 +# 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 + +# And 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 +company_name = "Dunder Mifflin" +company_name.gsub "Dunder", "Donald" #=> "Donald Mifflin" +company_name #=> "Dunder Mifflin" +company_name.gsub! "Dunder", "Donald" +company_name #=> "Donald Mifflin" + + +# Define a class with the class keyword +class Human + + # A class variable. It is shared by all instances of this class. + @@species = "H. sapiens" + + # 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 attr_accessor 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" # Already initialized constant Var + +# Class is also an object in crystal. So 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](http://crystal-lang.org/)