Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Nikita Shilnikov Me

Slide 3

Slide 3 text

I work on

Slide 4

Slide 4 text

Typed Ruby

Slide 5

Slide 5 text

There are no types in Ruby

Slide 6

Slide 6 text

Everything is an object

Slide 7

Slide 7 text

Objects interact via message passing

Slide 8

Slide 8 text

1 + 2

Slide 9

Slide 9 text

1 :+,2 message target

Slide 10

Slide 10 text

1.methods

Slide 11

Slide 11 text

=> [:%, :&, :*, :+, :-, :/, :<, :>, :^, :|, :~, :-@, :**, :<=>, :<<, :>>, :<=, :>=, :==, :===, :[], :inspect, :size, :succ, :to_int, :to_s, :to_i, :to_f, :next, :div, :upto, :chr, :ord, :coerce, :divmod, :fdiv, :modulo, :remainder, :abs, :magnitude, :integer?, :floor, :ceil, :round, :truncate, :odd?, :even?, :downto, :times, :pred, :bit_length, :digits, :to_r, :numerator, :denominator, :rationalize, :gcd, :lcm, :gcdlcm, :+@, :eql?, :singleton_method_added, :i, :real?, :zero?, :nonzero?, :finite?, :infinite?, :step, :positive?, :negative?, :quo, :arg, :rectangular, :rect, :polar, :real, :imaginary, :imag, :abs2, :angle, :phase, :conjugate, :conj, :to_c, :between?, :clamp, :instance_of?, :kind_of?, :is_a?, :tap, :public_send, :remove_instance_variable, :public_method, :singleton_method, :instance_variable_set, :define_singleton_method, :method, :extend, :to_enum, :enum_for, :=~, :!~, :respond_to?, :freeze, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variable_get, :instance_variables, :instance_variable_defined?, :!, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]

Slide 12

Slide 12 text

=> [:%, :&, :*, :+, :-, :/, :<, :>, :^, :|, :~, :-@, :**, :<=>, :<<, :>>, :<=, :>=, :==, :===, :[], :inspect, :size, :succ, :to_int, :to_s, :to_i, :to_f, :next, :div, :upto, :chr, :ord, :coerce, :divmod, :fdiv, :modulo, :remainder, :abs, :magnitude, :integer?, :floor, :ceil, :round, :truncate, :odd?, :even?, :downto, :times, :pred, :bit_length, :digits, :to_r, :numerator, :denominator, :rationalize, :gcd, :lcm, :gcdlcm, :+@, :eql?, :singleton_method_added, :i, :real?, :zero?, :nonzero?, :finite?, :infinite?, :step, :positive?, :negative?, :quo, :arg, :rectangular, :rect, :polar, :real, :imaginary, :imag, :abs2, :angle, :phase, :conjugate, :conj, :to_c, :between?, :clamp, :instance_of?, :kind_of?, :is_a?, :tap, :public_send, :remove_instance_variable, :public_method, :singleton_method, :instance_variable_set, :define_singleton_method, :method, :extend, :to_enum, :enum_for, :=~, :!~, :respond_to?, :freeze, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variable_get, :instance_variables, :instance_variable_defined?, :!, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]

Slide 13

Slide 13 text

Duck typing

Slide 14

Slide 14 text

a + b

Slide 15

Slide 15 text

a.respond_to?(:+)

Slide 16

Slide 16 text

Consequences

Slide 17

Slide 17 text

foo.size # => 8

Slide 18

Slide 18 text

foo.size # => 8

Slide 19

Slide 19 text

A real world example

Slide 20

Slide 20 text

name = params[:name]

Slide 21

Slide 21 text

name = params[:name] String

Slide 22

Slide 22 text

name # => true

Slide 23

Slide 23 text

name # => true

Slide 24

Slide 24 text

But true is not a String!

Slide 25

Slide 25 text

name.camelize

Slide 26

Slide 26 text

new_user = User.new(params.permit(:name)) new_user.name # => ?

Slide 27

Slide 27 text

"t"

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Problems 1. Using models for coercion is questionable. 2. Coercion logic is opaque. 3. Types are defined by database columns. 4. Unhappy paths end with an error (better) or a data corruption (worse).

Slide 31

Slide 31 text

Duck typing is unreliable when working with data

Slide 32

Slide 32 text

Duck typing is unreliable when working with data

Slide 33

Slide 33 text

Duck typing is unreliable when working with data

Slide 34

Slide 34 text

What is a type?

Slide 35

Slide 35 text

What is a type? 1. A category of people or things having common characteristics. 2. A person or thing exemplifying the ideal or defining characteristics of something (prototype). 3. Characters or letters that are printed or shown on a screen.

Slide 36

Slide 36 text

What is a type? 1. A category of people or things having common characteristics. 2. A person or thing exemplifying the ideal or defining characteristics of something (prototype). 3. Characters or letters that are printed or shown on a screen.

Slide 37

Slide 37 text

A category of people or things having common characteristics 1. Natural numbers (1, 2, 3, 4, ...). 2. Emojis ( ...). 3. Vertebrates (dog, cat, horse, ...).

Slide 38

Slide 38 text

Types in programming Also called data types. Play a major role in statically typed languages.

Slide 39

Slide 39 text

Types in static languages

Slide 40

Slide 40 text

–Haskell Wiki “Types are how you describe the data your program will work with.”

Slide 41

Slide 41 text

–Type-Driven Development with Idris “Types are a means of classifying (data) values.”

Slide 42

Slide 42 text

data Planet = Earth

Slide 43

Slide 43 text

data Planet = Earth keyword

Slide 44

Slide 44 text

data Planet = Earth type name

Slide 45

Slide 45 text

data Planet = Earth value

Slide 46

Slide 46 text

data Planet = Earth | Mars | ... values

Slide 47

Slide 47 text

isEarth Earth = True isEarth x = False

Slide 48

Slide 48 text

isEarth Earth = True isEarth x = False

Slide 49

Slide 49 text

def earth?(planet) planet == :earth end

Slide 50

Slide 50 text

def earth?(planet) planet.public_send(:==, :earth) end

Slide 51

Slide 51 text

earth?(:earth) # => true earth?(:mars) # => false earth?(:sun) # => false earth?(1) # => false

Slide 52

Slide 52 text

isEarth 1

Slide 53

Slide 53 text

isEarth :: Planet -> Bool

Slide 54

Slide 54 text

isEarth :: Planet -> Bool

Slide 55

Slide 55 text

1. Early sanity-check, the compiler will check your code for consistency. 2. Advanced compilers can check a program for correctness. 3. Better semantics and docs. 4. Better introspection/IDE integration. 5. A lot of performance optimizations can be done during the compile phase. Pros:

Slide 56

Slide 56 text

1. Compilation takes time. 2. Types require more typing making code more verbose and less flexible. Cons:

Slide 57

Slide 57 text

Statically typed languages have type inference

Slide 58

Slide 58 text

val firstName = "Adam"

Slide 59

Slide 59 text

Dynamically typed languages have type annotations

Slide 60

Slide 60 text

def is_earth(planet: Planet): ...

Slide 61

Slide 61 text

Types in Ruby

Slide 62

Slide 62 text

Duck typing has nothing to do with types

Slide 63

Slide 63 text

Duck typing is useless for describing data

Slide 64

Slide 64 text

Applications are useless without data

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

dry-validation

Slide 67

Slide 67 text

schema raw input valid data app DB errors

Slide 68

Slide 68 text

outer world application

Slide 69

Slide 69 text

CreateUserSchema = Dry::Validation.JSON do required(:name).filled(:str?) end

Slide 70

Slide 70 text

CreateUserSchema.call(params)

Slide 71

Slide 71 text

CreateUserSchema.call('name' => 'John').output # => { name: 'John' }

Slide 72

Slide 72 text

CreateUserSchema.call('name' => true).output # => { name: ["must be a string"] }

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

something.blank?

Slide 75

Slide 75 text

dry-types

Slide 76

Slide 76 text

—a type system built for coercion and data validation; —formerly known as dry-data; —initially was created as a replacement for virtus; —is a direct dependency of dry-validation, ROM, and hanami-model. dry-types

Slide 77

Slide 77 text

Adding types to Ruby

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

module Types include Dry::Types.module end

Slide 80

Slide 80 text

int = Types::Strict::Int int[3] # => 3

Slide 81

Slide 81 text

int = Types::Strict::Int int[3] # => 3 int['3'] # => '3' violates constraints # (type?(Integer, '3') failed)

Slide 82

Slide 82 text

int = Types::Coercible::Int int[3] # => 3 int['3'] # => 3

Slide 83

Slide 83 text

Integer('3') nil.to_i # => 0 Integer(nil) # => TypeError ''.to_i # => 0 Integer('') # => ArgumentError

Slide 84

Slide 84 text

Type constraints

Slide 85

Slide 85 text

No content

Slide 86

Slide 86 text

Types are like sets

Slide 87

Slide 87 text

No content

Slide 88

Slide 88 text

Types::Age = Types::Strict::Int.constrained(gteq: 18)

Slide 89

Slide 89 text

Case equality

Slide 90

Slide 90 text

case value when Types::Strict::Int then :number when Types::Strict::String then :str end

Slide 91

Slide 91 text

Types have algebra*

Slide 92

Slide 92 text

Types::Strict::Int | Types::Strict::String

Slide 93

Slide 93 text

dry-struct

Slide 94

Slide 94 text

class User < Dry::Struct attribute :name, Types::Strict::String attribute :age, Types::Age end

Slide 95

Slide 95 text

Structs are meant to be valid

Slide 96

Slide 96 text

module ProfileEvents class PasswordChanged < Dry::Struct attribute :user_id, Types::UUID attribute :password, Types::Password end class EmailChanged < Dry::Struct attribute :user_id, Types::UUID attribute :email, Types::Email end Updated = PasswordChanged | EmailChanged end

Slide 97

Slide 97 text

module ProfileEvents class PasswordChanged < Dry::Struct attribute :user_id, Types::UUID attribute :password, Types::Password end class EmailChanged < Dry::Struct attribute :user_id, Types::UUID attribute :email, Types::Email end Updated = PasswordChanged | EmailChanged end

Slide 98

Slide 98 text

module ProfileEvents class PasswordChanged < Dry::Struct attribute :user_id, Types::UUID attribute :password, Types::Password end class EmailChanged < Dry::Struct attribute :user_id, Types::UUID attribute :email, Types::Email end Updated = PasswordChanged | EmailChanged end

Slide 99

Slide 99 text

Custom types

Slide 100

Slide 100 text

module Types include Dry::Types.module end Types.Instance(Customer) Types.Constructor(Geopoint) { |lon, lat| "POINT (#{ lat } #{ lon })" } Types.Constant(:only_allowed)

Slide 101

Slide 101 text

What’s inside?

Slide 102

Slide 102 text

Types are values

Slide 103

Slide 103 text

Types::Int.constrained(gteq: 18) == Types::Int.constrained(gteq: 18)

Slide 104

Slide 104 text

age = Types::Strict::Int.constrained(gteq: 18) types = { age => :age } types[Types::Strict::Int.constrained(gteq: 18)] # => :age

Slide 105

Slide 105 text

schema do attribute :company, Types::String attribute :license, Types::PG::JSONB end

Slide 106

Slide 106 text

where { license.contain(enabled: true) } # WHERE "companies"."license" @> '{"enabled":true}'::jsonb)

Slide 107

Slide 107 text

TypeExtensions.register(JSONB) do def contain(type, expr, value) sql = wrap(expr).contains(value) Attribute[SQL::Types::Bool].meta(sql_expr: sql) end end

Slide 108

Slide 108 text

ROM 4.0 is out

Slide 109

Slide 109 text

Types have metadata

Slide 110

Slide 110 text

first_name = Types::Strict::String. constrained(min_size: 3) last_name = Types::Strict::String. constrained(min_size: 3)

Slide 111

Slide 111 text

first_name = Types::Strict::String. constrained(min_size: 3). meta(name: 'first_name') last_name = Types::Strict::String. constrained(min_size: 3). meta(name: 'last_name')

Slide 112

Slide 112 text

Types have ASTs

Slide 113

Slide 113 text

Types::Strict::String.constrained(min_size: 3) # => # options={:rule=># options={:args=>[String]}>, # options={:args=>[3]}>] options={}>} rule=# options={:args=>[String]}>, # options={:args=>[3]}>] options={}> meta={}>

Slide 114

Slide 114 text

int = Types::Strict::Int int.to_ast [:constrained, [[:definition, [Integer, {}]], [:predicate, [:type?, [[:type, Integer], [:input, Undefined]]]], {}]]

Slide 115

Slide 115 text

require 'dry/types/compiler' compiler = Dry::Types::Compiler.new(Dry::Types) int = Types::Strict::Int int == compiler.(int.to_ast)

Slide 116

Slide 116 text

Age (Int >= 18) rand(50) + 18

Slide 117

Slide 117 text

—data generators; —serializers; —data transformers; —auto-docs; —???

Slide 118

Slide 118 text

Types make life easier

Slide 119

Slide 119 text

Types make life easier So use them

Slide 120

Slide 120 text

Recap — Duck typing is neither about ducks nor typing. — Types are sets of possible values that shape the data. — In dry-types types are ordinary objects that can be build and composed. — Types can even be decomposed allowing you to build new abstractions on top of them.

Slide 121

Slide 121 text

Do not use .blank?

Slide 122

Slide 122 text

It’s not about Haskell

Slide 123

Slide 123 text

1.0 is almost there

Slide 124

Slide 124 text

Links dry-rb.org/gems/dry-types github.com/dry-rb/dry-types discourse.dry-rb.org gitter.im/dry-rb/chat

Slide 125

Slide 125 text

Thank you! github.com/flash-gordon @NikitaShilnikov