module Dry::Monads

Common, idiomatic monads for Ruby

@api public

Common, idiomatic monads for Ruby

@api private

Constants

Failure

@see Result::Failure

Invalid

@see Validated::Invalid

None

@see Maybe::None

Some

@see Maybe::Some

Success

@see Result::Success

Traverse

List of default traverse functions for types. It is implicitly used by List#traverse for making common cases easier to handle.

Unit

Unit is a special object you can use whenever your computations don’t return any payload. Previously, if your function ran a side-effect and returned no meaningful value, you had to return things like Success(nil), Success([]), Success({}), Maybe(“”), Success(true) and so forth.

You should use Unit if you wish to return an empty monad.

@example with Result

Success(Unit)
Failure(Unit)

@example with Maybe

Maybe(Unit)
=> Some(Unit)
VERSION

Gem version

Valid

@see Validated::Valid

Attributes

registry[R]

Public Class Methods

Result(error, **options) click to toggle source

Creates a module that has two methods: ‘Success` and `Failure`. `Success` is identical to {Result::Mixin::Constructors#Success} and Failure rejects values that don’t conform the value of the ‘error` parameter. This is essentially a Result type with the `Failure` part fixed.

@example using dry-types

module Types
  include Dry::Types.module
end

class Operation
  # :user_not_found and :account_not_found are the only
  # values allowed as failure results
  Error =
    Types.Value(:user_not_found) |
    Types.Value(:account_not_found)

  include Dry::Monads::Result(Error)

  def find_account(id)
    account = acount_repo.find(id)

    account ? Success(account) : Failure(:account_not_found)
  end

  def find_user(id)
    # ...
  end
end

@param error [#===] the type of allowed failures @return [Module]

# File lib/dry/monads/result.rb, line 397
def self.Result(error, **options)
  Result::Fixed[error, **options]
end
[](*monads) click to toggle source

Build a module with cherry-picked monads. It saves a bit of typing when you add multiple monads to one class. Not loaded monads get loaded automatically.

@example

require 'dry/monads'

class CreateUser
  include Dry::Monads[:result, :do]

  def initialize(repo, send_email)
    @repo = repo
    @send_email = send_email
  end

  def call(name)
    if @repo.user_exist?(name)
      Failure(:user_exists)
    else
      user = yield @repo.add_user(name)
      yield @send_email.(user)
      Success(user)
    end
  end
end

@param [Array<Symbol>] monads @return [Module] @api public

# File lib/dry/monads.rb, line 68
def self.[](*monads)
  monads.sort!
  @mixins.fetch_or_store(monads.hash) do
    monads.each { load_monad(_1) }
    mixins = monads.map { registry.fetch(_1) }
    ::Module.new { include(*mixins) }.freeze
  end
end
included(base) click to toggle source

@private

# File lib/dry/monads.rb, line 31
def self.included(base)
  if all_loaded?
    base.include(*constructors)
  else
    raise "Load all monads first with require 'dry/monads/all'"
  end
end
loader() click to toggle source

@api private

# File lib/dry/monads.rb, line 16
def self.loader
  @loader ||= Zeitwerk::Loader.new.tap do |loader|
    root = File.expand_path("..", __dir__)
    loader.tag = "dry-monads"
    loader.inflector = Zeitwerk::GemInflector.new("#{root}/dry-monads.rb")
    loader.push_dir(root)
    loader.ignore(
      "#{root}/dry-monads.rb",
      "#{root}/dry/monads/{all,constants,errors,registry,version}.rb",
      "#{root}/json/**/*.rb"
    )
  end
end

Protected Class Methods

registry=(registry) click to toggle source
# File lib/dry/monads/registry.rb, line 30
def registry=(registry)
  @constructors = nil
  @registry = registry.dup.freeze
end

Private Class Methods

all_loaded?() click to toggle source

@private

# File lib/dry/monads/registry.rb, line 68
def all_loaded?
  registry.size.eql?(@constants.size)
end
constructors() click to toggle source

@private

# File lib/dry/monads/registry.rb, line 61
def constructors
  @constructors ||= registry.values.filter_map { |m|
    m::Constructors if m.const_defined?(:Constructors)
  }
end
known_monads() click to toggle source

@private

# File lib/dry/monads/registry.rb, line 46
def known_monads
  @constants.keys
end
load_monad(name) click to toggle source

@private

# File lib/dry/monads/registry.rb, line 51
def load_monad(name)
  constants = @constants.fetch(name) {
    raise ::ArgumentError, "#{name.inspect} is not a known monad"
  }
  Array(constants).each do |const_name|
    const_name.split("::").reduce(Monads) { |mod, const| mod.const_get(const) }
  end
end
register_mixin(name, mod) click to toggle source

@private

# File lib/dry/monads/registry.rb, line 37
def register_mixin(name, mod)
  if registry.key?(name)
    raise ArgumentError, "#{name.inspect} is already registered"
  end

  self.registry = registry.merge(name => mod)
end