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 byList#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
Public Class Methods
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
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
@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
@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
# File lib/dry/monads/registry.rb, line 30 def registry=(registry) @constructors = nil @registry = registry.dup.freeze end
Private Class Methods
@private
# File lib/dry/monads/registry.rb, line 68 def all_loaded? registry.size.eql?(@constants.size) end
@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
@private
# File lib/dry/monads/registry.rb, line 46 def known_monads @constants.keys end
@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
@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