Skip to content

Latest commit

 

History

History
617 lines (419 loc) · 19.9 KB

CHANGELOG.md

File metadata and controls

617 lines (419 loc) · 19.9 KB

1.6.0 2022-11-04

Changed

  • This version uses dry-core 1.0 (@flash-gordon)

Compare v1.5.0...v1.6.0

1.5.0 2022-10-16

Changed

  • Use zeitwerk for auto-loading dry-monads classes (@flash-gordon)
  • Task#then is deprecated in favor of Task#bind (@flash-gordon)
  • Minimal Ruby version is now 2.7 (@flash-gordon)
  • Either (old name of Result) was removed (@flash-gordon)

Compare v1.4.0...v1.5.0

1.4.0 2021-07-20

Added

  • Unit destructures to an empty array (flash-gordon)
  • When .value! called on a Failure value the error references to the value (rewritten + flash-gordon)
    begin
      Failure("oops").value!
    rescue => error
      error.receiver # => Failure("oops")
    end
  • Result#alt_map for mapping failure values (flash-gordon)
    Failure("oops").alt_map(&:upcase) # => Failure("OOPS")
  • Try#recover recovers from errors (flash-gordon)
    error = Try { Hash.new.fetch(:missing) }
    error.recover(KeyError) { 'default' } # => Try::Value("default")
  • Maybe#filter runs a predicate against the wrapped value. Returns None if the result is false (flash-gordon)
    Some(3).filter(&:odd?)  # => Some(3)
    Some(3).filter(&:even?) # => None
    # no block given
    Some(3 == 5).filter     # => None
  • RightBiased#| is an alias for #or (flash-gordon)
    None() | Some(6) | Some(7) # => Some(6)
    Failure() | Success("one") | Success("two") # => Success("one")

Fixed

  • Do notation preserves method visibility (anicholson + flash-gordon)

Changed

  • Coercing nil values to None with Some#fmap is officially deprecated. (flash-gordon) Switch to Some#maybe when you expect nil. This behavior will be dropped in 2.0 but you can opt out of warnings for the time being
    Dry::Monads::Maybe.warn_on_implicit_nil_coercion false
  • Minimal Ruby version is 2.6

Compare v1.3.5...v1.4.0

1.3.5 2020-01-06

Added

  • Smarter keys deconstruction in pattern matching (flash-gordon)

Compare v1.3.4...v1.3.5

1.3.4 2019-12-28

Fixed

  • One more delegation warning happenning in do notation (flash-gordon)

Compare v1.3.3...v1.3.4

1.3.3 2019-12-11

Fixed

  • Incompatibility with Rails. Internal (!) halt exceptions now use mutable backtraces because spring mutates (!) them. For the record, this a bug in Rails (johnmaxwell)

Compare v1.3.2...v1.3.3

1.3.2 2019-11-30

Added

  • Pattern matching syntax was improved by implementing #deconstruct_keys. Now curly braces aren't necessary when the wrapped value is a Hash (flash-gordon)
    case result
    in Success(code: 200...300) then :ok
    end
  • Internal

  • Performance of do notation was improved for failing cases (1.2x to 1.3x on synthetic benchmarks) (flash-gordon)

Fixed

  • Warnings about keywords from Ruby 2.7 (flash-gordon)

Compare v1.3.1...v1.3.2

1.3.1 2019-09-07

Fixed

  • Added missing None#maybe 😅 (flash-gordon)

Compare v1.3.0...v1.3.1

1.3.0 2019-08-03

Added

  • Result#either (waiting-for-dev)

    Success(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 2
    Failure(1).either(-> x { x + 1 }, -> x { x + 2 }) # => 3
  • Maybe#to_result (SpyMachine + flash-gordon)

    Some(3).to_result(:no_value)   # => Success(3)
    None().to_result { :no_value } # => Failure(:no_value)
    None().to_result               # => Failure()
  • Do notation can be used with extend. This simplifies usage in class methods and in other "complicated" cases (gogiel + flash-gordon)

    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

    Or you can bind values directly:

    ma = Dry::Monads.Success(1)
    mb = Dry::Monads.Success(2)
    
    Dry::Monads::Do.() do
      a = Dry::Monads::Do.bind(ma)
      b = Dry::Monads::Do.bind(mb)
    
      Dry::Monads.Success(a + b)
    end
  • {Some,Success,Failure}#[] shortcuts for building arrays wrapped within monadic value (flash-gordon)

    Success[1, 2] # => Success([1, 2])
  • List.unfold yields a block returning Maybe<Any>. If the block returns Some(a) a is appended to the output list. Returning None halts the unfloding (flash-gordon)

    List.unfold(0) do |x|
      if x > 5
        None()
      else
        Some[x + 1, 2**x]
      end
    end # => List[1, 2, 3, 4, 5]
  • Experimental support for pattern matching! 🎉 (flash-gordon)

    case value
    in Failure(_) then :failure
    in Success(10) then :ten
    in Success(100..500 => code) then code
    in Success() then :empty
    in Success(:code, x) then x
    in Success[:status, x] then x
    in Success({ status: x }) then x
    in Success({ code: 200..300 => x }) then x
    end

    Read more about pattern matching in Ruby:

    Keep in mind this feature is experimental and can be changed by 2.7 release. But it rocks already!

Compare v1.2.0...v1.3.0

1.2.0 2019-01-12

Added

  • Most of constructors now have call alias so you can compose them with Procs nicely if you've switched to Ruby 2.6 (flash-gordon)

    pipe = -> x { x.upcase } >> Success
    pipe.('foo') # => Success('FOO')
  • List#collect gathers Some values from the list (flash-gordon)

    include Dry::Monads::List::Mixin
    include Dry::Monads::Maybe::Mixin
    # ...
    List[10, 5, 0].collect do |divisor|
      if divisor.zero?
        None()
      else
        Some(n / divisor)
      end
    end
    # => List[4, 2]

    Without block:

    List[Some(5), None(), Some(3)].collect.map { |x| x * 2 }
    # => [10, 6]
  • Right-biased monads got #flatten and #and (falsh-gordon)

    #flatten removes one level of monadic structure, it's useful when you're dealing with things like Maybe of Maybe of something:

    include Dry::Monads::Maybe::Mixin
    
    Some(Some(1)).flatten # => Some(1)
    Some(None()).flatten # => None
    None().flatten # => None

    In contrast to Array#flatten, dry-monads' version removes only 1 level of nesting, that is always acts as Array#flatten(1):

    Some(Some(Some(1))).flatten # => Some(Some(1))

    #and is handy for combining two monadic values and working with them at once:

    include Dry::Monads::Maybe::Mixin
    
    # using block
    Some(5).and(Some(3)) { |x, y| x + y } # => Some(8)
    # without block
    Some(5).and(Some(3)) # => Some([5, 3])
    # other cases
    Some(5).and(None()) # => None()
    None().and(Some(5)) # => None()
  • Concise imports with Dry::Monads.[]. You're no longer required to require all desired monads and include them one-by-one, the [] method handles it for you (flash-gordon)

    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
  • Task.failed is a counterpart of Task.pure, accepts an exception and returns a failed task immediately (flash-gordon)

Compare v1.1.0...v1.2.0

1.1.0 2018-10-16

Added

  • Success(), Failure(), and Some() now have Unit as a default argument:

    include Dry::Monads::Result::Mixin
    include Dry::Monads::Do
    
    def call
      yield do_1
      yield do_2
    
      Success() # returns Success(Unit)
    end

Fixed

  • Do notation was made to work nicely with inheritance. This shouldn't break any existing code but if it does please report (flash-gordon)

Compare v1.0.1...v1.1.0

1.0.1 2018-08-11

Added

  • to_proc was added to value constructors (flash-gordon)
    [1, 2, 3].map(&Some) # => [Some(1), Some(2), Some(3)]

Fixed

  • Fixed behavior of List<Validated>#traverse in presence of Valid values (flash-gordon + SunnyMagadan)

Compare v1.0.0...v1.0.1

1.0.0 2018-06-26

Added

  • do-like notation (the idea comes from Haskell of course). This is the biggest and most important addition to the release which greatly increases the ergonomics of using monads in Ruby. Basically, almost everything it does is passing a block to a given method. You call yield on monads to extract the values. If any operation fails i.e. no value can be extracted, the whole computation is halted and the failing step becomes a result. With Do you don't need to chain monadic values with fmap/bind and block, everything can be done on a single level of indentation. Here is a more or less real-life example:

    class CreateUser
      include Dry::Monads
      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])
    
          Success(user)
        end
      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

    In the code above any yield can potentially fail and return the failure reason as a result. In other words, yield None acts as return None. Internally, Do uses exceptions, not return, this is somewhat slower but allows to detect failed operations in DB-transactions and roll back the changes which far more useful than an unjustifiable speed boost (flash-gordon)

  • The Task monad based on Promise from the concurrent-ruby gem. Task represents an asynchronous computation which can be (doesn't have to!) run on a separated thread. Promise already offers a good API and implemented in a safe manner so dry-monads just adds a monad-compatible interface for it. Out of the box, concurrent-ruby has three types of executors for running blocks: :io, :fast, :immediate, check out the docs for details. You can provide your own executor if needed (flash-gordon)

    include Dry::Monads::Task::Mixin
    
    def call
      name = Task { get_name_via_http }    # runs a request in the background
      email = Task { get_email_via_http }  # runs another one request in the background
    
      # to_result forces both computations/requests to complete by pausing current thread
      # returns `Result::Success/Result::Failure`
      name.bind { |n| email.fmap { |e| create(e, n) } }.to_result
    end

    Task works perfectly with Do

    include Dry::Monads::Do.for(:call)
    
    def call
      name, email = yield Task { get_name_via_http }, Task { get_email_via_http }
      Success(create(e, n))
    end
  • Lazy is a copy of Task that isn't run until you ask for the value for the first time. It is guaranteed the evaluation is run at most once as opposed to lazy assignment ||= which isn't synchronized. Lazy is run on the same thread asking for the value (flash-gordon)

  • Automatic type inference with .typed for lists was deprecated. Instead, typed list builders were added

    list = List::Task[Task { get_name }, Task { get_email }]
    list.traverse # => Task(List['John', 'john@doe.org'])

    The code above runs two tasks in parallel and automatically combines their results with traverse (flash-gordon)

  • Try got a new call syntax supported in Ruby 2.5+

      Try[ArgumentError, TypeError] { unsafe_operation }

    Prior to 2.5, it wasn't possible to pass a block to [].

  • The Validated “monad” that represents a result of a validation. Suppose, you want to collect all the errors and return them at once. You can't have it with Result because when you traverse a List of Results it returns the first value and this is the correct behavior from the theoretical point of view. Validated, in fact, doesn't have a monad instance but provides a useful variant of applicative which concatenates the errors.

      include Dry::Monads
      include Dry::Monads::Do.for(:call)
    
      def call(input)
        name, email = yield [
          validate_name(input[:name]),
          validate_email(input[:email])
        ]
    
        Success(create(name, email))
      end
    
      # can return
      # * Success(User(...))
      # * Invalid(List[:invalid_name])
      # * Invalid(List[:invalid_email])
      # * Invalid(List[:invalid_name, :invalid_email])

    In the example above an array of Validated values is implicitly coerced to List::Validated. It's supported because it's useful but don't forget it's all about types so don't mix different types of monads in a single array, the consequences are unclear. You always can be explicit with List::Validated[validate_name(...), ...], choose what you like (flash-gordon).

  • Failure, None, and Invalid values now store the line where they were created. One of the biggest downsides of dealing with monadic code is lack of backtraces. If you have a long list of computations and one of them fails how do you know where did it actually happen? Say, you've got None and this tells you nothing about what variable was assigned to None. It makes sense to use Result instead of Maybe and use distinct errors everywhere but it doesn't always look good and forces you to think more. TLDR; call .trace to get the line where a fail-case was constructed

    Failure(:invalid_name).trace # => app/operations/create_user.rb:43
  • Dry::Monads::Unit which can be used as a replacement for Success(nil) and in similar situations when you have side effects yet doesn't return anything meaningful as a result. There's also the .discard method for mapping any successful result (i.e. Success(?), Some(?), Value(?), etc) to Unit.

      # we're making an HTTP request but "forget" any successful result,
      # we only care if the task was complete without an error
      Task { do_http_request }.discard
      # ... wait for the task to finish ...
      # => Task(valut=Unit)
  • Deprecations

  • Either, the former name of Result, is now deprecated

  • BREAKING CHANGES

  • Either#value and Maybe#value were both droped, use value_or or value! when you 💯 sure it's safe

  • require 'dry/monads' doesn't load all monads anymore, use require 'dry/monads/all' instead or cherry pick them with require 'dry/monads/maybe' etc (timriley)

Compare v0.4.0...v1.0.0

0.4.0 2017-11-11

Added

  • Try#or, works as Result#or (flash-gordon)
  • Maybe#success? and Maybe#failure? (aliases for #some? and #none?) (flash-gordon)
  • Either#flip inverts a Result value (flash-gordon)
  • List#map called without a block returns an Enumerator object (flash-gordon)
  • Right-biased monads (Maybe, Result, and Try) now implement the === operator which is used for equality checks in the case statement (flash-gordon)
      case value
      when Some(1..100)       then :ok
      when Some { |x| x < 0 } then :negative
      when Some(Integer)      then :invalid
      else raise TypeError
      end
  • Deprecated

  • Direct accessing value on right-biased monads has been deprecated, use the value! method instead. value! will raise an exception if it is called on a Failure/None/Error instance (flash-gordon)

Changed

  • The Either monad was renamed to Result which sounds less nerdy but better reflects the purpose of the type. Either::Right became Result::Success and Either::Left became Result::Failure. This change is backward-compatible overall but you will see the new names when using old Left and Right methods (citizen428)
  • Consequently, Try::Success and Try::Failure were renamed to Try::Value and Try::Error (flash-gordon)

Compare v0.3.1...v0.4.0

0.3.1 2017-03-18

Fixed

  • Fixed unexpected coercing to Hash on .bind call (flash-gordon)

Compare v0.3.0...v0.3.1

0.3.0 2017-03-16

Added

  • Added Either#either that accepts two callbacks, runs the first if it is Right and the second otherwise (nkondratyev)
  • Added #fmap2 and #fmap3 for mapping over nested structures like List Either and Either Some (flash-gordon)
  • Added Try#value_or (dsounded)
  • Added the List monad which acts as an immutable Array and plays nice with other monads. A common example is a list of Eithers (flash-gordon)
  • #bind made to work with keyword arguments as extra parameters to the block (flash-gordon)
  • Added List#traverse that "flips" the list with an embedded monad (flash-gordon + damncabbage)
  • Added #tee for all right-biased monads (flash-gordon)

Compare v0.2.1...v0.3.0

0.2.1 2016-11-13

Added

  • Added Either#tee that is similar to Object#tap but executes the block only for Right instances (saverio-kantox)

Fixed

  • Right(nil).to_maybe now returns None with a warning instead of failing (orisaka)
  • Some#value_or doesn't require an argument because None#value_or doesn't require it either if a block was passed (flash-gordon)

Compare v0.2.0...v0.2.1

0.2.0 2016-09-18

Added

  • Added Maybe#to_json as an opt-in extension for serialization to JSON (rocknruby)
  • Added Maybe#value_or which returns you the underlying value with a fallback in a single method call (dsounded)

Compare v0.1.1...v0.2.0

0.1.1 2016-08-25

Fixed

  • Added explicit requires of dry-equalizer. This allows to safely load only specific monads (artofhuman)

Compare v0.1.0...v0.1.1

0.1.0 2016-08-23

Added

  • Support for passing extra arguments to the block in .bind and .fmap (flash-gordon)

Changed

  • Dropped MRI 2.0 support (flash-gordon)

Compare v0.0.2...v0.1.0

0.0.2 2016-06-29

Added

  • Added Either#to_either so that you can rely on duck-typing when you work with different types of monads (timriley)
  • Added Maybe#to_maybe for consistency with #to_either (flash-gordon)

Compare v0.0.1...v0.0.2

0.0.1 2016-05-02

Initial release containing Either, Maybe, and Try monads.