> we can start listing all the GOF patterns.

Hardly. The GoF patterns are pretty much all poor attempts at functional patterns, with disturbingly long-winded implementations. They are there because of missing features in OO languages (mostly first class functions). The GoF also label good programming practices as bad (anaemic domain model for example). They are a scourge on the world of programming.

My point was that there are language features for composition outside of functions (type-classes, ad-hoc polymorphism); as well as very sound composition approaches in code (monads, applicatives, FRP ... which actually still use functions btw). The code approaches are based on sound and provable maths, the GoF patterns are a joke, as is inheritance based composition, or pretty much any approach to composition in OO-land, because the 'glue' you talk of should be at a lower level (the function level), not at a class level or higher, where the shapes of the things you're composing don't match in any meaningful mathematical sense. Even the higher level glue in Haskell (type-classes) and Scala (ad-hoc polymorphism) is all about set theory and matching of shapes.

I'm saying this as a reformed OO dev; after spending 20 years believing the GoF (and OO world) dogma I finally changed my thinking about 7 years ago after a super large project I still work on became cognitively too difficult for the team to manage (the scale of the code-base combined with the extreme abstraction of components and hidden mutable state meaning it's very difficult - unless the coder wrote the feature - to know what mutation would go on behind the scenes; this is one of the big failures of hidden mutable state with packaged functionality that hides the mutation).

Inheritance is a myth, interface based abstraction is a myth, code reuse is a myth, OO composition is a myth. You may achieve this stuff once in a while, but if you 'abstract' a service behind an interface, 9 times out of 10 that service's own interface is imprinted in the abstract interface. And when you do decide to replace it later, unless it's trivial, then there is nearly always a change to the interface. If the thing behind the interface mutated state, then whether you like it or not, the code that uses it makes assumptions - which increases coupling. Abstraction is used liberally, even when you're only abstracting one type of thing. "Ah, I have a MyService class, let's abstract it behind an IMyService". "But there's only one of them?", "No matter, Martin Fowler said I should".

When you see whole DI frameworks and tooling, and mocking frameworks, you must realise that something is wrong? Why use DI when you can just use first class functions? Or a Reader monad? There are a lot of people in the OO world with opinions on the perfect architecture for applications, but none of them have any study backing them - but the dogma flows and flows, it's like, once somebody reads the GoF book they think they have all the answers. Yeah let's use the Visitor pattern to get over the fact we don't have algebraic data types. Let's use the Command pattern because we don't have first class functions, etc.

I prefer to rely on the proven technology: mathematics. It works, and writing 'expression oriented' code with immutable values just makes sense. It makes sense for the brain, it makes sense when you don't want code rot, it makes sense in parallel programming, it makes sense when you want programmers to know what the hell is going on.

Bringing in functional practices, and going away from the whole OO 'belief system' was enlightening, and I'll never go back. Neither will anybody on my team. I realise you've nailed your colours to the mast, and it's clear from this thread that you're living the OO dogma dream, and none of this will make any difference to you. But your blanket dismissal of anything from the functional programming world is very telling.