lainchan / λ

Design patterns with functional programming

Acid Burn · 2024-12-04 16:14
Some days ago I saw a thread about an anon saying that hates OOP, SOLID and design patterns, and then I read some replies. One of them mentioned that OOP is unnecessarily complicated; a lot of steps to get a result (somewhat pic rel). So, one day, during my break, I thought about it: "What about functional programming?", so I searched and found this interesting material: https://mishadoff.com/blog/clojure-design-patterns/ https://dev.to/mrcaidev/design-patterns-in-functional-programming-4aah I program in C, so this is pretty interesting for me. I guess that design patterns are aplicable to FP. From de concept to the code. The keywords are: functions and pure functions. What do you think, /λ/?
cond · 2024-12-05 02:58
I personally believe too many FP evangelists will never be happy until we all use something that has no closures or side effects whatsoever and it will be a pain to use in practice and also I think you probably shouldn't outsource your architecture design to street soykafters so you don't get the right side picture. But even as a frequent OOP user I would also not recommend you to design anything like the left side image in practice since that leads to frequent issues which are avoidable by instead relying on interfaces and delegation. Component based designs also have their advantages. But what has become very popular the past years are hybrid approaches of having FP frameworks or language features which can be used from whatever Java enterprise project you're forced to work on to pay your bills and not end up in callback hell. That said this is not a diss on FP since I like to dabble with some personal projects in that style and it can be fun. But the issue with people pushing it that I've found is that they get way too much into that screwdriver vs hammer argument and need to accept that not everyone likes to write recursive loops all the time. Just take it easy, man and make sure you pick the right tool for the use case or the right project for your esoteric language, find the right people to help you out and make that thing more popular that way. And also hurry before all this stuff gets obsolete anyway by generative technologies that are controlled by brain implants which interface with our most basic caveman desires.
Backus · 2024-12-05 05:34
>>40584 Class-based, statically-typed OOP inevitably leads to something analogous to the right picture, regardless of how much you try to pretty it up in my experience. And I don't say this because I'm some massive OO hater, in fact I think there are use cases it is clearly best at. As it is the fundamental structure for message passing systems, complex GUIs and distributed networks work fantastically modeled as an object oriented system. However, in my opinion, most internal or processing systems are better represented using procedural or functional modeling. A separation of data and logic is the critical part here, the rest can quickly devolve into personal preference. I am a fan of semi-pure functional systems myself, using OCaml. Most software does not need robust message passing constructs, and the amount of work to make a system like that far exceeds the returns in most cases. Your first question should always be: What are you solving? If you are transforming data and running it through I/O streams, OOP will almost always be overkill compared to a procedural or functional pattern. >>40578 As for adoption of functional principles in non-functional languages like C, I would advise caution. Not because you can't, but because the C compiler is not equipped to optimize out a lot of things that FP takes advantage of. For example, reliance on immutable data structures work phenomenally in Haskell and OCaml, as their compilers know how to reuse data components at runtime (since they are immutable, they can be freely shared). Same with recursion, procedural iteration is all but required since tail call optimization does not exist in any C compiler I am aware of. That said, functional purity is a good thing to strive for in any language. It creates a software landscape of predictable, composable logic units. Since side effects are impossible, you can't accidentally wind up in a broken state machine.
Unidentified · 2024-12-05 06:57
>>40585 >Class-based, statically-typed OOP inevitably leads to something analogous to the right picture, regardless of how much you try to pretty it up in my experience. If by "class-based" you also mean without mixins/traits, without proper multiple inheritance (rarely needed, but when you do...), without more customization of method dispatch than "override" then yes. In fact, OOP and static typing aren't very good friends.
Cowboy · 2024-12-05 07:54
>>40587 Exactly so. Static typing and OOP pretty destructively interfere with each other. I loved my time with Smalltalk and Ruby by contrast, although I did not build things large enough to face notable maintenance challenges.
Fred Nurk · 2024-12-06 19:41
>>40585 Thing is, whatever you use and don't employ the correct patterns and architecture for your paradigm and use cases and it becomes complex enough then yeah, you will most certainly end up with a structure that's resembling a ball of yarn that even the initial author(s) will have a tough time untying that gordian knot. You can produce an unmanageable mess in Scala, Clojure, Elixir and so on if you just give the developers enough time, or rather lack thereof. And procedural is possibly the worst offender of that where you do need to be the original author to know about all the globals that are haunting the code base that's trading separation of concerns for whichever performance gains. So again, not soykafting on FP but that if you've got a hammer then you want to hit the nail and not your thumb. Do you follow? By now you can probably guess that I've been involved in various OOP projects of somewhat considerable complexities and there's a method to the madness as long as you do avoid things that come to bite you later. And, at least from my experience both those images are plain wrong. Inheritance is rarely used, and more rarely goes beyond 2 or 3 levels. Also have I seen less and less usage of the protected keyword and more emphasis on delegation which you can even use in procedural systems languages such as C and Zig. Hence the left image is not what you want to even start with and use Interfaces where you can to split up and modularize your project. Elixir has that concept of modules too which turns it into a quasi OOP language if you want to get philosophical. But that, and the usage of a type system is key to define proper contracts/APIs/whateveryoucall it to group things up by logic, purpose and abstraction level. So that while I enjoy doing certain types of projects in Javascript, namely prototyping, I would hate using it for anything that has more than a dozen source files and would insist on Typescript from that point on since knowing what type of data the interfaces you work with expect is already half the advantages. And the other is that instead of the right picture, where everything is seemingly connected to everything else in a convoluted manner you end up with something that looks more like a bunch of hot spots of fuzzy clusters with reasonable levels of convolution where you can work on one end without walking over someone else's feet who has been living in the opposite corner for the past month and a half. This is also the prime reason OOP is popular and will remain so in enterprise environments since developers are easier to swap out and aside from the one rock star guy who knows all the ins and outs, any support you hire really just needs to be intelligent enough to comprehend an additional layer of factories over everything which is really not that high of a bar. Also let me condense your explanation for the use cases: You want to use FP for anything that's a process, especially if it branches, threads and converges (server back ends, the request and storage parts of your software, balanced, parallelized and distributed systems). And you want OOP for anything that requires a complex set of contextual states to be preserved (UI or scene graphs in games, simulations and managing or editing software). And Procedural is essentially the meat of either, including the hacky globals (the data crunching and heavy maths parts of whatever you're doing). Or shorter: anything that flows like a river is functional and everything that looks like a solid tree is an object if you like to go all Feng Shui. >functional purity is a good thing to strive for in any language Again, functional purity is more of an academic endeavor but reducing the shotguns to the foot in any language, that's a good thing. For instance you always want to keep the amount of side effects low. But what matters more imho is how effective of a tool the language is for the range of tasks it was intended for. Something like OpenSCAD comes to mind. And I already told exactly what to do to push popularity and adaption of FP languages: get together, find good use cases and start a project around that. If it's good and solves a common enough problem, it will find integration and so will possibly the tooling that was utilized. There really is no need to be that hellbent on FP, especially when practical approaches to OOP still elude you after only having used Java in CS101. Just chill and keep making things. >>40587 >>40589 When talking about large code bases, the key is to have well defined interfaces between the individual components that are as self descriptive as possible. A dynamic language sounds like a horrible idea for that with the exception if you have pattern matching.
Lain Iwakura · 2024-12-08 04:50
>>40606 This was horrendously worded, in my view, but I think I have pieced together a generalized meaning. I have well over a decade of experience in OOP codebases of various complexities, and all advice and jargon used here have been mentioned as "best practice" in effectively each one. None of these concepts has helped make an object system readable when an object system is used as the hammer for anything vaguely resembling a nail. Object oriented systems that rely heavily on a contractual type system lead to a lot of interleaved coupling, which they then try to solve with complex runtime dependency injection schemes. It is horrendously complex, and fragile to boot. It also strays heavily away from the core tenant of OO: Message passing. In fact, it's why you may see a lot of people argue static typing is antithetical to OOP, because it restricts that core tenant so heavily. The large push away from inheritance in object systems also pushes away a fundamental tenant of the paradigm, and instead tries to model objects closer to stateful modules than actual objects. I would go as far as to argue most modern statically-typed OOP recommendations attempt to mangle the concept into something more akin to "procedural programming with advanced modules", which is better solved with programming languages that provide those concepts first class. >functional purity is more of an academic endeavor... I would not call it just an "academic endeavor". If you can break functions apart from their data (so eliminating OOP in this context), it is the jargon to describe an ideal function. It does not have to be attained across an entire piece of software, but it is a concept that will help anybody in ensuring code resiliency but also composability without the fragile landscape of OO dependency injection graphs. You can even argue that externally consistent functions, whose statefulness do not extend beyond function boundaries, can meet this expectation as well. If you were to say that pure applications, composed only of pure functions were academic endeavors, I would absolutely agree. >And I already told exactly what to do to push popularity and adaption of FP languages Will never happen, purely OO languages are also largely dead (see: Smalltalk, Simula, Self). The only languages that will maintain widespread market adoption are general-use, multi-paradigm languages. That is completely fine, and I in fact think that is for the best. Multi-paradigm languages have also spent the last decade grafting on functional systems as FP has been growing in popularity for a while now.
hash table · 2024-12-11 14:34
Just finished skimming "Effective Java". It gave the impression that even in "textbook" OOP there aren't many design principles in favor of inheritance. The example recall from the book was 23. "prefer class-hierarchies to tagged-classes" which seems to be a side-effect of lacking proper sum types. 18. "Favor composition over inheritance" is quite clear about this discouraging of inheritance claiming: "Unlike method invocation , inheritance violates encapsulation." and stating one should prioritize use of wrapper classes (or delegates), as was mentioned elsewhere in the thread. The book also emphasizes the use of lambdas over anonymous classes and encourages the use of the streams with some qualifications. Interfaces are emphasized throughout, even as an alternative to class hierarchies. If ever get around to it will be interested to compare the approach given in "Paradigms of Artificial Intelligence" chapter (unlucky) 13.
Dozer · 2024-12-11 16:56
>>40657 >If ever get around to it will be interested to compare the approach given in "Paradigms of Artificial Intelligence" chapter (unlucky) 13. It was on my window seal so went ahead and gave it a read. It didn't contain too much information but the basic impression one got is that CLOS emphasizes extensibility (or flexibility) over encapsulation. While reading "Effective Java" one also got the impression that encapsulation ought to be: Firstly more of a tool to maintain invariants, an essential restriction on the state space. Secondly encapsulation ought to be a tool to abstract implementation details from the caller, the exact manner and degree of such an abstraction is a case-by-case design decision. Encapsulation is not to be sought in itself, nor universal.
Neuromancer · 2024-12-12 07:49
And what about design patterns in structural programming?
Lady 3Jane.. · 2024-12-13 17:54
>>40658 Next up skimming "Design Patterns" and "Clojure Design Patterns" together, am about half way through. Am finding it much easier to "understand" (loosely) through "Clojure Design Patterns" than "Design Patterns". Essentially what "Clojure Design Patterns" is, is how you express object-orientated design patterns with methods (and structure) outside of the objects. Obviously the advantage of such a thing is access to methods (and structure) without a layer of indirection. Interfaces are not difficult to implement even in this paradigm. Imagine the advantages are less with prototype based object systems. One has to be careful not to rule out the patterns.
Hiro Protagonist · 2024-12-17 14:36
>>40659 Couldn't find anything online about this, it might just be algorithms and datastructures. >>40670 Finished skimming these two, but my conclusions remained roughly the same.
Batou · 2024-12-23 16:50
>>40731 >Couldn't find anything online about this, it might just be algorithms and datastructures. Question. I have read that using design patterns in structural programming can be a problem, because the compiler is not optimized for those paradigms. Is that true?
Stallman · 2025-01-05 02:48
>>40578 I don't think it's a good idea to look for "functional design patterns" to begin with, though there are patterns. It has a bad connotation as a cargo cult, because there seems to have been a long campaign (probably to sell books and training) pushing them in OOP to begin with (so it might be wrong to attack OOP for this reason, because you can start to see people doing it with FP). I think one of the most obvious things to come up when someone suggests a pattern in programming is why not abstract over it? And if it's not worth abstracting over, why call it a "design pattern"? E.g. consider "factories" or "factory functions" - these are normally just functions or closures (i.e. a function with some parameter baked in), sometimes the word is just used to indicate something like the result will be some subclass of a base class - but it always seems to me like there's nothing special about that, you know that from the return type of the function, so why not call it a function. One of the complaints I do think genuinely applies to classy OOP is how easy class methods make it to implicitly spread mutable state and effects around. In a way this is sort of the old problem with globals in C (compare to locals where you have to at least pass a pointer or reference), and of course in C you could always have a bunch of reference fields. Once this builds up parts of the state become basically uncontainable without gargantuan effort. If your FP code is pure FP then you can at least separate it out systematically, since f(x, y) = (x', y') = (f_x(x,y), f_y(x, y)). A lot of OOP ideas are not very compositional either, e.g. getters and setters make it harder to work with state in some cases. Even if it's not tangled in a web where it wouldn't make sense to do so, you can't directly set the state which is useful in some situations. You might already have some known valid state, or have several fields with invariants but have to do an awkward dance to set multiple fields to desired values (setting one of which would break an invariant). It's common to do void setX(int x) - how do I know if this succeeded or if it failed due to some invariant? What if I have some invariant and want to do setX(x) and setY(y) as one transaction, i.e. if either fails to set the state both should leave the old state? Exceptions are not a great solution either especially as most languages don't make it part of the signature (and what if the classes have listeners? maybe STM would work here). Then you get to the actual tangled web stuff - listeners and work queues and error logs and so on make it impossible to swap stuff out or change the context in which it runs without altering the behaviour or introducing unwanted extra activity. The order of listeners and when they fire matters, what else they touch matters. If something is queued onto a work queue and itself queues further work I can't just add something to the queue without an A1 B A2 situation. Basically all of these things have to be carefully controlled especially how they interact with lasting program state, and OOP is terribly bad at that (maybe there isn't anything better). I have another complaint that might not be OOP specific either, but also related to its popularity (in this case use in teaching). It kind of relates to the previous stuff - way too much focus on imitating natural language. I know class Animal is not realistic OOP, but people get far too comfortable in OOP with reasoning in natural language as if they're in a conversation with the compiler and it will work out what they mean. It's just a bad basis to reason about parts of the code with (even if something like it becomes essential at a certain level of complexity). Likewise emphasis on naming and so on when often a class really is just a specially shaped gear in a big machine and doesn't have any real meaning in an independent existence. I would be interested to see a Haskelly derivative of μμ (like duo lang but not based on subtyping) to see if some of this OO stuff would work better as codata (e.g. you can model exceptions this way, and anonymous classes in Java are a form of copattern matching) alongside FP code. If you look at it along this line it's easier to see where the difficulties reasoning about code are coming from, a command/effect is kind of like a place in which infinity can happen, and most classy procedural OO languages have that everywhere while FP heavily shies away and focuses on data and pure transformations in as much of the code as possible. Going back to FP patterns, the sort of patterns you see are often related to abstract mathematics like category theory e.g. adjunctions (monads, comonads), monoids in monoidal categories (applicative, alternative, divisible, ...), (co)algebra (f-algebra, fixpoints, ADTs), regular algebra (plain monoids, semigroups, groups, actions). Most of all of these are nice enough to seldom cause composition issues (at least not inherently), they are fairly transparent "this is a set of operations and nothing much else is done by them". Various type system features make it easier to constrain what a function is doing or write generic code (GADTs, HKTs, higher ranked types, ADT representation classes, ...) and then there's the ergonomics of everything. There are some more sort of heavy duty abstractions relating to effects that become ecosystems like pipes, FRP, certain effect systems where you start to see the similar issues as OOP in terms of managing it. TLDR maybe we still don't have good ways of programming effects in general
dictionary · 2025-01-09 03:31
design patterns are an explicitly OOP only thing OOP insists that your functions actually have to be taxonomized as """methods""" of some class. this doesn't even begin to make sense. what if you have a function that takes two objects of different classes? what if a 3rd party wants to make a function that works on your class (i'm well aware of the various """solutions""" to this such as traits and similar "features"). it's all downhill from there, and design patterns are just one of the many workarounds you need to work with these codebases.
Von Neumann · 2025-01-10 12:14
>>40902 Design patterns came from architecture. They're definitely not an OOP only thing.
Hitomi · 2025-01-10 14:11
>>40902 They certainly aren't. Also, reminder that you don't need an object oriented language to do OOP. (C.f. FILE*).
CarlosMZ · 2025-01-22 06:43
From my perspective, no matter the paradigm, if you don't plan ahead and don't properly document code, the project will always be a mess. Unfortunately, many people start coding without having a clear idea of what they want (myself included). I have seen all kinds of code bases that are a real headache.
Anonymous · 2025-01-24 11:05
>>40628 And I've been working with "flows" for a year now that's essentially an FP addon to java and kotlin and even then I am faced with the academic first mindset there when I had to unravel just today some fancy looking chain that's doing its job well but with certainly too many loops and container reallocations despite all the optimizations that are necessary to make it work efficiently into a simple for loop. And that's my issue with FP. It's a great paradigm to manage complex asynchronous data sequence, especially of uncertain frequency and to escape callback hell to which I never wish to return but sometimes it's simply the wrong tool. At the end of the day the goal in applied software development is to push 1s and 0s as fast as we can to the right places at the right time and also as infrequently as we need to in order to leave some computing power for other things. Everything beyond that is either quality of life features for the coder monkeys, safeguard mechanisms for orderly operation or personal intellectual musings of people who get dissatisfied or bored from just hacking something together until it meets the bare acceptance criteria of their task at hand.
Project 2501 · 2025-01-27 09:36
>>40902 >design patterns are an explicitly OOP only thing This claim is just false. In haskell we have the readerT patter and a bunch of different way of constructing effect program. This is why we have the mtl vs readerT pattern vs effecful conversation inside the comunity
Junichi Fujiwara · 2025-01-28 06:04
>>40578 After years of programming and experimenting with multiple paradigms and ideologies, I see no real usecase for anything more than procedural programming with classes. Why abstract away from machine logic to such an incredible degree where we don't think in algorithms anymore and instead we think in objects, monads and bloat our systems with immutable data collections that need to be fully copied to do anything? Procedural programming is simple, gets things done, is flexible and is closer to machine logic and so is easier to optimize (and less bloated by default). Classes are great when they are used responsibly: fully static classes serve as modules to contain code inside, improving code readability and navigation, while non-static classes are great to create data structures when the alternative would be a code spaghetti of multiple arrays and vectors, index functions, internal logic functions.
Nameless · 2025-01-28 07:48
>>41058 what do classes offer that functions, pointers and structs don't?
FILLER · 2025-01-28 14:29
>>41058 So that the class itself can be a collection of multiple vector types and internal logic? What? You're really just trying to reinvent ADTs + modules with encapsulation as a tacked on "bonus", which you can get similar operation using abstract higher-order data types in most functional languages. Let alone how severely verbose this is by comparison. I have nothing against procedural programming + modules, I love and write plenty of code in that style. However classes are an odd, and largely unneeded addition that only serves to ruin clarity under the guise of "it being the way humans categorize things". Object oriented systems work well outside of an individual software project, as a means of representing an entire system and its internal relations. However an individual piece of software rarely (except in some niches) needs the paradigm. Secondarily, you severely under represent the power of immutable data structures and systems. The String data structure in your procedural programming language is likely immutable, and that's for good reason. While the high level code may appear to imply a copy, the compiler should be smart enough to understand data reuse since once something is defined, it cannot change. Therefore, what may appear as a "copy" is just a re-ordering of immutable, reusable data. It's very elegant, and allows you to actually operate in the field of algorithms, data structures, and advanced type theory to write code in a very clear and effective way. In fact, this is why you can see languages like OCaml and Standard ML brush up against C in some performance cases (and not as a result of lazy evaluation, like Haskell). Should you replace everything with them? Of course not, C will still perform better in many average cases due to it not having a runtime. But it does show just how effective functional language compilers are at optimization. If you prefer the "simplicity" of procedural code, by all means. I programmed in C for years before the ML style of functional programming finally took root in me, and in fact I still write plenty of C for FFI (thanks MLton). But to decry other paradigms as useless because they don't follow your model of simplicity (I find functional programming much simpler than procedural programming, on average), is tacitly a reflection on yourself, and not the paradigms themselves.
Mentat · 2025-01-29 02:28
>>41059 Take your Turing tarpit meme elsewhere, mate.
JARGON · 2025-02-04 20:18
>>41059 it's a question of how you implement non-primitive types (as well as type checking) in your language. this is what the debate between FP and OOP is really about, and it's why Rust is idolized as a panacea because it borrows from Haskell's typing system.
drop · 2025-02-07 00:30
>>40578 Haskell und funktionale "programmierung" ist sheisse.