1. 19

  2. 3

    I read the article top to bottom, but, I have to confess, the sales pitch about the benefits did not come across to me. It’s not at all clear that there’s enough value obtained from paying the complexity cost (n lines to replace Time.now?). Maybe this is awesome, but the elevator pitch has to be improved [in order to reach people like me].

    1. 4

      The short elevator pitch is that this lets you make large swaths of code pure by getting control over the places where data and effect are entering and leaving an operation.

      A pure operation f(x) is trivially tested and composed because we know that all of the inputs are contained in x and all of the outputs are passed through the return. In some sense, this function lives happily within a container and can be easily replaced/moved/composed/tested.

      An impure operation f(x) now may consume input from any number of places and may create output effects in any number of places. Generally speaking, you don’t know whether there is implicit context required to make f(x) work. Even if you do know that such context exists, it’s hard to know everything that’s required to make it work. This is the “test suite only runs in Germany” example Skade mentions—Germany accidentally became part of the context.

      Getting explicit control over effects enables you to eventually write code where it is easy to determine all of the context and all of the outputs necessary for execution.

      1. 1

        The pot of gold sounds well and good, but I think I need some concrete examples showing “here’s how $operation is traditionally done; but oh no, $bad_thing happens, see?” and “when you do it with this shiny new dry-effects thingie, $bad_thing is gone, see?”

        I admit there are examples in the OP, but they’re either hard to follow or not compelling. They don’t leap out at me as “wow, what a simple, elegant solution to a clear problem that I am definitely experiencing in my codebases today”. They come across a lot more as “gee, this is rather a lot more lines, and significantly more complexity, and if I can’t even grok it within 10 minutes of pondering, and discussion on Lobsters, how am I going to convince my team that this is worth adopting?”

        1. 2

          I don’t know enough about Ruby to make compelling Ruby examples. Additionally, most of these examples are not painkillers, they’re vitamins. You do something less compelling now for future benefits. It’s just a harder sell.

          There are immediate benefits, though. They’re a bit of an acquired taste. The immediate benefits I know from other languages where this style is possible are (a) dramatically less anxiety about code, anxiety I didn’t even know I had and (b) fearlessness around refactoring. To be clear, (b) is also facilitated by types, but purity is a big element as well.

          Specifically, it’s less direct to write but translates to less things to be concerned about over longer periods of time. That’s the win.

      2. 1

        Please note that the actual two crucial lines are:

        class CreateSubscription
          include Dry::Effects.CurrentTime # < this
          def call(values)
            subscription_repo.create(values.merge(start_at: current_time)) # < this

        So n equals 1 for the user.

        The implementation of the effect is provided as a library.

        Factoring out Time.now makes a lot of sense, because Time is a side effect that makes code hard to test. There’s tons of issues like “test suite doesn’t run over midnight” or “test suite only runs in Germany”. An effect system gives you the ability to know where “effects” happen and allows you to let replace them at certain situations, because you can pinpoint them.

        You can then start tests like “start a process at 5 minutes to midnight, and continue at 5 minutes past midnight, assuming that nothing breaks because your system knows how to deal with the date change”. Previous approaches to that worked by replacing the time library completely, which is a big hammer.

        1. 1

          I’m aware of the pitfalls of using the current date or time in tests, as I’ve hit them before. Nowadays, I tell my teams to explicitly test the edge cases (daylight saving time, leap years, different time zones, etc.) and set up the contexts with, as you mention, things like TimeCop or Rails’ recently-added time travel features. However, I don’t quite agree with you that they’re too big a hammer. They seem to be doing the job for me.

          1. 2

            Well, different approaches. Rails and timecop go the “let’s just monkeypatch a core library”, dry-effects implements a rather small version of “let’s make time a service”. The dry and rom projects are pretty opposed to monkeypatching and having been bitten by that in Rails multiple times, I tend to agree.

      3. 1

        (…) if code doesn’t perform any side effects, such as reading/writing data to the disc or network communications, the only thing it actually does is heating the CPU.

        Hey, heating the CPU still counts as a side effect of running code. It’s still impure! Yuck! 😏

        1. 1

          It mentions dependency injection. Is this supposed to replace dry-autoinject / dry-container?

          1. 3

            Nope, it offers a barebone DI mechanics. The existing Reslove effect works with dry-container out of the box. dry-effects also offers a strategy for dry-auto_inject that uses effects for resolution. If you use dry-auto_inject already, you can just switch the strategy and use effects under the hood. My current opinion is that pure dry-effects DI is fine for small apps but something larger would be more manageable with dry-system. It does all the heavy lifting, i.e. loading files, booting dependencies etc.