I mentioned in my Hello, world! post that I built this site using Hanami. This site may seem simple on the surface; it is, after all, just a blog. However, the backend is packed with features that help me day to day. I have built a tool that lets me cross-post to both Bluesky and Mastodon. I have a private journal where I can keep notes throughout the day. There is a custom analytics engine to provide me with insights on how well my blog posts are doing. There is a messaging backend that lets readers reach me through my contact form without cluttering my inbox. I have also built a task manager that syncs with both Linear and GitHub Issues and helps me track what I need to do. Most importantly, I have built an activity feed that captures my GitHub commits, journal entries, blog posts, and tasks. It helps me answer the question "What did I do between ___ and ___ dates?", which, believe it or not, is hard for me to answer on my own, even for last week. The application is open source, and you can find it on GitHub.
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.
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?
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.
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.
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
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.
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