4 ms·
The problem with closures is that they are opaque. The only way to interact with them is to call them. Making them the foundation of your system eliminates a lo
by jerf 5y ago
The problem with closures is that they are opaque. The only way to interact with them is to call them. Making them the foundation of your system eliminates a lot of useful architectural patterns, such as putting filters on the event stream, being able to prioritize some of them based on certain criteria, being able to ship them between different servers if necessary, creating a generic logger that can operate on the enumeration values without having to have each closure log things, implementing various security checks (you really don't want a security system to be stuck only being able to run a bit of code to see if it's safe), just a whole lot of things that can't be done if all you have are closures.
If you don't care about your values leaving your local OS process, which is a pretty common use case, there's a hybrid approach you can take too, which is to put closures inside your data structures that describe the value, instead of passing raw closures around. You get the benefits of being able to examine the values without executing the handler and being able to filter, decorate, etc. but while retaining all the advantages of being able to implement handlers inline. Depending on your local language, various slick implementations may allow this to be one degree or another of transparent, such as implementing some sort of interface/trait for all these values, or implementing __call__ in Python, etc.
"And by the way, defunctionalization sounds a lot like transforming my code into a bytecode interpreter which interprets my code. This is something I don't need and which would make my code slower and more difficult to read."
It depends on the language you're in. Most OO languages have some way of doing this that doesn't add any speed issues to speak of, it's just a slight rearrangement of code.
I'd also say this is an issue of scale. I don't bother with this in small programs and may just glue everything together with closures, but as program size increases the odds that you'll want to do something that you can't do with closures directly increase. But the languages I tend to work in don't make this much of a hassle usually, either. I think you may be overestimating the expense.
- verdagon 5y agoBy opaque, do you mean encapsulated? Encapsulation is pretty important to software architecture, in that it enforces decoupling between components. Rust has private visibility for this very reason. The filtering you're talking about sounds interesting, but the GUI layer is (imo) definitely the wrong place to do it. When's the last time you wanted to filter, prioritize, secure, or distribute UI events? Imagine if we transformed every method call in our app into enums, it would be an unwieldy and unreadable mess. I'm not saying you're suggesting that, just highlighting that there are definitely some drawbacks here. So, I don't see why we're being forced to use enums in this case. I wonder if this a conscious decision by the Iced folks, and if they first tried with closures and ran into some trouble. Your hybrid idea is interesting! Is that doable with Rust in practice? I'd imagine the borrow checker might enforce it can't read from outside its scope.
- jerf 5y ago"By opaque, do you mean encapsulated?" By opaque, I mean the only thing you can do is execute them. While this is encapsulated, it isn't at the right level of control. " but the GUI layer is (imo) definitely the wrong place to do it. When's the last time you wanted to filter, prioritize, secure, or distribute UI events?" Ironically, the GUI is already that way. Under the hood, enumerated values were passed to your program by the windowing system. It has to be, because there's a serialization layer there. GUI programs already have no choice but to be structured this way, and indeed, also certainly have some sort of filtering mechanism in them to do things like ignore events it doesn't want to handle. You're probably programming at an abstraction layer that is already sitting above that. You think this isn't useful in a GUI precisely because the GUI framework you're using implemented this pattern for you already! "Your hybrid idea is interesting! Is that doable with Rust in practice?" Really it's just creating objects that have various inspectable attributes as needed, that also have some trait or something that allows them to be called in a uniform manner. It's not too hard, it's just a bit more structure around what is already there; you basically copy & paste the closure into the method of a new object, and make the capture explicit in the object.