From 06a9c663ace50c2be62e101b0935b0ed58a6b73c Mon Sep 17 00:00:00 2001 From: Aaron Allen Date: Thu, 23 Oct 2025 16:53:52 -0500 Subject: [PATCH 1/4] Add bin/gen-sig --- Gemfile.devtools | 5 +++++ bin/gen-sig | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100755 bin/gen-sig diff --git a/Gemfile.devtools b/Gemfile.devtools index abe5367..90390e6 100644 --- a/Gemfile.devtools +++ b/Gemfile.devtools @@ -4,6 +4,11 @@ gem "rake", ">= 12.3.3" +group :sig do + gem "rbs" + gem "rbs-inline" +end + group :test do gem "simplecov", require: false, platforms: :ruby gem "simplecov-cobertura", require: false, platforms: :ruby diff --git a/bin/gen-sig b/bin/gen-sig new file mode 100755 index 0000000..e8803af --- /dev/null +++ b/bin/gen-sig @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "fileutils" +require "pathname" + +root_path = Pathname.new(File.expand_path("../", __dir__)) + +manifest_path = root_path.join("sig/manifest.yaml") +manifest = manifest_path.exist? ? manifest_path.read : "dependencies: []" + +FileUtils.rm_rf(root_path.join("sig")) + +system "rbs-inline lib --opt-out --output=./sig" + +EMPTY_COMMENT_BEFORE_CODE = / + ^[ \t]*\#[ \t]*\n + (?=^[ \t]*[^#\s]) +/mx +EMPTY_STRING = "" +GENERATED_LINE = /^ *# Generated from .+.rb with RBS::Inline *$/ +RBS_COMMENT_BLOCK = / + ^[ \t]*\#[ \t]*@rbs.*\n + (?:^[ \t]*\#.*\n)* +/x + +root_path.glob("sig/**/*.rbs").each do |file| + contents = file.read + + contents.gsub!(GENERATED_LINE, EMPTY_STRING) + &.gsub!(RBS_COMMENT_BLOCK, EMPTY_STRING) + &.gsub!(EMPTY_COMMENT_BEFORE_CODE, EMPTY_STRING) + &.strip + + if contents.nil? || contents.strip.empty? + File.delete(file) + else + File.write(file, "#{contents}\n") + end +end + +File.write(manifest_path, manifest) From 5a2e60cfd58a67869321ac7fa89a4af5b7123391 Mon Sep 17 00:00:00 2001 From: Aaron Allen Date: Thu, 23 Oct 2025 16:57:58 -0500 Subject: [PATCH 2/4] Generate initial signatures --- sig/dry/monads.rbs | 46 +++ sig/dry/monads/all.rbs | 7 + sig/dry/monads/constants.rbs | 8 + sig/dry/monads/conversion_stubs.rbs | 18 ++ sig/dry/monads/curry.rbs | 12 + sig/dry/monads/do.rbs | 102 ++++++ sig/dry/monads/do/all.rbs | 87 +++++ sig/dry/monads/do/mixin.rbs | 47 +++ sig/dry/monads/errors.rbs | 23 ++ sig/dry/monads/extensions/pretty_print.rbs | 20 ++ sig/dry/monads/extensions/rspec.rbs | 26 ++ sig/dry/monads/extensions/super_diff.rbs | 198 ++++++++++++ sig/dry/monads/lazy.rbs | 52 +++ sig/dry/monads/list.rbs | 274 ++++++++++++++++ sig/dry/monads/maybe.rbs | 298 +++++++++++++++++ sig/dry/monads/registry.rbs | 28 ++ sig/dry/monads/result.rbs | 357 +++++++++++++++++++++ sig/dry/monads/result/fixed.rbs | 20 ++ sig/dry/monads/right_biased.rbs | 280 ++++++++++++++++ sig/dry/monads/task.rbs | 192 +++++++++++ sig/dry/monads/transformer.rbs | 33 ++ sig/dry/monads/traverse.rbs | 11 + sig/dry/monads/try.rbs | 213 ++++++++++++ sig/dry/monads/unit.rbs | 23 ++ sig/dry/monads/validated.rbs | 219 +++++++++++++ sig/dry/monads/version.rbs | 9 + sig/json/add/dry/monads/maybe.rbs | 21 ++ sig/manifest.yaml | 1 + 28 files changed, 2625 insertions(+) create mode 100644 sig/dry/monads.rbs create mode 100644 sig/dry/monads/all.rbs create mode 100644 sig/dry/monads/constants.rbs create mode 100644 sig/dry/monads/conversion_stubs.rbs create mode 100644 sig/dry/monads/curry.rbs create mode 100644 sig/dry/monads/do.rbs create mode 100644 sig/dry/monads/do/all.rbs create mode 100644 sig/dry/monads/do/mixin.rbs create mode 100644 sig/dry/monads/errors.rbs create mode 100644 sig/dry/monads/extensions/pretty_print.rbs create mode 100644 sig/dry/monads/extensions/rspec.rbs create mode 100644 sig/dry/monads/extensions/super_diff.rbs create mode 100644 sig/dry/monads/lazy.rbs create mode 100644 sig/dry/monads/list.rbs create mode 100644 sig/dry/monads/maybe.rbs create mode 100644 sig/dry/monads/registry.rbs create mode 100644 sig/dry/monads/result.rbs create mode 100644 sig/dry/monads/result/fixed.rbs create mode 100644 sig/dry/monads/right_biased.rbs create mode 100644 sig/dry/monads/task.rbs create mode 100644 sig/dry/monads/transformer.rbs create mode 100644 sig/dry/monads/traverse.rbs create mode 100644 sig/dry/monads/try.rbs create mode 100644 sig/dry/monads/unit.rbs create mode 100644 sig/dry/monads/validated.rbs create mode 100644 sig/dry/monads/version.rbs create mode 100644 sig/json/add/dry/monads/maybe.rbs create mode 100644 sig/manifest.yaml diff --git a/sig/dry/monads.rbs b/sig/dry/monads.rbs new file mode 100644 index 0000000..a02ca66 --- /dev/null +++ b/sig/dry/monads.rbs @@ -0,0 +1,46 @@ + + +module Dry + # Common, idiomatic monads for Ruby + # + # @api public + module Monads + # @api private + def self.loader: () -> untyped + + # @private + def self.included: (untyped base) -> untyped + + # 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] monads + # @return [Module] + # @api public + def self.[]: (*untyped monads) -> untyped + end +end + diff --git a/sig/dry/monads/all.rbs b/sig/dry/monads/all.rbs new file mode 100644 index 0000000..cb2ef76 --- /dev/null +++ b/sig/dry/monads/all.rbs @@ -0,0 +1,7 @@ + + +module Dry + module Monads + end +end + diff --git a/sig/dry/monads/constants.rbs b/sig/dry/monads/constants.rbs new file mode 100644 index 0000000..8855a0b --- /dev/null +++ b/sig/dry/monads/constants.rbs @@ -0,0 +1,8 @@ + + +module Dry + module Monads + include Core::Constants + end +end + diff --git a/sig/dry/monads/conversion_stubs.rbs b/sig/dry/monads/conversion_stubs.rbs new file mode 100644 index 0000000..29d2195 --- /dev/null +++ b/sig/dry/monads/conversion_stubs.rbs @@ -0,0 +1,18 @@ + + +module Dry + module Monads + module ConversionStubs + def self.[]: (*untyped method_names) -> untyped + + module Methods + def self?.to_maybe: () -> untyped + + def self?.to_result: () -> untyped + + def self?.to_validated: () -> untyped + end + end + end +end + diff --git a/sig/dry/monads/curry.rbs b/sig/dry/monads/curry.rbs new file mode 100644 index 0000000..0cfb98d --- /dev/null +++ b/sig/dry/monads/curry.rbs @@ -0,0 +1,12 @@ + + +module Dry + module Monads + # @private + module Curry + # @private + def self.call: (untyped value) -> untyped + end + end +end + diff --git a/sig/dry/monads/do.rbs b/sig/dry/monads/do.rbs new file mode 100644 index 0000000..627510d --- /dev/null +++ b/sig/dry/monads/do.rbs @@ -0,0 +1,102 @@ + + +module Dry + module Monads + # An implementation of do-notation. + # + # @see Do.for + module Do + extend Mixin + + VISIBILITY_WORD: untyped + + # @api private + class Halt < StandardError + # @api private + attr_reader result: untyped + + def initialize: (untyped result) -> untyped + end + + # @api private + class MethodTracker < ::Module + # @api private + def initialize: (untyped tracked_methods, untyped base, untyped wrapper) -> untyped + end + + # Generates a module that passes a block to methods + # that either unwraps a single-valued monadic value or halts + # the execution. + # + # @example A complete example + # + # class CreateUser + # include Dry::Monads::Result::Mixin + # include Dry::Monads::Try::Mixin + # include Dry::Monads::Do.for(:call) + # + # attr_reader :user_repo + # + # def initialize(:user_repo) + # @user_repo = user_repo + # end + # + # def call(params) + # json = yield parse_json(params) + # hash = yield validate(json) + # + # user_repo.transaction do + # user = yield create_user(hash[:user]) + # yield create_profile(user, hash[:profile]) + # end + # + # Success(user) + # end + # + # private + # + # def parse_json(params) + # Try(JSON::ParserError) { + # JSON.parse(params) + # }.to_result + # end + # + # def validate(json) + # UserSchema.(json).to_monad + # end + # + # def create_user(user_data) + # Try(Sequel::Error) { + # user_repo.create(user_data) + # }.to_result + # end + # + # def create_profile(user, profile_data) + # Try(Sequel::Error) { + # user_repo.create_profile(user, profile_data) + # }.to_result + # end + # end + # + # @param [Array] methods + # @return [Module] + def self.for: (*untyped methods) -> untyped + + # @api private + def self.included: (untyped base) -> untyped + + # @api private + def self.wrap_method: (untyped target, untyped method, untyped visibility) -> untyped + + # @api private + def self.method_visibility: (untyped mod, untyped method) -> untyped + + # @api private + def self.coerce_to_monad: (untyped monads) -> untyped + + # @api private + def self.halt: (untyped result) -> untyped + end + end +end + diff --git a/sig/dry/monads/do/all.rbs b/sig/dry/monads/do/all.rbs new file mode 100644 index 0000000..baeeb44 --- /dev/null +++ b/sig/dry/monads/do/all.rbs @@ -0,0 +1,87 @@ + + +module Dry + module Monads + module Do + # Do::All automatically wraps methods defined in a class with an unwrapping block. + # Similar to what `Do.for(...)` does except wraps every method so you don't have + # to list them explicitly. + # + # @example annotated example + # + # require 'dry/monads/do/all' + # require 'dry/monads/result' + # + # class CreateUser + # include Dry::Monads::Do::All + # include Dry::Monads::Result::Mixin + # + # def call(params) + # # Unwrap a monadic value using an implicitly passed block + # # if `validates` returns Failure, the execution will be halted + # values = yield validate(params) + # user = create_user(values) + # # If another block is passed to a method then takes + # # precedence over the unwrapping block + # safely_subscribe(values[:email]) { Logger.info("Already subscribed") } + # + # Success(user) + # end + # + # def validate(params) + # if params.key?(:email) + # Success(email: params[:email]) + # else + # Failure(:no_email) + # end + # end + # + # def create_user(user) + # # Here a block is passed to the method but we don't use it + # UserRepo.new.add(user) + # end + # + # def safely_subscribe(email) + # repo = SubscriptionRepo.new + # + # if repo.subscribed?(email) + # # This calls the logger because a block + # # explicitly passed from `call` + # yield + # else + # repo.subscribe(email) + # end + # end + # end + module All + # @private + class MethodTracker < ::Module + attr_reader wrappers: untyped + + def initialize: (untyped wrappers) -> untyped + + def extend_object: (untyped target) -> untyped + + def wrap_method: (untyped target, untyped method) -> untyped + end + + # @api private + def self.included: (untyped base) -> untyped + + # @api private + def self.wrap_defined_methods: (untyped klass, untyped target) -> untyped + + # @api private + module InstanceMixin + # @api private + def extended: (untyped object) -> untyped + end + + extend InstanceMixin + end + end + + def warn: (untyped message, ?category: untyped, **untyped) -> untyped + end +end + diff --git a/sig/dry/monads/do/mixin.rbs b/sig/dry/monads/do/mixin.rbs new file mode 100644 index 0000000..310d78c --- /dev/null +++ b/sig/dry/monads/do/mixin.rbs @@ -0,0 +1,47 @@ + + +module Dry + module Monads + module Do + # Do notation as a mixin. + # You can use it in any place in your code, see examples. + # + # @example class-level mixin + # + # class CreateUser + # extend Dry::Monads::Do::Mixin + # extend Dry::Monads[:result] + # + # def self.run(params) + # self.call do + # values = bind Validator.validate(params) + # user = bind UserRepository.create(values) + # + # Success(user) + # end + # end + # end + # + # @example using methods defined on Do + # + # create_user = proc do |params| + # Do.() do + # values = bind validate(params) + # user = bind user_repo.create(values) + # + # Success(user) + # end + # end + # + # @api public + module Mixin + # @api public + def call: () -> untyped + + # @api public + def bind: (untyped monads) -> untyped + end + end + end +end + diff --git a/sig/dry/monads/errors.rbs b/sig/dry/monads/errors.rbs new file mode 100644 index 0000000..27303d9 --- /dev/null +++ b/sig/dry/monads/errors.rbs @@ -0,0 +1,23 @@ + + +module Dry + module Monads + # An unsuccessful result of extracting a value from a monad. + class UnwrapError < ::StandardError + attr_reader receiver: untyped + + def initialize: (untyped receiver) -> untyped + end + + # An error thrown on returning a Failure of unknown type. + class InvalidFailureTypeError < ::StandardError + def initialize: (untyped failure) -> untyped + end + + # Improper use of None + class ConstructorNotAppliedError < ::NoMethodError + def initialize: (untyped method_name, untyped constructor_name) -> untyped + end + end +end + diff --git a/sig/dry/monads/extensions/pretty_print.rbs b/sig/dry/monads/extensions/pretty_print.rbs new file mode 100644 index 0000000..13db40c --- /dev/null +++ b/sig/dry/monads/extensions/pretty_print.rbs @@ -0,0 +1,20 @@ + + +module Dry + module Monads + module Extensions + module PrettyPrint + class PrintValue < ::Module + def initialize: (untyped constructor, ?accessor: untyped) -> untyped + end + + class LazyPrintValue < ::Module + def initialize: (untyped constructor, ?success_prefix: untyped, ?error_prefix: untyped) -> untyped + end + end + + def pretty_print: (untyped pp) -> untyped + end + end +end + diff --git a/sig/dry/monads/extensions/rspec.rbs b/sig/dry/monads/extensions/rspec.rbs new file mode 100644 index 0000000..00d001e --- /dev/null +++ b/sig/dry/monads/extensions/rspec.rbs @@ -0,0 +1,26 @@ + + +module Dry + module Monads + module RSpec + module Matchers + extend ::RSpec::Matchers::DSL + + PREDICATES: untyped + + CONSTRUCTOR_CLASSES: untyped + end + + Constructors: untyped + + CONSTANTS: untyped + + NESTED_CONSTANTS: untyped + + def self.resolve_constant_name: (untyped name) -> untyped + + def self.name_to_const: (untyped name) -> untyped + end + end +end + diff --git a/sig/dry/monads/extensions/super_diff.rbs b/sig/dry/monads/extensions/super_diff.rbs new file mode 100644 index 0000000..fb4dd84 --- /dev/null +++ b/sig/dry/monads/extensions/super_diff.rbs @@ -0,0 +1,198 @@ + + +module Dry + module Monads + module SuperDiff + VALUES: untyped + + EXTRACT_VALUE_MAP: untyped + + EXTRACT_VALUE: untyped + + IS_ARRAY: untyped + + IS_HASH: untyped + + TOKEN_MAP: untyped + + class Tuple < ::SimpleDelegator + def is_a?: (untyped klass) -> untyped + end + + class Dict < ::SimpleDelegator + def is_a?: (untyped klass) -> untyped + end + + module OTFlatteners + module MonasAsCollectionConstructor + def call: () -> untyped + + def build_tiered_lines: () -> untyped + + # prevent super_diff from adding a newline after the open token + # for arrays + def build_lines_for_non_change_operation: (*untyped) -> untyped + + def open_token: () -> untyped + + def close_token: () -> untyped + end + + class RegularConstructor < ::SuperDiff::Basic::OperationTreeFlatteners::CustomObject + private + + def initialize: () -> untyped + + def open_token: () -> untyped + + def close_token: () -> untyped + + def item_prefix_for: (untyped _) -> untyped + end + + class TupleConstructor < RegularConstructor + private + + def open_token: () -> untyped + + def close_token: () -> untyped + end + + class Tuple < ::SuperDiff::Basic::OperationTreeFlatteners::Array + include MonasAsCollectionConstructor + end + + class Dict < ::SuperDiff::Basic::OperationTreeFlatteners::Hash + include MonasAsCollectionConstructor + end + end + + module OT + class RegularConstructor < ::SuperDiff::Basic::OperationTrees::CustomObject + def self.applies_to?: (untyped value) -> untyped + + def operation_tree_flattener_class: () -> untyped + end + + class TupleConstructor < RegularConstructor + def self.applies_to?: (untyped value) -> untyped + + def operation_tree_flattener_class: () -> untyped + end + + class Tuple < ::SuperDiff::Basic::OperationTrees::Array + def self.applies_to?: (untyped value) -> untyped + + def operation_tree_flattener_class: () -> untyped + end + + class Dict < ::SuperDiff::Basic::OperationTrees::Hash + def self.applies_to?: (untyped value) -> untyped + + def operation_tree_flattener_class: () -> untyped + end + end + + module OTBuilders + class CompareDefault < ::SuperDiff::Basic::OperationTreeBuilders::CustomObject + def self.applies_to?: (untyped expected, untyped actual) -> untyped + + def build_operation_tree: () -> untyped + + def attribute_names: () -> untyped + + private + + def establish_expected_and_actual_attributes: () -> untyped + + def get_value: (untyped object) -> untyped + end + + class Tuple < ::SuperDiff::Basic::OperationTreeBuilders::Array + def self.applies_to?: (untyped expected, untyped actual) -> untyped + + private + + def operation_tree: () -> untyped + end + + class Dict < ::SuperDiff::Basic::OperationTreeBuilders::Hash + def self.applies_to?: (untyped expected, untyped actual) -> untyped + + private + + def build_operation_tree: () -> untyped + end + + class CompareTuples < CompareDefault + def self.applies_to?: (untyped expected, untyped actual) -> untyped + + private + + def get_value: (untyped object) -> untyped + + def build_operation_tree: () -> untyped + end + + class CompareDicts < CompareDefault + def self.applies_to?: (untyped expected, untyped actual) -> untyped + + private + + def get_value: (untyped object) -> untyped + end + end + + module Differs + class CompareDefault < ::SuperDiff::Basic::Differs::CustomObject + def self.applies_to?: (untyped expected, untyped actual) -> untyped + + def operation_tree_builder_class: () -> untyped + end + + class CompareTuples < CompareDefault + def self.applies_to?: (untyped expected, untyped actual) -> untyped + + def operation_tree_builder_class: () -> untyped + end + + class CompareDicts < CompareDefault + def self.applies_to?: (untyped expected, untyped actual) -> untyped + + def operation_tree_builder_class: () -> untyped + end + end + + module ITBuilders + class RegularConstructor < ::SuperDiff::Basic::InspectionTreeBuilders::CustomObject + def self.applies_to?: (untyped object) -> untyped + + def call: () -> untyped + + private + + def build_tree: () ?{ (?) -> untyped } -> untyped + end + + class TupleConstructor < RegularConstructor + def self.applies_to?: (untyped object) -> untyped + + def call: () -> untyped + end + + class DictConstructor < RegularConstructor + def self.applies_to?: (untyped object) -> untyped + + def call: () -> untyped + end + + class Dict < ::SuperDiff::Basic::InspectionTreeBuilders::Hash + def self.applies_to?: (untyped object) -> untyped + + def call: () -> untyped + end + end + end + end +end + diff --git a/sig/dry/monads/lazy.rbs b/sig/dry/monads/lazy.rbs new file mode 100644 index 0000000..4eac393 --- /dev/null +++ b/sig/dry/monads/lazy.rbs @@ -0,0 +1,52 @@ + + +module Dry + module Monads + # Lazy is a twin of Task which is always executed on the current thread. + # The underlying mechanism provided by concurrent-ruby ensures the given + # computation is evaluated not more than once (compare with the built-in + # lazy assignement ||= which does not guarantee this). + class Lazy < Task + # @private + def self.new: (?untyped promise) ?{ (?) -> untyped } -> untyped + + # Forces the compution and returns its value. + # + # @return [Object] + def value!: () -> untyped + + # Forces the computation. Note that if the computation + # thrown an error it won't be re-raised as opposed to value!/force!. + # + # @return [Lazy] + def force: () -> untyped + + # @return [Boolean] + def evaluated?: () -> untyped + + # @return [String] + def to_s: () -> untyped + + # Lazy constructors + module Mixin + # @see Dry::Monads::Lazy + Lazy: untyped + + # @see Dry::Monads::Unit + Unit: untyped + + # Lazy constructors + module Constructors + # Lazy computation contructor + # + # @param block [Proc] + # @return [Lazy] + def Lazy: () ?{ (?) -> untyped } -> untyped + end + + include Constructors + end + end + end +end + diff --git a/sig/dry/monads/list.rbs b/sig/dry/monads/list.rbs new file mode 100644 index 0000000..3806bb8 --- /dev/null +++ b/sig/dry/monads/list.rbs @@ -0,0 +1,274 @@ + + +module Dry + module Monads + # The List monad. + class List + # Builds a list. + # + # @param values [Array] List elements + # @return [List] + def self.[]: (*untyped values) -> untyped + + # Coerces a value to a list. `nil` will be coerced to an empty list. + # + # @param value [Object] Value + # @param type [Monad] Embedded monad type (used in case of list of monadic values) + # @return [List] + def self.coerce: (untyped value, ?untyped type) -> untyped + + # Wraps a value with a list. + # + # @param value [Object] any object + # @return [List] + def self.pure: (?untyped value, ?untyped type) ?{ (?) -> untyped } -> untyped + + # Iteratively builds a new list from a block returning Maybe values + # + # @see https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-List.html#g:9 + # + # @param state [Object.new] Initial state + # @param type [#pure] Type of list element + # @return [List] + def self.unfold: (untyped state, ?untyped type) -> untyped + + include Transformer + + # Internal array value + attr_reader value: untyped + + # Internal array value + attr_reader type: untyped + + # @api private + def initialize: (untyped value, ?untyped type) -> untyped + + # Lifts a block/proc and runs it against each member of the list. + # The block must return a value coercible to a list. + # As in other monads if no block given the first argument will + # be treated as callable and used instead. + # + # @example + # Dry::Monads::List[1, 2].bind { |x| [x + 1] } # => List[2, 3] + # Dry::Monads::List[1, 2].bind(-> x { [x, x + 1] }) # => List[1, 2, 2, 3] + # + # @param args [Array] arguments will be passed to the block or proc + # @return [List] + def bind: (*untyped args) -> untyped + + # Maps a block over the list. Acts as `Array#map`. + # As in other monads if no block given the first argument will + # be treated as callable and used instead. + # + # @example + # Dry::Monads::List[1, 2].fmap { |x| x + 1 } # => List[2, 3] + # + # @param args [Array] arguments will be passed to the block or proc + # @return [List] + def fmap: (*untyped args) -> untyped + + # Maps a block over the list. Acts as `Array#map`. + # If called without a block, this method returns an enumerator, not a List + # + # @return [List,Enumerator] + def map: () ?{ (?) -> untyped } -> untyped + + # Concatenates two lists. + # + # @example + # Dry::Monads::List[1, 2] + Dry::Monads::List[3, 4] # => List[1, 2, 3, 4] + # + # @param other [List] Other list + # @return [List] + def +: (untyped other) -> untyped + + # Returns a string representation of the list. + # + # @example + # Dry::Monads::List[1, 2, 3].inspect # => "List[1, 2, 3]" + # + # @return [String] + def inspect: () -> untyped + + # Returns the first element. + # + # @return [Object] + def first: () -> untyped + + # Returns the last element. + # + # @return [Object] + def last: () -> untyped + + # Folds the list from the left. + # + # @param initial [Object] Initial value + # @return [Object] + def fold_left: (untyped initial) ?{ (?) -> untyped } -> untyped + + # Folds the list from the right. + # + # @param initial [Object] Initial value + # @return [Object] + def fold_right: (untyped initial) -> untyped + + # Whether the list is empty. + # + # @return [TrueClass, FalseClass] + def empty?: () -> untyped + + # Sorts the list. + # + # @return [List] + def sort: () -> untyped + + # Filters elements with a block + # + # @return [List] + def filter: () ?{ (?) -> untyped } -> untyped + + # List size. + # + # @return [Integer] + def size: () -> untyped + + # Reverses the list. + # + # @return [List] + def reverse: () -> untyped + + # Returns the first element wrapped with a `Maybe`. + # + # @return [Maybe] + def head: () -> untyped + + # Returns list's tail. + # + # @return [List] + def tail: () -> untyped + + # Turns the list into a typed one. + # Type is required for some operations like .traverse. + # + # @param type [Monad] Monad instance + # @return [List] Typed list + def typed: (?untyped type) -> untyped + + # Whether the list is types + # + # @return [Boolean] + def typed?: () -> untyped + + # Traverses the list with a block (or without it). + # This methods "flips" List structure with the given monad (obtained from the type). + # Note that traversing requires the list to be typed. + # Also if a block given, its returning type must be equal list's type. + # + # @example + # List[Success(1), Success(2)].traverse # => Success([1, 2]) + # List[Some(1), None, Some(3)].traverse # => None + # + # @return [Monad] Result is a monadic value + def traverse: (?untyped proc) ?{ (?) -> untyped } -> untyped + + # Applies the stored functions to the elements of the given list. + # + # @param list [List] + # @return [List] + def apply: (?untyped list) ?{ (?) -> untyped } -> untyped + + # Returns the List monad. + # + # @return [Monad] + def monad: () -> untyped + + # Returns self. + # + # @return [List] + def to_monad: () -> untyped + + # Iterates over the list and collects Some values. + # + # @example with block syntax + # n = 20 + # List[10, 5, 0].collect do |divisor| + # if divisor.zero? + # None() + # else + # Some(n / divisor) + # end + # end + # # => List[2, 4] + # + # @example without block + # List[Some(5), None(), Some(3)].collect.map { |x| x * 2 } + # # => [10, 6] + # + # @return [List] + def collect: () -> untyped + + # Pattern matching + # + # @example + # case List[1, 2, 3] + # in List[1, 2, x] then ... + # in List[Integer, _, _] then ... + # in List[0..2, _, _] then ... + # end + # + # @api private + def deconstruct: () -> untyped + + private + + def coerce: (untyped other) -> untyped + + # Empty list + EMPTY: untyped + + # @private + class ListBuilder + attr_reader type: untyped + + def initialize: (untyped type) -> untyped + + def []: (*untyped args) -> untyped + + def coerce: (untyped value) -> untyped + + def pure: (?untyped val) ?{ (?) -> untyped } -> untyped + end + + # List of tasks + Task: untyped + + # List of results + Result: untyped + + # List of maybes + Maybe: untyped + + # List of tries + Try: untyped + + # List of validation results + Validated: untyped + + # List contructors. + # + # @api public + module Mixin + # @see Dry::Monads::List + List: untyped + + # @see Dry::Monads::List + L: untyped + + # List constructor. + # @return [List] + def List: (untyped value) -> untyped + end + end + end +end + diff --git a/sig/dry/monads/maybe.rbs b/sig/dry/monads/maybe.rbs new file mode 100644 index 0000000..78d93e6 --- /dev/null +++ b/sig/dry/monads/maybe.rbs @@ -0,0 +1,298 @@ + + +module Dry + module Monads + # Represents a value which can exist or not, i.e. it could be nil. + # + # @api public + class Maybe + include Transformer + + extend Core::ClassAttributes + + # Wraps the given value with into a Maybe object. + # + # @param value [Object] value to be stored in the monad + # @return [Maybe::Some, Maybe::None] + def self.coerce: (untyped value) -> untyped + + # Wraps the given value with `Some`. + # + # @param value [Object] value to be wrapped with Some + # @param block [Object] block to be wrapped with Some + # @return [Maybe::Some] + def self.pure: (?untyped value) ?{ (?) -> untyped } -> untyped + + # Reutrns a Some wrapper converted to a block + # + # @return [Proc] + def self.to_proc: () -> untyped + + # Returns true for an instance of a {Maybe::None} monad. + def none?: () -> untyped + + # Returns true for an instance of a {Maybe::Some} monad. + def some?: () -> untyped + + # Returns self, added to keep the interface compatible with that of Either monad types. + # + # @return [Maybe::Some, Maybe::None] + def to_maybe: () -> untyped + + # Returns self. + # + # @return [Maybe::Some, Maybe::None] + def to_monad: () -> untyped + + # Returns the Maybe monad. + # This is how we're doing polymorphism in Ruby 😕 + # + # @return [Monad] + def monad: () -> untyped + + # Represents a value that is present, i.e. not nil. + # + # @api public + class Some < Maybe + include RightBiased::Right + + # Shortcut for Some([...]) + # + # @example + # include Dry::Monads[:maybe] + # + # def call + # Some[200, {}, ['ok']] # => Some([200, {}, ['ok']]) + # end + # + # @api public + def self.[]: (*untyped value) -> untyped + + def initialize: (?untyped value) -> untyped + + # Does the same thing as #bind except it also wraps the value + # in an instance of Maybe::Some monad. This allows for easier + # chaining of calls. + # + # @example + # Dry::Monads.Some(4).fmap(&:succ).fmap(->(n) { n**2 }) # => Some(25) + # + # @param args [Array] arguments will be transparently passed through to #bind + # @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None, + # other values will be wrapped with Some + def fmap: () -> untyped + + # Does the same thing as #bind except it also wraps the value + # in an instance of the Maybe monad. This allows for easier + # chaining of calls. + # + # @example + # Dry::Monads.Some(4).maybe(&:succ).maybe(->(n) { n**2 }) # => Some(25) + # Dry::Monads.Some(4).maybe(&:succ).maybe(->(_) { nil }) # => None() + # + # @param args [Array] arguments will be transparently passed through to #bind + # @return [Maybe::Some, Maybe::None] Wrapped result, i.e. nil will be mapped to None, + # other values will be wrapped with Some + def maybe: () -> untyped + + # Accepts a block and runs it against the wrapped value. + # If the block returns a trurhy value the result is self, + # otherwise None. If no block is given, the value serves + # and its result. + # + # @param with [#call] positional block + # @param block [Proc] block + # + # @return [Maybe::None, Maybe::Some] + def filter: (?untyped with) ?{ (?) -> untyped } -> untyped + + # @return [String] + def to_s: () -> untyped + end + + # Represents an absence of a value, i.e. the value nil. + # + # @api public + class None < Maybe + include RightBiased::Left + + # @api private + def self.method_missing: (untyped m, *untyped) -> untyped + + # Line where the value was constructed + # + # @return [String] + attr_reader trace: untyped + + def initialize: (?untyped trace) -> untyped + + # If a block is given passes internal value to it and returns the result, + # otherwise simply returns the parameter val. + # + # @example + # Dry::Monads.None.or('no value') # => "no value" + # Dry::Monads.None.or { Time.now } # => current time + # + # @param args [Array] if no block given the first argument will be returned + # otherwise arguments will be transparently passed to the block + # @return [Object] + def or: (*untyped args) -> untyped + + # A lifted version of `#or`. Applies `Maybe.coerce` to the passed value or + # to the block result. + # + # @example + # Dry::Monads.None.or_fmap('no value') # => Some("no value") + # Dry::Monads.None.or_fmap { Time.now } # => Some(current time) + # + # @param args [Array] arguments will be passed to the underlying `#or` call + # @return [Maybe::Some, Maybe::None] Lifted `#or` result, i.e. nil will be mapped to None, + # other values will be wrapped with Some + def or_fmap: () -> untyped + + # @return [String] + def to_s: () -> untyped + + # @api private + def eql?: (untyped other) -> untyped + + # @private + def hash: () -> untyped + + # Pattern matching + # + # @example + # case Some(:foo) + # in Some(Integer) then ... + # in Some(:bar) then ... + # in None() then ... + # end + # + # @api private + def deconstruct: () -> untyped + + # @see Maybe::Some#filter + # + # @return [Maybe::None] + def filter: (?untyped _) ?{ (?) -> untyped } -> untyped + end + + # A module that can be included for easier access to Maybe monads. + module Mixin + # @see Dry::Monads::Maybe + Maybe: untyped + + # @see Maybe::Some + Some: untyped + + # @see Maybe::None + None: untyped + + # @private + module Constructors + # @param value [Object] the value to be stored in the monad + # @return [Maybe::Some, Maybe::None] + def Maybe: (untyped value) -> untyped + + # Some constructor + # + # @overload Some(value) + # @param value [Object] any value except `nil` + # @return [Maybe::Some] + # + # @overload Some(&block) + # @param block [Proc] a block to be wrapped with Some + # @return [Maybe::Some] + def Some: (?untyped value) ?{ (?) -> untyped } -> untyped + + # @return [Maybe::None] + def None: () -> untyped + end + + include Constructors + end + + # Utilities for working with hashes storing Maybe values + module Hash + # Traverses a hash with maybe values. If any value is None then None is returned + # + # @example + # Maybe::Hash.all(foo: Some(1), bar: Some(2)) # => Some(foo: 1, bar: 2) + # Maybe::Hash.all(foo: Some(1), bar: None()) # => None() + # Maybe::Hash.all(foo: None(), bar: Some(2)) # => None() + # + # @param hash [::Hash] + # @return [Maybe<::Hash>] + def self.all: (untyped hash, ?untyped trace) -> untyped + + # Traverses a hash with maybe values. Some values are unwrapped, keys with + # None values are removed + # + # @example + # Maybe::Hash.filter(foo: Some(1), bar: Some(2)) # => { foo: 1, bar: 2 } + # Maybe::Hash.filter(foo: Some(1), bar: None()) # => { foo: 1 } + # Maybe::Hash.filter(foo: None(), bar: Some(2)) # => { bar: 2 } + # + # @param hash [::Hash] + # @return [::Hash] + def self.filter: (untyped hash) -> untyped + end + end + + extend Maybe::Mixin::Constructors + + # @see Maybe::Some + Some: untyped + + # @see Maybe::None + None: untyped + + class Result + class Success < Result + # @return [Maybe] + def to_maybe: () -> untyped + end + + class Failure < Result + # @return [Maybe::None] + def to_maybe: () -> untyped + end + end + + class Task + # Converts to Maybe. Blocks the current thread if required. + # + # @return [Maybe] + def to_maybe: () -> untyped + end + + class Try + class Value < Try + # @return [Maybe] + def to_maybe: () -> untyped + end + + class Error < Try + # @return [Maybe::None] + def to_maybe: () -> untyped + end + end + + class Validated + class Valid < Validated + # Converts to Maybe::Some + # + # @return [Maybe::Some] + def to_maybe: () -> untyped + end + + class Invalid < Validated + # Converts to Maybe::None + # + # @return [Maybe::None] + def to_maybe: () -> untyped + end + end + end +end + diff --git a/sig/dry/monads/registry.rbs b/sig/dry/monads/registry.rbs new file mode 100644 index 0000000..de29559 --- /dev/null +++ b/sig/dry/monads/registry.rbs @@ -0,0 +1,28 @@ + + +module Dry + # Common, idiomatic monads for Ruby + # + # @api private + module Monads + attr_reader registry: untyped + + private def self.registry=: (untyped registry) -> untyped + + # @private + private def self.register_mixin: (untyped name, untyped mod) -> untyped + + # @private + private def self.known_monads: () -> untyped + + # @private + private def self.load_monad: (untyped name) -> untyped + + # @private + private def self.constructors: () -> untyped + + # @private + private def self.all_loaded?: () -> untyped + end +end + diff --git a/sig/dry/monads/result.rbs b/sig/dry/monads/result.rbs new file mode 100644 index 0000000..498d88d --- /dev/null +++ b/sig/dry/monads/result.rbs @@ -0,0 +1,357 @@ + + +module Dry + module Monads + # Represents an operation which either succeeded or failed. + # + # @api public + class Result + include Transformer + + # @return [Object] Successful result + attr_reader success: untyped + + # @return [Object] Error + attr_reader failure: untyped + + # Wraps the given value with Success. + # + # @overload pure(value) + # @param value [Object] + # @return [Result::Success] + # + # @overload pure(&block) + # @param block [Proc] a block to be wrapped with Success + # @return [Result::Success] + def self.pure: (?untyped value) ?{ (?) -> untyped } -> untyped + + # Returns self, added to keep the interface compatible with other monads. + # + # @return [Result::Success, Result::Failure] + def to_result: () -> untyped + + # Returns self. + # + # @return [Result::Success, Result::Failure] + def to_monad: () -> untyped + + # Returns the Result monad. + # This is how we're doing polymorphism in Ruby 😕 + # + # @return [Monad] + def monad: () -> untyped + + # Represents a value of a successful operation. + # + # @api public + class Success < Result + include RightBiased::Right + + # Shortcut for Success([...]) + # + # @example + # include Dry::Monads[:result] + # + # def call + # Success[200, {}, ['ok']] # => Success([200, {}, ['ok']]) + # end + # + # @api public + def self.[]: (*untyped value) -> untyped + + # @param value [Object] a value of a successful operation + def initialize: (untyped value) -> untyped + + # Apply the second function to value. + # + # @api public + def result: (untyped _, untyped f) -> untyped + + # Returns false + def failure?: () -> untyped + + # Returns true + def success?: () -> untyped + + # Does the same thing as #bind except it also wraps the value + # in an instance of Result::Success monad. This allows for easier + # chaining of calls. + # + # @example + # Dry::Monads.Success(4).fmap(&:succ).fmap(->(n) { n**2 }) # => Success(25) + # + # @param args [Array] arguments will be transparently passed through to #bind + # @return [Result::Success] + def fmap: () -> untyped + + # Returns result of applying first function to the internal value. + # + # @example + # Dry::Monads.Success(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 2 + # + # @param f [#call] Function to apply + # @param _ [#call] Ignored + # @return [Any] Return value of `f` + def either: (untyped f, untyped _) -> untyped + + # @return [String] + def to_s: () -> untyped + + # Transforms to a Failure instance + # + # @return [Result::Failure] + def flip: () -> untyped + + # Ignores values and returns self, see {Failure#alt_map} + # + # @return [Result::Success] + def alt_map: (?untyped _) ?{ (?) -> untyped } -> untyped + end + + # Represents a value of a failed operation. + # + # @api public + class Failure < Result + include RightBiased::Left + + # Shortcut for Failure([...]) + # + # @example + # include Dry::Monads[:result] + # + # def call + # Failure[:error, :not_found] # => Failure([:error, :not_found]) + # end + # + # @api public + def self.[]: (*untyped value) -> untyped + + # Returns a constructor proc + # + # @return [Proc] + def self.to_proc: () -> untyped + + # Line where the value was constructed + # + # @return [String] + # @api public + attr_reader trace: untyped + + # @param value [Object] failure value + # @param trace [String] caller line + def initialize: (untyped value, ?untyped trace) -> untyped + + # @private + def failure: () -> untyped + + # Apply the first function to value. + # + # @api public + def result: (untyped f, untyped _) -> untyped + + # Returns true + def failure?: () -> untyped + + # Returns false + def success?: () -> untyped + + # If a block is given passes internal value to it and returns the result, + # otherwise simply returns the first argument. + # + # @example + # Dry::Monads.Failure(ArgumentError.new('error message')).or(&:message) + # # => "error message" + # + # @param args [Array] arguments that will be passed to a block + # if one was given, otherwise the first + # value will be returned + # @return [Object] + def or: (*untyped args) -> untyped + + # A lifted version of `#or`. Wraps the passed value or the block + # result with Result::Success. + # + # @example + # Dry::Monads.Failure.new('no value').or_fmap('value') # => Success("value") + # Dry::Monads.Failure.new('no value').or_fmap { 'value' } # => Success("value") + # + # @param args [Array] arguments will be passed to the underlying `#or` call + # @return [Result::Success] Wrapped value + def or_fmap: () -> untyped + + # @return [String] + def to_s: () -> untyped + + # Transform to a Success instance + # + # @return [Result::Success] + def flip: () -> untyped + + # @see RightBiased::Left#value_or + def value_or: (?untyped val) -> untyped + + # @param other [Result] + # @return [Boolean] + def ===: (untyped other) -> untyped + + # Returns result of applying second function to the internal value. + # + # @example + # Dry::Monads.Failure(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 3 + # + # @param _ [#call] Ignored + # @param g [#call] Function to call + # @return [Any] Return value of `g` + def either: (untyped _, untyped g) -> untyped + + # Lifts a block/proc over Failure + # + # @overload alt_map(proc) + # @param proc [#call] + # @return [Result::Failure] + # + # @overload alt_map + # @param block [Proc] + # @return [Result::Failure] + def alt_map: (?untyped proc) ?{ (?) -> untyped } -> untyped + end + + # A module that can be included for easier access to Result monads. + # + # @api public + module Mixin + # @see Result::Success + Success: untyped + + # @see Result::Failure + Failure: untyped + + # Value constructors + module Constructors + # Success constructor + # + # @overload Success(value) + # @param value [Object] + # @return [Result::Success] + # + # @overload Success(&block) + # @param block [Proc] a block to be wrapped with Success + # @return [Result::Success] + def Success: (?untyped value) ?{ (?) -> untyped } -> untyped + + # Failure constructor + # + # @overload Success(value) + # @param value [Object] + # @return [Result::Failure] + # + # @overload Success(&block) + # @param block [Proc] a block to be wrapped with Failure + # @return [Result::Failure] + def Failure: (?untyped value) ?{ (?) -> untyped } -> untyped + end + + include Constructors + end + end + + extend Result::Mixin::Constructors + + # @see Result::Success + Success: untyped + + # @see Result::Failure + Failure: untyped + + # 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] + def self.Result: (untyped error, **untyped options) -> untyped + + class Maybe + class Some < Maybe + # Converts to Sucess(value!) + # + # @param fail [#call] Fallback value + # @param block [Proc] Fallback block + # @return [Success] + def to_result: (?untyped _fail) ?{ (?) -> untyped } -> untyped + end + + class None < Maybe + # Converts to Failure(fallback_value) + # + # @param fail [#call] Fallback value + # @param block [Proc] Fallback block + # @return [Failure] + def to_result: (?untyped fail) -> untyped + end + end + + class Task + # Converts to Result. Blocks the current thread if required. + # + # @return [Result] + def to_result: () -> untyped + end + + class Try + class Value < Try + # @return [Result::Success] + def to_result: () -> untyped + end + + class Error < Try + # @return [Result::Failure] + def to_result: () -> untyped + end + end + + class Validated + class Valid < Validated + # Converts to Result::Success + # + # @return [Result::Success] + def to_result: () -> untyped + end + + class Invalid < Validated + # Converts to Result::Failure + # + # @return [Result::Failure] + def to_result: () -> untyped + end + end + end +end + diff --git a/sig/dry/monads/result/fixed.rbs b/sig/dry/monads/result/fixed.rbs new file mode 100644 index 0000000..0f517da --- /dev/null +++ b/sig/dry/monads/result/fixed.rbs @@ -0,0 +1,20 @@ + + +module Dry + module Monads + class Result + # @see Monads#Result + # @private + class Fixed < ::Module + def self.[]: (untyped error, **untyped options) -> untyped + + def initialize: (untyped error, **untyped) -> untyped + + private + + def included: (untyped base) -> untyped + end + end + end +end + diff --git a/sig/dry/monads/right_biased.rbs b/sig/dry/monads/right_biased.rbs new file mode 100644 index 0000000..0675a58 --- /dev/null +++ b/sig/dry/monads/right_biased.rbs @@ -0,0 +1,280 @@ + + +module Dry + module Monads + # A common module for right-biased monads, such as Result/Either, Maybe, and Try. + module RightBiased + # Right part + # + # @api public + module Right + # @private + def self.included: (untyped m) -> untyped + + # Unwraps the underlying value + # + # @return [Object] + def value!: () -> untyped + + # Calls the passed in Proc object with value stored in self + # and returns the result. + # + # If proc is nil, it expects a block to be given and will yield to it. + # + # @example + # Dry::Monads.Right(4).bind(&:succ) # => 5 + # + # @param [Array] args arguments that will be passed to a block + # if one was given, otherwise the first + # value assumed to be a Proc (callable) + # object and the rest of args will be passed + # to this object along with the internal value + # @return [Object] result of calling proc or block on the internal value + def bind: (*untyped args, **untyped kwargs) -> untyped + + # Does the same thing as #bind except it returns the original monad + # when the result is a Right. + # + # @example + # Dry::Monads.Right(4).tee { Right('ok') } # => Right(4) + # Dry::Monads.Right(4).tee { Left('fail') } # => Left('fail') + # + # @param [Array] args arguments will be transparently passed through to #bind + # @return [RightBiased::Right] + def tee: () -> untyped + + # Abstract method for lifting a block over the monad type. + # Must be implemented for a right-biased monad. + # + # @return [RightBiased::Right] + def fmap: () -> untyped + + # Ignores arguments and returns self. It exists to keep the interface + # identical to that of {RightBiased::Left}. + # + # @return [RightBiased::Right] + def or: () -> untyped + + # Ignores arguments and returns self. It exists to keep the interface + # identical to that of {RightBiased::Left}. + # + # @param _alt [RightBiased::Right, RightBiased::Left] + # + # @return [RightBiased::Right] + def |: (untyped _alt) -> untyped + + # A lifted version of `#or`. For {RightBiased::Right} acts in the same way as `#or`, + # that is returns itselt. + # + # @return [RightBiased::Right] + def or_fmap: () -> untyped + + # Returns value. It exists to keep the interface identical to that of RightBiased::Left + # + # @return [Object] + def value_or: (?untyped _val) ?{ (?) -> untyped } -> untyped + + # Applies the stored value to the given argument if the argument has type of Right, + # otherwise returns the argument. + # + # @example happy path + # create_user = Dry::Monads::Success(CreateUser.new) + # name = Success("John") + # create_user.apply(name) # equivalent to CreateUser.new.call("John") + # + # @example unhappy path + # name = Failure(:name_missing) + # create_user.apply(name) # => Failure(:name_missing) + # + # @return [RightBiased::Left,RightBiased::Right] + def apply: (?untyped val) ?{ (?) -> untyped } -> untyped + + # @param other [Object] + # @return [Boolean] + def ===: (untyped other) -> untyped + + # Maps the value to Dry::Monads::Unit, useful when you don't care + # about the actual value. + # + # @example + # Dry::Monads::Success(:success).discard + # # => Success(Unit) + # + # @return [RightBiased::Right] + def discard: () -> untyped + + # Removes one level of monad structure by joining two values. + # + # @example + # include Dry::Monads::Result::Mixin + # Success(Success(5)).flatten # => Success(5) + # Success(Failure(:not_a_number)).flatten # => Failure(:not_a_number) + # Failure(:not_a_number).flatten # => Failure(:not_a_number) + # + # @return [RightBiased::Right,RightBiased::Left] + def flatten: () -> untyped + + # Combines the wrapped value with another monadic value. + # If both values are right-sided, yields a block and passes a tuple + # of values there. If no block given, returns a tuple of values wrapped with + # a monadic structure. + # + # @example + # include Dry::Monads::Result::Mixin + # + # Success(3).and(Success(5)) # => Success([3, 5]) + # Success(3).and(Failure(:not_a_number)) # => Failure(:not_a_number) + # Failure(:not_a_number).and(Success(5)) # => Failure(:not_a_number) + # Success(3).and(Success(5)) { |a, b| a + b } # => Success(8) + # + # @param mb [RightBiased::Left,RightBiased::Right] + # + # @return [RightBiased::Left,RightBiased::Right] + def and: (untyped mb) -> untyped + + # Pattern matching + # + # @example + # case Success(x) + # in Success(Integer) then ... + # in Success(2..100) then ... + # in Success(2..200 => code) then ... + # end + # + # @api private + def deconstruct: () -> untyped + + # Pattern matching hash values + # + # @example + # case Success(x) + # in Success(code: 200...300) then :ok + # in Success(code: 300...400) then :redirect + # in Success(code: 400...500) then :user_error + # in Success(code: 500...600) then :server_error + # end + # + # @api private + def deconstruct_keys: (untyped keys) -> untyped + + private + + # @api private + def destructure: (untyped value) -> untyped + + # @api private + def curry: () -> untyped + end + + # Left/wrong/erroneous part + # + # @api public + module Left + # @private + # @return [String] Caller location + def self.trace_caller: () -> untyped + + # Raises an error on accessing internal value + def value!: () -> untyped + + # Ignores the input parameter and returns self. It exists to keep the interface + # identical to that of {RightBiased::Right}. + # + # @return [RightBiased::Left] + def bind: () -> untyped + + # Ignores the input parameter and returns self. It exists to keep the interface + # identical to that of {RightBiased::Right}. + # + # @return [RightBiased::Left] + def tee: () -> untyped + + # Ignores the input parameter and returns self. It exists to keep the interface + # identical to that of {RightBiased::Right}. + # + # @return [RightBiased::Left] + def fmap: () -> untyped + + # Left-biased #bind version. + # + # @example + # Dry::Monads.Left(ArgumentError.new('error message')).or(&:message) # => "error message" + # Dry::Monads.None.or('no value') # => "no value" + # Dry::Monads.None.or { Time.now } # => current time + # + # @return [Object] + def or: () -> untyped + + # Returns the passed value. Works in pair with {RightBiased::Right#|}. + # + # @param alt [RightBiased::Right, RightBiased::Left] + # + # @return [RightBiased::Right, RightBiased::Left] + def |: (untyped alt) -> untyped + + # A lifted version of `#or`. This is basically `#or` + `#fmap`. + # + # @example + # Dry::Monads.None.or_fmap('no value') # => Some("no value") + # Dry::Monads.None.or_fmap { Time.now } # => Some(current time) + # + # @return [RightBiased::Left, RightBiased::Right] + def or_fmap: () -> untyped + + # Returns the passed value + # + # @return [Object] + def value_or: (?untyped val) -> untyped + + # Ignores the input parameter and returns self. It exists to keep the interface + # identical to that of {RightBiased::Right}. + # + # @return [RightBiased::Left] + def apply: () -> untyped + + # Returns self back. It exists to keep the interface + # identical to that of {RightBiased::Right}. + # + # @return [RightBiased::Left] + def discard: () -> untyped + + # Returns self back. It exists to keep the interface + # identical to that of {RightBiased::Right}. + # + # @return [RightBiased::Left] + def flatten: () -> untyped + + # Returns self back. It exists to keep the interface + # identical to that of {RightBiased::Right}. + # + # @return [RightBiased::Left] + def and: (untyped _) ?{ (?) -> untyped } -> untyped + + # Pattern matching + # + # @example + # case Success(x) + # in Success(Integer) then ... + # in Success(2..100) then ... + # in Success(2..200 => code) then ... + # in Failure(_) then ... + # end + # + # @api private + def deconstruct: () -> untyped + + # Pattern matching hash values + # + # @example + # case Failure(x) + # in Failure(code: 400...500) then :user_error + # in Failure(code: 500...600) then :server_error + # end + # + # @api private + def deconstruct_keys: (untyped keys) -> untyped + end + end + end +end + diff --git a/sig/dry/monads/task.rbs b/sig/dry/monads/task.rbs new file mode 100644 index 0000000..4b60e33 --- /dev/null +++ b/sig/dry/monads/task.rbs @@ -0,0 +1,192 @@ + + +module Dry + module Monads + # The Task monad represents an async computation. The implementation + # is a rather thin wrapper of Concurrent::Promise from the concurrent-ruby. + # The API supports setting a custom executor from concurrent-ruby. + # + # @api public + class Task + # @api private + class Promise < ::Concurrent::Promise + end + + # Creates a Task from a block + # + # @overload new(promise) + # @param promise [Promise] + # @return [Task] + # + # @overload new(&block) + # @param block [Proc] a task to run + # @return [Task] + def self.new: (?untyped promise) ?{ (?) -> untyped } -> untyped + + # Creates a Task with the given executor + # + # @example providing an executor instance, using Ruby 2.5+ syntax + # IO = Concurrent::ThreadPoolExecutor.new + # Task[IO] { do_http_request } + # + # @example using a predefined executor + # Task[:fast] { do_quick_task } + # + # @param executor [Concurrent::AbstractExecutorService,Symbol] + # Either an executor instance + # or a name of predefined global + # from concurrent-ruby + # + # @return [Task] + def self.[]: (untyped executor) ?{ (?) -> untyped } -> untyped + + # Returns a completed task from the given value + # + # @overload pure(value) + # @param value [Object] + # @return [Task] + # + # @overload pure(&block) + # @param block [Proc] + # @return [Task] + def self.pure: (?untyped value) ?{ (?) -> untyped } -> untyped + + # Returns a failed task from the given exception + # + # @param exc [Exception] + # @return [Task] + def self.failed: (untyped exc) -> untyped + + # @api private + attr_reader promise: untyped + + # @api private + def initialize: (untyped promise) -> untyped + + # Retrieves the value of the computation. + # Blocks current thread if the underlying promise + # hasn't been complete yet. + # Throws an error if the computation failed. + # + # @return [Object] + # @api public + def value!: () -> untyped + + # Lifts a block over the Task monad. + # + # @param block [Proc] + # @return [Task] + # @api public + def fmap: () ?{ (?) -> untyped } -> untyped + + # Composes two tasks to run one after another. + # A more common name is `then` exists as an alias. + # + # @param block [Proc] A block that yields the result of the current task + # and returns another task + # @return [Task] + def bind: () ?{ (?) -> untyped } -> untyped + + # @return [String] + def to_s: () -> untyped + + # Tranforms the error if the computation wasn't successful. + # + # @param block [Proc] + # @return [Task] + def or_fmap: () ?{ (?) -> untyped } -> untyped + + # Rescues the error with a block that returns another task. + # + # @param block [Proc] + # @return [Object] + def or: () ?{ (?) -> untyped } -> untyped + + # Extracts the resulting value if the computation was successful + # otherwise yields the block and returns its result. + # + # @param block [Proc] + # @return [Object] + def value_or: () ?{ (?) -> untyped } -> untyped + + # Blocks the current thread until the task is complete. + # + # @return [Task] + def wait: (?untyped timeout) -> untyped + + # Compares two tasks. Note, it works + # good enough only for complete tasks. + # + # @return [Boolean] + def ==: (untyped other) -> untyped + + # Whether the computation is complete. + # + # @return [Boolean] + def complete?: () -> untyped + + # @return [Class] + def monad: () -> untyped + + # Returns self. + # + # @return [Maybe::Some, Maybe::None] + def to_monad: () -> untyped + + # Applies the stored value to the given argument. + # + # @example + # Task. + # pure { |x, y| x ** y }. + # apply(Task { 2 }). + # apply(Task { 3 }). + # to_maybe # => Some(8) + # + # @param val [Task] + # @return [Task] + def apply: (?untyped val) ?{ (?) -> untyped } -> untyped + + # Maps a successful result to Unit, effectively discards it + # + # @return [Task] + def discard: () -> untyped + + private + + # @api private + def curry: (untyped value) -> untyped + + # @api private + def compare_promises: (untyped x, untyped y) -> untyped + + # Task constructors. + # + # @api public + module Mixin + # @private + Task: untyped + + # @see Dry::Monads::Unit + Unit: untyped + + # Created a mixin with the given executor injected. + # + # @param executor [Concurrent::AbstractExecutorService,Symbol] + # @return [Module] + def self.[]: (untyped executor) -> untyped + + # Task constructors + module Constructors + # Builds a new Task instance. + # + # @param block [Proc] + # @return Task + def Task: () ?{ (?) -> untyped } -> untyped + end + + include Constructors + end + end + end +end + diff --git a/sig/dry/monads/transformer.rbs b/sig/dry/monads/transformer.rbs new file mode 100644 index 0000000..d6d9713 --- /dev/null +++ b/sig/dry/monads/transformer.rbs @@ -0,0 +1,33 @@ + + +module Dry + module Monads + # Advanced tranformations. + module Transformer + # Lifts a block/proc over the 2-level nested structure. + # This is essentially fmap . fmap (. is the function composition + # operator from Haskell) or the functor instance for + # a two-level monadic structure like List Either. + # + # @example + # List[Right(1), Left(1)].fmap2 { |x| x + 1 } # => List[Right(2), Left(1)] + # Right(None).fmap2 { |x| x + 1 } # => Right(None) + # + # @param args [Array] arguments will be passed to the block or the proc + # @return [Object] some monadic value + def fmap2: (*untyped args) -> untyped + + # Lifts a block/proc over the 3-level nested structure. + # + # @example + # List[Right(Some(1)), Left(Some(1))].fmap3 { |x| x + 1 } + # # => List[Right(Some(2)), Left(Some(1))] + # Right(None).fmap3 { |x| x + 1 } # => Right(None) + # + # @param args [Array] arguments will be passed to the block or the proc + # @return [Object] some monadic value + def fmap3: (*untyped args) -> untyped + end + end +end + diff --git a/sig/dry/monads/traverse.rbs b/sig/dry/monads/traverse.rbs new file mode 100644 index 0000000..2790874 --- /dev/null +++ b/sig/dry/monads/traverse.rbs @@ -0,0 +1,11 @@ + + +module Dry + module Monads + # List of default traverse functions for types. + # It is implicitly used by List#traverse for + # making common cases easier to handle. + Traverse: ::Hash[untyped, untyped] + end +end + diff --git a/sig/dry/monads/try.rbs b/sig/dry/monads/try.rbs new file mode 100644 index 0000000..3661825 --- /dev/null +++ b/sig/dry/monads/try.rbs @@ -0,0 +1,213 @@ + + +module Dry + module Monads + # Represents a value which can be either success or a failure (an exception). + # Use it to wrap code that can raise exceptions. + # + # @api public + class Try + # @private + DEFAULT_EXCEPTIONS: untyped + + # @return [Exception] Caught exception + attr_reader exception: untyped + + # Invokes a callable and if successful stores the result in the + # {Try::Value} type, but if one of the specified exceptions was raised it stores + # it in a {Try::Error}. + # + # @param exceptions [Array] list of exceptions to rescue + # @param f [#call] callable object + # @return [Try::Value, Try::Error] + def self.run: (untyped exceptions, untyped f) -> untyped + + # Wraps a value with Value + # + # @overload pure(value, exceptions = DEFAULT_EXCEPTIONS) + # @param value [Object] value for wrapping + # @param exceptions [Array] list of exceptions to rescue + # @return [Try::Value] + # + # @overload pure(exceptions = DEFAULT_EXCEPTIONS, &block) + # @param exceptions [Array] list of exceptions to rescue + # @param block [Proc] value for wrapping + # @return [Try::Value] + def self.pure: (?untyped value, ?untyped exceptions) ?{ (?) -> untyped } -> untyped + + # Safely runs a block + # + # @example using Try with [] and a block (Ruby 2.5+) + # include Dry::Monads::Try::Mixin + # + # def safe_db_call + # Try[DatabaseError] { db_call } + # end + # + # @param exceptions [Array] + # @return [Try::Value,Try::Error] + def self.[]: (*untyped exceptions) ?{ (?) -> untyped } -> untyped + + # Returns true for an instance of a {Try::Value} monad. + def value?: () -> untyped + + # Returns true for an instance of a {Try::Error} monad. + def error?: () -> untyped + + # Returns self. + # + # @return [Try::Value, Try::Error] + def to_monad: () -> untyped + + # Represents a result of a successful execution. + # + # @api public + class Value < Try + include RightBiased::Right + + # @return [Array] List of exceptions to rescue + attr_reader catchable: untyped + + # @param exceptions [Array] list of exceptions to be rescued + # @param value [Object] the value to be stored in the monad + def initialize: (untyped exceptions, untyped value) -> untyped + + # Calls the passed in Proc object with value stored in self + # and returns the result. + # + # If proc is nil, it expects a block to be given and will yield to it. + # + # @example + # success = Dry::Monads::Try::Value.new(ZeroDivisionError, 10) + # success.bind(->(n) { n / 2 }) # => 5 + # success.bind { |n| n / 0 } # => Try::Error(ZeroDivisionError: divided by 0) + # + # @param args [Array] arguments that will be passed to a block + # if one was given, otherwise the first + # value assumed to be a Proc (callable) + # object and the rest of args will be passed + # to this object along with the internal value + # @return [Object, Try::Error] + def bind: () -> untyped + + # Does the same thing as #bind except it also wraps the value + # in an instance of a Try monad. This allows for easier + # chaining of calls. + # + # @example + # success = Dry::Monads::Try::Value.new(ZeroDivisionError, 10) + # success.fmap(&:succ).fmap(&:succ).value # => 12 + # success.fmap(&:succ).fmap { |n| n / 0 }.fmap(&:succ).value # => nil + # + # @param args [Array] extra arguments for the block, arguments are being processes + # just as in #bind + # @return [Try::Value, Try::Error] + def fmap: () -> untyped + + # @return [String] + def to_s: () -> untyped + + # Ignores values and returns self, see {Try::Error#recover} + # + # @param errors [Class] List of Exception subclasses + # + # @return [Try::Value] + def recover: (*untyped _errors) ?{ (?) -> untyped } -> untyped + end + + # Represents a result of a failed execution. + # + # @api public + class Error < Try + include RightBiased::Left + + # @param exception [Exception] + def initialize: (untyped exception) -> untyped + + # @return [String] + def to_s: () -> untyped + + # If a block is given passes internal value to it and returns the result, + # otherwise simply returns the first argument. + # + # @example + # Try(ZeroDivisionError) { 1 / 0 }.or { "zero!" } # => "zero!" + # + # @param args [Array] arguments that will be passed to a block + # if one was given, otherwise the first + # value will be returned + # @return [Object] + def or: (*untyped args) -> untyped + + # @param other [Try] + # @return [Boolean] + def ===: (untyped other) -> untyped + + # Acts in a similar way to `rescue`. It checks if + # {exception} is one of {errors} and yields the block if so. + # + # @param errors [Class] List of Exception subclasses + # + # @return [Try::Value] + def recover: (*untyped errors) -> untyped + end + + # A module that can be included for easier access to Try monads. + # + # @example + # class Foo + # include Dry::Monads::Try::Mixin + # + # attr_reader :average + # + # def initialize(total, count) + # @average = Try(ZeroDivisionError) { total / count }.value + # end + # end + # + # Foo.new(10, 2).average # => 5 + # Foo.new(10, 0).average # => nil + module Mixin + # @see Dry::Monads::Try + Try: untyped + + # @private + module Constructors + # A convenience wrapper for {Monads::Try.run}. + # If no exceptions are provided it falls back to StandardError. + # In general, relying on this behaviour is not recommended as it can lead to unnoticed + # bugs and it is always better to explicitly specify a list of exceptions if possible. + # + # @param exceptions [Array] + # @return [Try] + def Try: (*untyped exceptions) ?{ (?) -> untyped } -> untyped + end + + include Constructors + + # Value constructor + # + # @overload Value(value) + # @param value [Object] + # @return [Try::Value] + # + # @overload Value(&block) + # @param block [Proc] a block to be wrapped with Value + # @return [Try::Value] + def Value: (?untyped value, ?untyped exceptions) ?{ (?) -> untyped } -> untyped + + # Error constructor + # + # @overload Error(value) + # @param error [Exception] + # @return [Try::Error] + # + # @overload Error(&block) + # @param block [Proc] a block to be wrapped with Error + # @return [Try::Error] + def Error: (?untyped error) ?{ (?) -> untyped } -> untyped + end + end + end +end + diff --git a/sig/dry/monads/unit.rbs b/sig/dry/monads/unit.rbs new file mode 100644 index 0000000..1c507cc --- /dev/null +++ b/sig/dry/monads/unit.rbs @@ -0,0 +1,23 @@ + + +module Dry + module Monads + # 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) + Unit: untyped + end +end + diff --git a/sig/dry/monads/validated.rbs b/sig/dry/monads/validated.rbs new file mode 100644 index 0000000..5de814c --- /dev/null +++ b/sig/dry/monads/validated.rbs @@ -0,0 +1,219 @@ + + +module Dry + module Monads + # Validated is similar to Result and represents an outcome of a validation. + # The difference between Validated and Result is that the former implements + # `#apply` in a way that concatenates errors. This means that the error type + # has to have `+` implemented (be a semigroup). This plays nice with arrays and lists. + # Also, List#traverse implicitly uses a block that wraps errors with + # a list so that you don't have to do it manually. + # + # @example using with List + # List::Validated[Valid('London'), Invalid(:name_missing), Invalid(:email_missing)] + # # => Invalid(List[:name_missing, :email_missing]) + # + # @example with valid results + # List::Validated[Valid('London'), Valid('John')] + # # => Valid(List['London', 'John']) + class Validated + # Wraps a value with `Valid`. + # + # @overload pure(value) + # @param value [Object] value to be wrapped with Valid + # @return [Validated::Valid] + # + # @overload pure(&block) + # @param block [Object] block to be wrapped with Valid + # @return [Validated::Valid] + def self.pure: (?untyped value) ?{ (?) -> untyped } -> untyped + + # Returns self. + # + # @return [Validated::Valid, Validated::Invalid] + def to_monad: () -> untyped + + # Bind/flat_map is not implemented + def bind: () -> untyped + + # Valid result + class Valid < Validated + def initialize: (untyped value) -> untyped + + # Extracts the value + # + # @return [Object] + def value!: () -> untyped + + # Applies another Valid to the stored function + # + # @overload apply(val) + # @example + # Validated.pure { |x| x + 1 }.apply(Valid(2)) # => Valid(3) + # + # @param val [Validated::Valid,Validated::Invalid] + # @return [Validated::Valid,Validated::Invalid] + # + # @overload apply + # @example + # Validated.pure { |x| x + 1 }.apply { Valid(4) } # => Valid(5) + # + # @yieldreturn [Validated::Valid,Validated::Invalid] + # @return [Validated::Valid,Validated::Invalid] + def apply: (?untyped val) ?{ (?) -> untyped } -> untyped + + # Lifts a block/proc over Valid + # + # @overload fmap(proc) + # @param proc [#call] + # @return [Validated::Valid] + # + # @overload fmap + # @param block [Proc] + # @return [Validated::Valid] + def fmap: (?untyped proc) ?{ (?) -> untyped } -> untyped + + # Ignores values and returns self, see {Invalid#alt_map} + # + # @return [Validated::Valid] + def alt_map: (?untyped _) ?{ (?) -> untyped } -> untyped + + # Ignores arguments, returns self + # + # @return [Validated::Valid] + def or: (?untyped _) ?{ (?) -> untyped } -> untyped + + # @return [String] + def inspect: () -> untyped + + # @param other [Object] + # @return [Boolean] + def ===: (untyped other) -> untyped + end + + # Invalid result + class Invalid < Validated + # The value stored inside + # + # @return [Object] + attr_reader error: untyped + + # Line where the value was constructed + # + # @return [String] + # @api public + attr_reader trace: untyped + + def initialize: (untyped error, ?untyped trace) -> untyped + + # Collects errors (ignores valid results) + # + # @overload apply(val) + # @param val [Validated::Valid,Validated::Invalid] + # @return [Validated::Invalid] + # + # @overload apply + # @yieldreturn [Validated::Valid,Validated::Invalid] + # @return [Validated::Invalid] + def apply: (?untyped val) ?{ (?) -> untyped } -> untyped + + # Lifts a block/proc over Invalid + # + # @overload alt_map(proc) + # @param proc [#call] + # @return [Validated::Invalid] + # + # @overload alt_map + # @param block [Proc] + # @return [Validated::Invalid] + def alt_map: (?untyped proc) ?{ (?) -> untyped } -> untyped + + # Ignores the passed argument and returns self + # + # @return [Validated::Invalid] + def fmap: (?untyped _) ?{ (?) -> untyped } -> untyped + + # Yields the given callable and returns the result + # + # @overload or(proc) + # @param proc [#call] + # @return [Object] + # + # @overload or + # @param block [Proc] + # @return [Object] + def or: (?untyped proc) ?{ (?) -> untyped } -> untyped + + # @return [String] + def inspect: () -> untyped + + # @param other [Object] + # @return [Boolean] + def ===: (untyped other) -> untyped + end + + # Mixin with Validated constructors + module Mixin + # Successful validation result + # @see Dry::Monads::Validated::Valid + Valid: untyped + + # Unsuccessful validation result + # @see Dry::Monads::Validated::Invalid + Invalid: untyped + + # Actual constructor methods + module Constructors + # Valid constructor + # + # @overload Valid(value) + # @param value [Object] + # @return [Valdated::Valid] + # + # @overload Valid(&block) + # @param block [Proc] + # @return [Valdated::Valid] + def Valid: (?untyped value) ?{ (?) -> untyped } -> untyped + + # Invalid constructor + # + # @overload Invalid(value) + # @param value [Object] + # @return [Valdated::Invalid] + # + # @overload Invalid(&block) + # @param block [Proc] + # @return [Valdated::Invalid] + def Invalid: (?untyped value) ?{ (?) -> untyped } -> untyped + end + + include Constructors + end + end + + extend Validated::Mixin::Constructors + + # @see Validated::Valid + Valid: untyped + + # @see Validated::Invalid + Invalid: untyped + + class Result + class Success < Result + # Transforms to Validated + # + # @return [Validated::Valid] + def to_validated: () -> untyped + end + + class Failure < Result + # Transforms to Validated + # + # @return [Validated::Invalid] + def to_validated: () -> untyped + end + end + end +end + diff --git a/sig/dry/monads/version.rbs b/sig/dry/monads/version.rbs new file mode 100644 index 0000000..00a2a79 --- /dev/null +++ b/sig/dry/monads/version.rbs @@ -0,0 +1,9 @@ + + +module Dry + module Monads + # Gem version + VERSION: ::String + end +end + diff --git a/sig/json/add/dry/monads/maybe.rbs b/sig/json/add/dry/monads/maybe.rbs new file mode 100644 index 0000000..d3844e4 --- /dev/null +++ b/sig/json/add/dry/monads/maybe.rbs @@ -0,0 +1,21 @@ + + +# Inspired by standard library implementation +# for Time serialization/deserialization see (json/lib/json/add/time.rb) +module Dry + module Monads + class Maybe + # Deserializes JSON string by using Dry::Monads::Maybe#lift method + def self.json_create: (untyped serialized) -> untyped + + # Returns a hash, that will be turned into a JSON object and represent this + # object. + def as_json: (*untyped, **untyped) -> untyped + + # Stores class name (Dry::Monads::Maybe::Some or Dry::Monads::Maybe::None) + # with the monad value as JSON string + def to_json: () -> untyped + end + end +end + diff --git a/sig/manifest.yaml b/sig/manifest.yaml new file mode 100644 index 0000000..5514b6a --- /dev/null +++ b/sig/manifest.yaml @@ -0,0 +1 @@ +dependencies: [] \ No newline at end of file From b0f76578aa806bb71f1d89af50df6f5bd0436adb Mon Sep 17 00:00:00 2001 From: Aaron Allen Date: Thu, 23 Oct 2025 16:59:11 -0500 Subject: [PATCH 3/4] Add signatures for lib/dry/monads.rb --- lib/dry/monads.rb | 3 +++ sig/dry/monads.rbs | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/dry/monads.rb b/lib/dry/monads.rb index f7e6645..1ca2d03 100644 --- a/lib/dry/monads.rb +++ b/lib/dry/monads.rb @@ -14,6 +14,7 @@ module Dry # @api public module Monads # @api private + # @rbs () -> Zeitwerk::Loader def self.loader @loader ||= ::Zeitwerk::Loader.new.tap do |loader| root = ::File.expand_path("..", __dir__) @@ -31,6 +32,7 @@ def self.loader end # @private + # @rbs (::Class | ::Module base) -> void def self.included(base) if all_loaded? base.include(*constructors) @@ -68,6 +70,7 @@ def self.included(base) # @param [Array] monads # @return [Module] # @api public + # @rbs (*::Symbol monads) -> void def self.[](*monads) monads.sort! @mixins.fetch_or_store(monads.hash) do diff --git a/sig/dry/monads.rbs b/sig/dry/monads.rbs index a02ca66..b28a73b 100644 --- a/sig/dry/monads.rbs +++ b/sig/dry/monads.rbs @@ -6,10 +6,10 @@ module Dry # @api public module Monads # @api private - def self.loader: () -> untyped + def self.loader: () -> Zeitwerk::Loader # @private - def self.included: (untyped base) -> untyped + def self.included: (::Class | ::Module base) -> void # Build a module with cherry-picked monads. # It saves a bit of typing when you add multiple @@ -40,7 +40,7 @@ module Dry # @param [Array] monads # @return [Module] # @api public - def self.[]: (*untyped monads) -> untyped + def self.[]: (*::Symbol monads) -> void end end From 12a5746ff432a9d1216081ae2633e2b64f6f70b7 Mon Sep 17 00:00:00 2001 From: Aaron Allen Date: Thu, 23 Oct 2025 18:13:16 -0500 Subject: [PATCH 4/4] Add signatures for lib/dry/monads/do/all.rb --- lib/dry/monads/do/all.rb | 9 ++++++++- sig/dry/monads/do/all.rbs | 16 ++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/dry/monads/do/all.rb b/lib/dry/monads/do/all.rb index 57a01c9..6a98d1b 100644 --- a/lib/dry/monads/do/all.rb +++ b/lib/dry/monads/do/all.rb @@ -57,8 +57,9 @@ module Do module All # @private class MethodTracker < ::Module - attr_reader :wrappers + attr_reader :wrappers #: ::Hash[::Class | ::Module, ::Module] + # @rbs (::Hash[::Class | ::Module, ::Module] wrappers) -> void def initialize(wrappers) super() @@ -86,11 +87,13 @@ def included(base) end end + # @rbs (::Class | ::Module target) -> void def extend_object(target) super target.prepend(wrappers[target]) end + # @rbs (::Class | ::Module target, ::Symbol method) -> void def wrap_method(target, method) visibility = Do.method_visibility(target, method) Do.wrap_method(wrappers[target], method, visibility) @@ -99,6 +102,7 @@ def wrap_method(target, method) class << self # @api private + # @rbs (::Class | ::Module base) -> void def included(base) super @@ -110,6 +114,7 @@ def included(base) end # @api private + # @rbs (::Class | ::Module klass, ::Module target) -> void def wrap_defined_methods(klass, target) klass.public_instance_methods(false).each do |m| Do.wrap_method(target, m, :public) @@ -128,6 +133,7 @@ def wrap_defined_methods(klass, target) # @api private module InstanceMixin # @api private + # @rbs (::Class | ::Module object) -> void def extended(object) super @@ -156,6 +162,7 @@ def extended(object) if ::Gem::Version.new(::RUBY_VERSION) >= ::Gem::Version.new("3.4.0") ::Warning.singleton_class.prepend(::Module.new { + # @rbs (::String message, ?category: ::Symbol?, **__todo__) -> void def warn(message, category: nil, **) if message.include?("lib/dry/monads/do.rb") && message.include?("warning: the block passed to") diff --git a/sig/dry/monads/do/all.rbs b/sig/dry/monads/do/all.rbs index baeeb44..4c617a1 100644 --- a/sig/dry/monads/do/all.rbs +++ b/sig/dry/monads/do/all.rbs @@ -56,32 +56,32 @@ module Dry module All # @private class MethodTracker < ::Module - attr_reader wrappers: untyped + attr_reader wrappers: ::Hash[::Class | ::Module, ::Module] - def initialize: (untyped wrappers) -> untyped + def initialize: (::Hash[::Class | ::Module, ::Module] wrappers) -> void - def extend_object: (untyped target) -> untyped + def extend_object: (::Class | ::Module target) -> void - def wrap_method: (untyped target, untyped method) -> untyped + def wrap_method: (::Class | ::Module target, ::Symbol method) -> void end # @api private - def self.included: (untyped base) -> untyped + def self.included: (::Class | ::Module base) -> void # @api private - def self.wrap_defined_methods: (untyped klass, untyped target) -> untyped + def self.wrap_defined_methods: (::Class | ::Module klass, ::Module target) -> void # @api private module InstanceMixin # @api private - def extended: (untyped object) -> untyped + def extended: (::Class | ::Module object) -> void end extend InstanceMixin end end - def warn: (untyped message, ?category: untyped, **untyped) -> untyped + def warn: (::String message, ?category: ::Symbol?, **__todo__) -> void end end