Boilerplate busting in functional languages
lambdaland.org
lambdaland.org
> A functional programmer is one who calls their variables `f` and `x`, and their design patterns "zygohistomorphic prepromorphism".
You might want to take a look at F#, which has no typeclasses (yet) but does have excellent first-class support for monadic syntax via "computation expressions".
https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...
https://gist.github.com/williamcotton/95ebc21965e393f4c99a59...
Maybe a “string list list”?
Ursula Le Guin translating the Tao Te Ching, verse 2.
What? Why would you consider using global state for this? I'd do it like this. Mutability without globals.
export function transform(ast) {
const queries = [];
walk(ast.root, queries);
return { ast, queries };
}
function walk(node, queries) {
// recursively called on child nodes
// can mutate node, and append to queries
}
> Surprise! This is exactly the writer monad! This whole post has been a monad tutorial in disguise!Aha. That explains why it stopped making sense about 6 paragraphs ago.
walk can mutate node. transform can invoke walk. transform is public.
This is why I presumed this post is using the popular understanding of the term.
Anyway, nodes are only public if the calling code makes them public by leaking a reference or something.
> Don’t forget about all the other problems that global mutable state brings. There must be a better way.
When this blog says "global mutable state", then it must be referring to what's commonly understood to be "mutable state". It seems to be an article of faith that there must be a better way. To my sensibility, that is the best way. The argument seems to rest on some sleight-of-hand regarding "global".
Reaching in to access variables was bad, but if you used a getter instead, then that was somehow good because you were doing encapsulation?
IDEs gained the ability to generate your getters and setters for you, C# had properties which streamlined things and made Java look boilerplatey in comparison, and Java got Lombok.
But it was all bullshit. If making fields private is 'locking the door', then adding getters and setters is 'leaving the key on top of the doormat'.
Show me a logic bug caused by a global variable, I'll slap some getters and setters onto it, give it back to you, and it will still have the same logic bug.
What's my point?
It's not that FP has some competing definition of 'global variables' that somehow makes FP look good and imperative look bad, it's that FP actually followed the idea through to its logical conclusion and generalised it as 'shared mutable state', rather than slapping "encapsulation" on it and calling it a day.
> Anyway, nodes are only public if the calling code makes them public by leaking a reference or something.
This is true. However people commonly leak things by putting 'export' in front of them. Otherwise how can callers access them?
I guess the modern conception of FP says 'all values and state are best modeled immutably'. Whereas I tend to think it's only some of them.
I'd still be a little worried about your above solution though—I'd have to be sure of the state semantics in $LANGUAGE and I'd want to make sure the compiler wouldn't share copies of `queries` between invocations. (Maybe that's a weird thing to worry about, but having written compilers it's something that crosses my mind.) Also, I might also worry about that state leaking in other ways. Mutating Python's default arguments comes to mind.
Moreover, some languages (like Elixir in this post) don't even give you the option of having semi-protected local mutable state like the above. (Yes, I know about the process dictionary. No, I don't want to use that—my PR would be rejected immediately by all my coworkers if I did that.)
The point is, this feels like something that shouldn't necessitate having mutable state anywhere, so here's how to get to that nice, pure functional solution without sacrificing readability with tons of boilerplate.
The benefit of immutability is that values can never change "out from under you". But there seems to be a counter-point, which I haven't seen articulated much. In cases where object reference identities are meaningful, all references to an object are always current. Think video game characters running around in an arena interacting with each other.
I think immutability is a great tool for domains where it's helpful. That's the same position I have regarding mutability. I think the mutable Date object in javascript was a mistake. I also think it's hard to compete with the simplicity of a recursive `walk()` function that mutates the AST nodes and query queue in place.
Maybe I'll see the light when I manage to comprehend what a monad is.
They only make sense in a functional language that has support for “type wrappers”, eg, Result, Option, or custom made types like a ResultWriter.
The gist is you define a bind function that takes two arguments (a white lie), the first one of those “wrapped values” and the second a “function that takes an unwrapped value, does something, and returns a wrapped value”, and then the bind function “unwraps the value from the first arg and calls the second arg with that unwrapped value”.
This lets you chain together these “functions that take a value, does something, and then returns a wrapped value” and if you can define an infix operator it makes for for legible code that’s easy to read and modify. It looks even better in F#’s computation expression syntactic sugar.
Of course I am now going to try to explain this better with Yet Another Monad Tutorial to torture the internet with…
Like this in JS:
let f = (x) => (y) => x * y;
let p = f(2)(3);
Is this in F#: let f x y = x * y
let p = f 2 3
A ResultWriter is just a wrapper type that takes a tuple of a Result and a list of log messages.The side effects are you can write imperative code that works with normal values but does exception handling and log messaging behind the scenes and without it cluttering the task at hand.
So you write some functions that return either Success wrapping a value with some good news log message or an Error with some bad news log message. Then with a bind function or especially with a computation expression in F# you can use those functions without dealing with the exceptions and log messages until after you’ve completed all of the steps.
Unless you’re using a language that supports this kind of monad design pattern it won’t really make any sense!
I'm not much of a JS guy, but isn't mutable objects the norm, not the exception? Over in Java land we have the mutable ArrayList and the mutable HashSet etc.
> Maybe I'll see the light when I manage to comprehend what a monad is.
That's not going to happen.
First you need to see a problem (e.g. a mutable object being a mistake.)
Then you need to see a range of solutions.
Then the best solution needs to be a particular Thing.
Then that Thing needs to resemble other different Things which solve other problems.
If those different solutions resemble one another enough, you can put them under a common interface Monad.
But if there is no problem with a mutable AST object or a mutable Date object, then you won't even consider the solutions. A monad won't solve the problem you don't have.
In the case of business logic and Elixir, this is where you may need to write your own DSL to model domain logic (DDD). It makes it makes things more flexible and more maintainable than piling together mud and hoping it won't fall over and that someone else in the future will be able to reason about it and maintain it.
What I'm saying is, this problem shouldn't need state to solve it, therefore any introduction of state is a hack that will be a headache to maintain, test, and reason about.
> In the case of business logic and Elixir, this is where you may need to write your own DSL to model domain logic (DDD). It makes it makes things more flexible and more maintainable than piling together mud and hoping it won't fall over and that someone else in the future will be able to reason about it and maintain it.
I don't know what you're referring to here. What problem would a DSL solve (and how?), and what do you consider to be the mud that's being piled on?
Business rules are often represented as an ordering of constraints and triggers in models when using DDD & MVC. One example in another language would be trailblazer.[0]
Mud is when gobs of fragile, low-level of code is slapped together just to solve today's problem where everything is a special case without testability, architecture, standardization, or concern for maintenance or extensibility.
See also: [1]
You're right. GenServers are wonderful and simple. I'm just saying this problem doesn't need them.
> Use the patterns…
I am. Monads are just another pattern. You know the `with {:ok, ...} <- ... else ... end` pattern in Elixir? That's another monad!
I show one concrete example here in my post that monads cover well. I've come across other scenarios in industry where monads can cut down on boilerplate better than anything else. Would another solution work? Sure—it's a Turing-complete language. Would it be as clean? Sometimes not.
> Business rules, mud
I don't need definitions—I understand those concepts just fine. I'm just trying to understand why you think it's relevant to my post. What's the specific "mud" here?
If you like monads, maybe you should look into Haskell or Idris.
Please let me know when you write an interesting post that solves a class of problems rather than getting excited over something so trivial. Perhaps I'm jaded. Good luck to you.
This is a contradiction, unless I'm misunderstanding 'global', 'highly-available' and 'share-nothing'.