Ducks and Monads

Wonders of Ruby Types

Hey, I'm Igor


I'm a polyglot programmer at

I don't trust myself

There are four reasons I don't trust myself

undefined is not a function

N + 1

I mess up business logic

I forget to close a bracket

I forget some details

I have to double-check my code

New hobby: finding the right tools

Tools to write type-safe Ruby code

Dynamic typing is not an excuse to be reckless

2 out of 10 most common errors are about types

Silent data corruption:
JSON in Rails

      value = "{\"foo\":\"bar\"}"

      # Rails 4
      my_model.value = value # => { foo: "bar" }

      # Rails 5
      my_model.value = value # => "{\"foo\":\"bar\"}"


We have to use the right libraries

Writing code is easier than fixing it

Every object has a type

What's a type, anyway?

  • Syntactic
  • Representation
  • Representation and behavior
  • Value space
  • Value space and behavior

A type is a set of values which a variable can possess and a set of functions that one can apply to these values.

We learn to consider all possible values

nil does not belong to every type

ActiveSupport teaches us to be reckless

blank? and present?
are bad for your code

We have to think about design of our data

Being careful is not enough

dry-types is a simple and extendable type system for Ruby

Data constructors

      Types::Strict::String["foo"] # => "foo"
      Types::Strict::String[nil] # => Dry::Types::ConstraintError:
      # nil violates constraints

      Types::Optional::Strict::String[nil] # => nil


Complex definitions

      IntArray = Types::Strict::Array.of(Types::Strict::Integer)

      Enum = Types::Strict::String.enum('foo', 'bar')

      Timestamp = Types::Strict::String | Types::Strict::Time

      MyHash = Types::Strict::Hash.schema(
        foo: Types.Instance(MyClass),
        bar: Types::Strict::Float


dry-types won't change your architecture

Trying out dry-types: Hanami

Enhancing your application with dry-types

  • Describe your data using dry-types
  • Use those definitions in constructors
  • Use them in setters
  • (hard) Use dry-struct for domain models

Three steps to type safety

  • Stop using ActiveSupport
  • Consider all possible values
  • Use strict constructors

Expressing errors with types

Errors are not that exceptional

Types can facilitate error handling

Monads in Ruby

Monads are similar to musical instruments

A monad is a result object with a few rules

Result (Either) monad

type Result a b = Failure a | Success b

Using Result

      require 'dry/monads/result'

      include Dry::Monads::Result

      Success(1).success # => 1
      Success(1).success? # => true

      Failure("abc").failure # => "abc"
      Failure("abc").failure? # => true


Computations: bind

      Success(1).bind do |value|
        if value.positive?
          Success(value + 3)
      end # => Failure("Welp")


Handling errors: or

      Failure(:not_found).or do |error|
        case error
        when :not_found
      end # => Failure(:db_error)


Non-monadic computations: fmap

      Success(1).fmap do |value|
        "The value is #{value}"
      end # => Success("The value is 1")

      Failure(1).fmap do |value|
        "The value is #{value}"
      end # => Failure(1)


Escaping the context:

      Success(-5).bind do |x|
        if x.positive?
          Failure("Something went wrong")
      end.value_or { nil } # => nil



        "Something went wrong"
      ).value_or(&:itself) # => "Something went wrong"


Do notation

      class Operation
        include Dry::Monads::Do.for(:call)
        include Dry::Monads::Result

        def call(name)
          user = yield find_by_name(name)

          contract = yield find_contract(user)

            amount: contract.amount + 100



Types help us design our applications

ActiveSupport may be bad for our code

Use dry-types for a type-safe application design

Monads help us handle errors gracefully

Useful links and references

Thank you! ❤

I would love to hear from you
[email protected]