3 ms·
> This could work, but the fact that we are using global mutable state ... What? Why would you consider using global state for this? I'd do it like this. Mu
by recursive 2y ago
> This could work, but the fact that we are using global mutable state ...
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.
- mrkeen 2y agoThe label 'global mutable state' has a slightly broader reach in FP. Just because you don't see a 'static int' hanging out in the space between methods doesn't mean that you haven't given the whole world mutable access into your state. walk can mutate node. transform can invoke walk. transform is public.
- recursive 2y ago> Whenever we call our transform function, we would have to ensure that we clear out the old list of accumulated information. 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".
- mrkeen 2y agoRemember 20 years ago, when getters and setters were all the rage? 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?
- recursive 2y agoI do remember. I never really bought into the dogma about property accessors. Sometimes they are good. But mutable object fields might be fine too. In fact, I even think goto is appropriate in some circumstances. 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.
- ashton314 2y agoYou're right that you could create some local state and mutate that. That would be a better solution than some dumb global variable sitting somewhere. I considered touching on that, but this was already a long blog post! 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.
- recursive 2y agoThanks for your response. 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.
- williamcotton 2y agoA monad is just a fancy name for a design pattern in functional programing that manages side effects so you can write what looks like imperative code but that does some other stuff behind the scenes, eg, logging, async, exception handling. 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…