[0] https://exploring-better-ways.bellroy.com/designing-for-the-...
> We adopted a pragmatic approach: maintain the Ruby code but only port functionality to Haskell when we could add meaningful value in the process. This meant our Ruby codebase gradually became legacy code, maintained but not actively developed. This transition — which we expected to take a couple of years — has now stretched into its seventh year. > > Here’s where the Clean Architecture approach began to work against us. As we hired more Haskell-focused developers and our institutional knowledge of Ruby faded, those carefully crafted abstraction layers became archaeological puzzles. Reverse-engineering what a piece of code actually did — especially complex, multi-step operations with side effects — became a nightmare.
I'd say <1% of all developers world wide have even heard of Nix.
I have had one of their cardholders for 10 years now, it is incredible how durable and practical that thing is.
One funny thing about software is that beautiful things can emerge from the most unexpected places. I appreciate that there are folks out there with the bravery to share their journey.
A-a-and so we went from programming in Haskell to creating a new DSL, with an interpreter for it in Haskell, and programming in that DSL. Which kinda begs a question: you already have a perfectly serviceable programming language (i.e. Haskell) at hand; why not just use it?
Other programming traditions also have this pattern of inventing minilanguages and interpreters for them; regular expressions are by far the most successful example. You could hand-roll string matching by writing your matching functions by hand, but it's often much easier to ask a regex library to run an interpreter over a string describing the pattern to match.
In Haskell, it's really cheap to invent data structures, so using the same language to describe the work is quite convenient. Laziness also means you almost never materialise the entire intermediate "work to be done" structure - you build little bits as the interpreter demands them. So it doesn't feel as heavyweight as an eDSL in some other language.
Of course you can inspect it: open the source code you wrote and read it. Also, don't write the code you don't want to be executed?
> but it's often much easier to ask a regex library to run an interpreter over a string describing the pattern to match.
Which, I might notice, you never inspect. You execute it blindly and look at the outputs of the match() method falling out of it. In fact, most regex libraries compile your regex into an opaque data structure which you can't inspect — and nobody complain about it.
This is not what they meant by inspection.
What they mean is that you can write a function, in Haskell, that given a value in the DSL, it returns the list of all requests it will perform on execution.
This can be useful for tests, security, caching, performance, debugging...
It's not principally about convenience though is it? It's about defining the semantics of your program through the DSL. Then you can verify the program logic, prove properties about it if you wish. It is denotative.
What exactly you expected them do write?
It's just its main differentiator. But there's way more to the language.
I wonder how different the code would look if it was just written to deal with N things from the start.
I’m also not sure how far this code can go, if I have queries that depend on responses of preceding queries , how will my runAp_ give me this? It probably won’t.
always wondered where are http frameworks that just give me a batch of requests to deal with from the start.
It definitely won't, which is what I was trying to get at with the discussion of monads and data dependencies. Applicatives by definition cannot have one "effectful" computation depend on the result of another. You could do a large bunch of parallel work until you need to pass a result into a function that decides what additional work to perform, at which point you need a monad. More advanced frameworks like Haxl apparently make this distinction explicit, so your computation proceeds as a sequence of batched parallel options, combining as much work as possible.
These patterns constantly appear yet we continue writing code step by step.
I remember Gwyneth Paltrow said something along the line moving away from Shopify was the biggest mistake she made with her online shop. I think that was before Pandemic and Shopify have improved a lot since then.
Which makes me wonder if it make sense for Bellroy to continue their path.
Can you give an example of that happening?
> For an intuition why this is true, consider that the constant functor Const r has an Applicative instance whenever r is a monoid, because pure stores a mempty value and (<*>) combines the held values with (<>). For a fun exercise, implement runAp_ in terms of runAp and Const.
Really?