• I’m seeing a parallel to when Perl tried to be something else. There was this library called Moose. And it was really needed when you needed to build a webapp. Ruby has a great DSL and package ecosystem, where something like Hamami can be so expressive and high level. But the Result type basically, seems to be trying to modify the language. It reminds me of Moose. Moose didn’t happen because you had “Perl Moose” vs “Perl”. I know this isn’t the same scale because Result, Option isn’t the same as trying to add OOP to Perl. But I do wonder if someone will add all the Dry stuff to Ruby.

    Rust has Maybe and Result so it doesn’t have to do this. But then, it doesn’t have the same DSL style. And then really my point goes back to the growing a language talk. Should dry-rb be a library? Maybe not, there’s no exhaustive pattern matching compile step. 🤷🏻‍♂️


    score 1 · canonical

    • Never knew there was a Perl Moose vs Perl divide. I only knew that Moose was one of the few object systems with a Meta Object Protocol, which I found really cool. It seems that Perl is not adding an object system into core named object::Pad?


      score 1 · canonical

    • I think when people make this argument, they are confusing a prior emphasis of OOP with language design. Ruby has always been a marriage of OOP and Functional, and it’s worth emphasizing those features more.

      “Modifying the language” has also long been a tradition in Ruby, e.g. ActiveSupport.


      score 1 · canonical

      • Whether it’s extending a language or not is a red herring. Dry-monad let’s us treats errors as values (result monad?). This is very useful. As it let’s use compose operations, etc. But it does go against the grain of the Ruby ecosystem, where exceptions are used for control flow. E.j. how many http client gems raise on 4xx or 5xx?

        Does using dry-rb mean that one needs to ensure their dependencies aren’t using exceptions or write wrappers/adapters to enforce the errors as values pattern?

        I know from other languages (Rust) that errors as values are great. But I also had a bad experience working with Ruby. Where they tried to force adoption of errors as values. But they did so with an anemic implementation instead of using dry-monad. And it definitely felt like an uphill battle. Hopefully Hanakai’s ideas grow in mindshare in the Ruby ecosystem.


        score 1 · canonical

        • Dry-monad let’s us treats errors as values (result monad?). This is very useful. As it let’s use compose operations, etc.

          The particular phrasing “errors as values” seems to come from Go, Pike in particular, unless I’m mistaken and he got it from somewhere. But I find this description to be really poor. Exceptions are values, what this is actually describing is the locality of control-flow: when you receive an error, can you be certain that it came from the code you’re interacting with? The nasty thing about exceptions is that any point in the stack can raise, and this makes it exponentially difficult to comprehend what possible exceptions there are.

          Does using dry-rb mean that one needs to ensure their dependencies aren’t using exceptions or write wrappers/adapters to enforce the errors as values pattern?

          dry-rb is a toolkit that contains many useful things. They work well together, but you can generally use them independently as well. This question relates to dry-monads specifically. The answer is, yes when I write an Operation object with a monadic interface I capture exceptions inside it and translate them into Result monads. This is very easy to do. It makes the Operation object a boundary where I can actually provide a level of certainty that exceptions raised are truly exceptional.


          score 1 · canonical

          • But I find this description to be really poor. Exceptions are values, what this is actually describing is the locality of control-flow.

            That’s a good point. I used that phrasing as I expect it easier to understand that result monad, but it is conveying the wrong thing we care about, which is treating the error path a part of the normal control flow.


            The answer is, yes when I write an Operation object with a monadic interface I capture exceptions inside it and translate them into Result monads. This is very easy to do. It makes the Operation object a boundary where I can actually provide a level of certainty that exceptions raised are truly exceptional.

            I know it is straight-foward to map things, but it is hard to do so exhaustively as Ruby gems use exceptions for all sorts of things. So one either has to convert all StandardError exceptions or have to deal with the problems of using exceptions for control flow. Maybe the wide-net approach doesn’t lead to any issues?


            score 1 · canonical

            • Some libraries are more well-behaved than others and define a top-level Error class. Those that don’t (looking at you, jwt) are annoying but often just tagging what step in the process raised is sufficient to know how to handle it. I usually have an all-else-fails rescue for StandardError that I log for better handling.

              The benefit is that you get to choose how granular your handling needs to be. Going all-in on exceptions-as-control-flow leads to an explosion of possible branching paths that is unsustainable; but using Results allows you to compress layers into fewer choices.

              I view the tradeoff here as similar to using case…in pattern matching: okay sure it’s not exhaustive in the same way as other languages, but it’s still a meaningful improvement.


              score 1 · canonical

      • Yeah, libraries. The library I’d point to that “modifies the language” more than ActiveSupport would be something like event machine. And so my question is more along the lines of did it work. I would say ActiveSupport worked. But EM didn’t and neither did Moose. Because you’re trying to make a library that changes the paradigm. EM: reactor, Moose: OOP.

        Why didn’t EM work imo? Well, you had to find em-libraries for everything that hit that I/O or scheduling seam. So you had em-this, em-that. I consider this different than something that depended on ActiveSupport but this is just my thought-parallel.


        score 1 · canonical

        • I think my pushback on this standpoint is that it essentially posits that we can never learn from our mistakes and get better, that languages are what they are and cannot change, and that using different constructs requires a language change.

          Anyone who’s been writing Ruby for a while understands that languages do change. The boundary between language experiments in a codebase to libraries to language support is porous. I will remind you: symbol-to-proc was just one sort of experiment that was elevated to a language feature.


          score 1 · canonical

          • lobsters__squarism@crustaceanson.theshelf.topM
            link
            fedilink
            arrow-up
            1
            ·
            edit-2
            9 days ago

            I see it more from Joe Armstrong’s three laws of programming design. The things you messed up, those things don’t change. Matz “isn’t a threading guy” so you get EM. Ruby 7.0 could be compile to binary, some very different concurrency model as 1st and only citizen, pure functions or whatever other characteristics that would be significant enough for comparison and uniqueness to other languages but it probably won’t be. Whatever the reason.

            Yes, Ruby has changed. I would say types most recently but I don’t know if it’s been successful. Has adding static types to Ruby been successful? Has it been complete? Is it an addon or type of thing like EM? I don’t know. “Oh, this repo is trying [ Reactor | type safety | pure functions ]” but it can’t commit to the paradigm because it’s like a soft-style inside of a language choice.

            I don’t mean the syntax sugar or small things. I mean concurrency, paradigm, memory model, packaging, runtime performance tier as compared with other languages. I don’t think Ruby itself has changed, I can’t think of any language that has “shipped” is constrained by legacy that has changed a paradigm category. I think this is what Joe meant, or it’s my interpretation.

            1. “What you get right, nobody mentions it.”
            2. “What you get wrong, people [complain] about.”
            3. “What is difficult to understand you have to explain to people over and over again.”

            And so, if things are changeable and you learned a lesson then the language could change. But it doesn’t because of backwards compatibility. But also, the language itself has made a choice. In the major categories of language differentiation, you’re in one camp. I’m trying to think of a language that has switched camps, the language itself. Not a library. This is my point. Making a library to switch camps is imo historically difficult because of what Joe said.


            score 1 · canonical

            • In the major categories of language differentiation, you’re in one camp. I’m trying to think of a language that has switched camps, the language itself. Not a library. This is my point. Making a library to switch camps is imo historically difficult because of what Joe said.

              I addressed this point in my original reply: Ruby is not fully in the OOP camp and never was. If you have that sense, it was because the OOP features previously got more emphasis than the functional ones. But Ruby began as a LISP VM, its functional bona fides are not new, and a greater emphasis on FP in Ruby is not “switching camps.” It’s a false binary.


              score 1 · canonical