Slide 1

Slide 1 text

Crystal for Rubists Rubyists February, 29 2020

Slide 2

Slide 2 text

Igor Alexandrov •JetRockets Co-Founder •CTO •SmallTalk => Ruby •Ruby => Crystal

Slide 3

Slide 3 text

Everything begins and ends in the mind

Slide 4

Slide 4 text

– favourite client “We need an ability to upload data about all real estate loans in US”

Slide 5

Slide 5 text

– JetRockets “Ok… But what do you mean by “All”?”

Slide 6

Slide 6 text

– favourite client “2.5 Gb CSV files that will be updated every day”

Slide 7

Slide 7 text

• ruby is slow (to be honest – not); • we will hit memory leaks again; • why not try something new?

Slide 8

Slide 8 text

We need new technology!

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Crystal Language • Syntax inspired by Ruby • Compiled (based on LLVM) • With type inference and union types • With concurrency and parallelism • Does not support Windows

Slide 14

Slide 14 text

2014 1995 19 years

Slide 15

Slide 15 text

Types

Slide 16

Slide 16 text

Type Inference (Ruby) if some_condition a = 1 a.abs else a = "hello" a.upcase end a.upcase

Slide 17

Slide 17 text

Type Inference (Ruby) if some_condition a = 1 a.abs # will never fail else a = "hello" a.upcase # will never fail too end a.upcase # can fail at runtime

Slide 18

Slide 18 text

Type Inference (Ruby) if some_condition a = 1 a.abs # will never fail else a = "hello" a.upcase # will never fail too end a.upcase # can fail at runtime

Slide 19

Slide 19 text

Type Inference (Crystal) if some_condition a = 1 a.abs else a = "hello" a.upcase end a.upcase

Slide 20

Slide 20 text

Type Inference (Crystal) if some_condition a = 1 # a is Int32 a.abs else a = "hello" # a is String a.upcase end a.upcase # a is Int32 or String

Slide 21

Slide 21 text

Type Inference (Crystal) if some_condition a = 1 # a is Int32 a.abs else a = "hello" # a is String a.upcase end a.upcase # a is Int32 or String

Slide 22

Slide 22 text

Type Composition struct Int32OrString { int type_id; union { int int_value; char[] string_value; } data; }

Slide 23

Slide 23 text

Type Composition if something_that_you_know_will_happen a = 1 end a.abs # here a is Int32 or Nil

Slide 24

Slide 24 text

Type Composition if something_that_you_know_will_happen a = 1 end # You can guarantee that a is not a nil a.not_nil!.abs

Slide 25

Slide 25 text

Type Composition class Object def not_nil! self end end class Nil def not_nil! raise NilAssertionError.new end end a = some_condition ? 1 : nil a.not_nil!.abs

Slide 26

Slide 26 text

Type Composition a = 1 while some_condition a # here a is actually Int32 or String a = false # here a is Bool a = "hello" # here a is String a.size # ok, a is String end a # here a is Int32 or String

Slide 27

Slide 27 text

Type Filter a = some_condition ? 1 : nil # a is Int32 or Nil if a a.abs # a is Int32 end # compiler assumes the variable is not nil # inside the if branch

Slide 28

Slide 28 text

Types in Crystal • Raise on compile-time if types mismatch • Type Inference • Type Composition • Type Filter

Slide 29

Slide 29 text

Concurrency and Parallelism

Slide 30

Slide 30 text

Concurrency Sandwich example • you’re chopping an onion, putting it to fry.; • while it’s being fried, you’re chopping a tomato; • you’re not doing all of those things at the same time;

Slide 31

Slide 31 text

Is Ruby concurrent?

Slide 32

Slide 32 text

Indeed!

Slide 33

Slide 33 text

Parallelism Sandwich example • you have at least four arms (2 pairs); • you’re chopping an onion, putting it to fry; • while you’re cutting an onion, you’re also chopping a tomato; • you are doing at least two things at the same time;

Slide 34

Slide 34 text

Does Ruby (cRuby) has parallelism?

Slide 35

Slide 35 text

Nope…

Slide 36

Slide 36 text

Parallelism Crystal 0.31

Slide 37

Slide 37 text

Metaprogramming

Slide 38

Slide 38 text

Metaprogramming • no code modifications at runtime • no #send, #method_missing, #instance_eval… • Crystal has macros

Slide 39

Slide 39 text

Simple Example (Ruby) class Point def initialize(x, y) @x, @y = x, y end def x @x end def y @y end end

Slide 40

Slide 40 text

Simple Example (Ruby) class Point def initialize(x, y) @x, @y = x, y end getter :x, :y end

Slide 41

Slide 41 text

Simple Example (Ruby) def getter(*names) names.each do |name| class_eval "def #{name}; @#{name}; end" end end class Point def initialize(x, y) @x, @y = x, y end getter :x, :y end

Slide 42

Slide 42 text

Simple Example (Ruby) class Point def initialize(x, y) @x, @y = x, y end attr_reader :x, :y end

Slide 43

Slide 43 text

Simple Example (Crystal) class Point def initialize(@x : Int32, @y : Int32) end def x @x end def y @y end end

Slide 44

Slide 44 text

Simple Example (Crystal) macro attr_reader def x @x end end class Point def initialize(@x : Int32, @y : Int32) end attr_reader def y @y end end

Slide 45

Slide 45 text

Simple Example (Crystal) macro attr_reader(*names) {% for name in names %} def {{name}} @{{name}} end {% end %} end class Point def initialize(@x : Int32, @y : Int32) end attr_reader x, y end

Slide 46

Slide 46 text

Simple Example (Crystal) macro attr_reader(*names) {% for name in names %} def {{name}} @{{name}} end {% end %} end class Point def initialize(@x : Int32, @y : Int32) end attr_reader x, y # this is not an error end

Slide 47

Slide 47 text

Simple Example (Crystal) macro attr_reader(*names) {% for name in names %} def {{name}} @{{name}} end {% end %} end class Point def initialize(@x : Int32, @y : Int32) end attr_reader :x, :y end

Slide 48

Slide 48 text

Simple Example (Crystal) class Point def initialize(@x : Int32, @y : Int32) end def :x @:x end def :y @:y end end

Slide 49

Slide 49 text

Simple Example (Crystal) macro attr_reader(*names) {% for name in names %} def {{name.id}} @{{name.id}} end {% end %} end class Point def initialize(@x : Int32, @y : Int32) end attr_reader :x, :y end

Slide 50

Slide 50 text

How Macros Works? • AST nodes are passed as arguments • These are processed to produce a string • The string is parsed to a valid expression • The expression is “pasted” into the program

Slide 51

Slide 51 text

Shrine Example (Ruby) class Uploader < Shrine plugin :determine_mime_type, analyzer: :file end

Slide 52

Slide 52 text

Shrine Example (Ruby) def plugin(plugin, *args, **kwargs, &block) plugin = Plugins.load_plugin(plugin) if plugin.is_a?(Symbol) Plugins.load_dependencies(plugin, self, *args, **kwargs, &block) self.include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods) self.extend(plugin::ClassMethods) if defined?(plugin::ClassMethods) self::UploadedFile.include(plugin::FileMethods) if defined?(plugin::FileMethods) self::UploadedFile.extend(plugin::FileClassMethods) if defined?(plugin::FileClassMethods) self::Attachment.include(plugin::AttachmentMethods) if defined?(plugin::AttachmentMethods) self::Attachment.extend(plugin::AttachmentClassMethods) if defined?(plugin::AttachmentClassMethods) self::Attacher.include(plugin::AttacherMethods) if defined?(plugin::AttacherMethods) self::Attacher.extend(plugin::AttacherClassMethods) if defined?(plugin::AttacherClassMethods) Plugins.configure(plugin, self, *args, **kwargs, &block) plugin end

Slide 53

Slide 53 text

– From documentation “Crystal does not support #include and #extend for adding modules to other modules or to create new class or instance methods at runtime.”

Slide 54

Slide 54 text

Shrine Example (Crystal) macro load_plugin(plugin, **options) {% plugin = plugin.resolve %} {% PLUGINS << {decl: plugin, options: options} %} {% if plugin.constant(:InstanceMethods) %} include {{plugin.constant(:InstanceMethods)}} {% end %} {% if plugin.constant(:ClassMethods) %} extend {{plugin.constant(:ClassMethods)}} {% end %} end

Slide 55

Slide 55 text

Shrine Example (Crystal) macro load_plugin(plugin, **options) # ... {% if plugin.constant(:FileClassMethods) %} class UploadedFile < Shrine::UploadedFile extend {{plugin.constant(:FileClassMethods)}} end {% end %} {% if plugin.constant(:FileMethods) %} class UploadedFile < Shrine::UploadedFile include {{plugin.constant(:FileMethods)}} end {% end %} end

Slide 56

Slide 56 text

Shrine Example (Crystal) class Uploader < Shrine load_plugin( Shrine::Plugins::DetermineMimeType, analyzer: Shrine::Plugins::DetermineMimeType::Tools::File ) finalize_plugins! end

Slide 57

Slide 57 text

Another Shrine Example (Ruby) class Shrine module ClassMethods def inherited(subclass) # ... file_class = Class.new(self::Attacher) file_class.shrine_class = subclass subclass.const_set(:Attacher, file_class) # ... end end end

Slide 58

Slide 58 text

Another Shrine Example (Ruby) class Shrine module ClassMethods def inherited(subclass) # ... file_class = Class.new(self::Attacher) file_class.shrine_class = subclass subclass.const_set(:Attacher, file_class) # ... end end end

Slide 59

Slide 59 text

Another Shrine Example (Crystal) class Shrine macro inherited # Reopen the class... class Attacher < Shrine::Attacher def self.shrine_class {{ @type }} end end end end

Slide 60

Slide 60 text

Another Shrine Example (Crystal) class Uploader < Shrine plugin :determine_mime_type, analyzer: :file end puts Uploader::Attacher.shrine_class # => Uploader

Slide 61

Slide 61 text

Macros Benefits • Macros gets evaluated at compile time (not a runtime) • Less chances to get a runtime error • No performance downgrades • Compiler can even perform an optimizations

Slide 62

Slide 62 text

Macros Сons • Extremely hard to debug • Extremely hard to debug! • Only YOU can understand your macros • If you can do it without macros – do it :)

Slide 63

Slide 63 text

Nice Macro Trick a = 1 pp! a # => "a # => 1" pp! [1, 2, 3].map(&.to_s) # => "[1, 2, 3].map(&.to_s) # => ["1", "2", "3"]"

Slide 64

Slide 64 text

Community?

Slide 65

Slide 65 text

What We Like in Ruby?

Slide 66

Slide 66 text

Of course – integrity

Slide 67

Slide 67 text

Ruby Community • Everything is on GitHub • One spec library (yes, I’ve never used minitest) • One code formatting rules (I recently discovered Rufo) • One deployment tool (who ever used Mina?) • One web framework :(

Slide 68

Slide 68 text

Crystal Community • Young • A lot of people came from Ruby • Even more “standardised”

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

Tooling • web frameworks (Kemal, Amber, Lucky); • background processing (sidekiq, it does not eat memory); • specs (Spec, Spectator); • ORMs (Clear, Jennifer.cr);

Slide 73

Slide 73 text

Tooling • a lot is still to be done; • ability to make your OpenSource commitment; • we maintain Shrine.cr; • https://github.com/jetrockets/shrine.cr • https://jetrockets.github.io/shrine.cr

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

Documentation

Slide 76

Slide 76 text

Documentation (Ruby)

Slide 77

Slide 77 text

YARD??? # Converts the object into textual markup given a specific `format` # (defaults to `:html`) # # == Parameters: # format:: # A Symbol declaring the format to convert the object to. This # can be `:text` or `:html`. # # == Returns: # A string representing the object in a specified # format. # def to_format(format = :html) # format the object end

Slide 78

Slide 78 text

Documentation (Crystal) • included into compiler; • $ crystal docs • knows everything about params; • knows all return types; • generates HTML (even with sitemaps); • #exceptions

Slide 79

Slide 79 text

Documentation (Crystal)

Slide 80

Slide 80 text

Documentation (Crystal)

Slide 81

Slide 81 text

Code Style and Analysis • included into compiler; • $ crystal tool format • Ameba – static code analysis • like Rubocop for Ruby • with adequate configuration

Slide 82

Slide 82 text

Ameba # This configuration file was generated by # `ameba --gen-config`on 2020-02-23 13:02:10 UTC using # Ameba version 0.11.0. # The point is for the user to remove these configuration # records one by one as the reported problems are removed # from the code base. # Problems found: 1 # Run `ameba --only Lint/ShadowingOuterLocalVar` for details Lint/ShadowingOuterLocalVar: Description: Disallows the usage of the same name as outer local variables for block or proc arguments. Enabled: true Severity: Warning Excluded: - spec/lib/uploaded_file_spec.cr

Slide 83

Slide 83 text

Ruby => Crystal?

Slide 84

Slide 84 text

Convert Ruby code to Crystal? • https://github.com/DocSpring/ruby_crystal_codemod; • ruby gem; • fork of Rufo (an alternative code formatter for Ruby); • produces semi-valid Crystal code; • https://github.com/jetrockets/content_disposition.cr; • 70 lines of code :)

Slide 85

Slide 85 text

Links • https://www.crystalforrubyists.com/ • https://github.com/crystal-lang/crystal/wiki/Crystal-Shards- for-Ruby-Gems • https://github.com/veelenga/awesome-crystal