ValueObject (2016)
martinfowler.com
martinfowler.com
But it would be so much nicer if JavaScript had proper operator overloading. You could actually define it nicely on top of TypeScript since you usually know the types of the operands, and could rewrite the call at compile time. Unfortunately, the TS developers are philosophically opposed to adding such a feature. And I'm sceptical JS proper will add operator overloading soon.
It would be incredible for e.g. graphics programming to have proper matrix and vector types and to be able to write formulas in a natural way!
What javascript lacks is a culture of [in]formal protocols which could provide additional semantics. It has some: toString, toJSON, @@iterator, etc. Maybe this is a good thing.
Check this generic implementation https://jsfiddle.net/tas2n59L/
Maybe we should explicitly say O.referenceEquals(O2) or O.fullDepthEquals(O2) or O.itemsReferencesEqualInAnyOrder(O2). etc.
You meant heap-allocated immutable objects, like some kind of a Haskell list, or maybe small stack-allocated immutable objects, like a tuple.
...and then it keeps going. EQ differs from EQL only by having undefined behavior on integers and characters, because that gives implementations the freedom to box those datatypes or not while still guaranteeing that EQ is always a pointer-comparison. And = is numeric equality, which is different than value equality because 0.0 is = to -0.0 but not EQL to it.
EQUALP is not a generic function.
> EQ differs from EQL only by having undefined behavior on integers and characters, because that gives implementations the freedom to box those datatypes or not while still guaranteeing that EQ is always a pointer-comparison
The distinction is more subtle than that. EQ could have been defined to have consistent behaviour for numbers and characters, such that eg (eq x x) would always be true (but (eq x y) would not be implied by (eql x y)), but it was not. Such a definition would have been easier to understand, and would not have prevented implementations from using efficient immediate representations for numbers and characters.
> = is numeric equality, which is different than value equality because 0.0 is = to -0.0 but not EQL to it
Only on implementations which distinguish negative zero. The standard explicitly calls this out. A better example is 3 vs 3.0.
Evil starts when objects of different types are considered value-equal under some circumstances. Python does not have this, with the benign exception of promoting ints to floats, so that 1 == 1.0.
But, conversely, a design that doesn't allow == for everything means that value-equality and reference-equality dicts would have to be different, as well.
Once you start getting away from simple, well-defined spaces, things start to get complex. What is a “phone number space”? Or a “given name & surname” space? They exist, but they are insurmountably complex — both for us and for code — and are ever-changing as the world changes. So we bring order by converting these problem to a well-defined space that we know how to work with: strings. Or rather, strings that conform to certain patterns (numbers for a phone number). This is a grey-area. ValueObjects may be useful, or may just complicate matters.
But not all problems can be mapped to strings. Customers have N sales orders. Sales orders have X lines. Lines refer to inventory items with Y modifiers. You could probably define the “space of all customers, sales orders, inventory items & modifiers” but it would have so many dimensions, and be mostly enormous regions of non-sense. This is where ValueObjects have no place.
I think that statement is at odds with the Turing-Church hypothesis, but I understand that is pedantic
Do you need to call the phone number? Then you probably need to type it. If two phone numbers when typed result in the same number, they are the same. So you probably want to normalize your numbers: remove ( or - or spaces or anything that isn't actually meant to be typed, then you can compare two phone numbers.
Of course it doesn't end there. There's another, looser equivalence between phone numbers that are much more useful: if we call both numbers, will they call the same phone? Things like presence vs absence of country codes, area codes and other things could be part of your normalization. But then, you would need to keep the context in which each phone number was collected. For example, if someone in a given city tells they have a phone number, and someone else in another far away city informs the same phone number, they probably refer to different phones (because the area code is likely different)
What's better, of course, is to ask for country code and area code in the same form. If all your phone numbers are "fully qualified", then you can just compare them for equality. But this is not always possible
So this kind of thing is fuzzy and has edge cases, but getting it right is essential if you want to search for two people with the same phone number (for example). Since there are edge cases, it's important that phone numbers are stored exactly as they were input, and then normalize as a separate column or something.
My main problem with OOP in general is that people try to write programs in ill-defined spaces without defining them first. OOP promises it will be fine: just hide everything behind black boxes and nobody needs to know what actually happens on zeroes and ones level. But it's a lie - somebody needs to make a conscious decision on what happens on data level. It needs to be well defined or you'll get a huge object-oriented ball of spaghetti code. Which if you ask me is even worse than procedural spaghetti code (cause it's harder to refactor).
So once you define your data space well - value objects are usually a good idea, even for complex types with variable-length containers inside.
The main exception is when you need in-place modification for performance reasons, but it's rare in business code.
ValueObject - https://news.ycombinator.com/item?id=12953266 - Nov 2016 (104 comments)
Consider the possibility that you order two laptops from your favourite computer store. When you order them, you customise one, then in the shopping cart you hit "+1" to order two of them, so for the purposes of this example they are functionally equivalent. When they arrive though, they have different serial numbers ABC, and XYZ. So then we start asking the question, are they equal?
One way of asking this question is "Do these two things perform the same function?" to which we can answer yes, ABC and XYZ perform the same function - this line of questioning treats the laptops as ValueObjects as described in the article - laptop ABC and laptop XYZ can be swapped for each other without concern about whether they will behave differently.
Another way of asking this question is what amounts to something closer to "Is this thing the one that I care about?". This line of questioning treats the laptops as ReferenceObjects. You've just received both laptops, taken them both out of the box, they're functionally identical except that you've decided that you want to use ABC for work stuff, and XYZ for personal stuff, and now you have some work stuff to do. You pick up one laptop and ask "Is this laptop ABC?" - not because the function of the laptops matter at this point, but now because of what they represent.
Both have their pros and cons (as described, ValueObjects are better when they are immutable, ReferenceObjects might tend to be more complex), and different languages make different choices about whether certain language primitives and standard objects/classes are implemented as ValueObjects or ReferenceObjects, but understanding them both conceptually allows you to write code that forces one kind of behaviour because that's what matters most for your chosen solution, independently of what the language you are writing it in has as its standard behaviour.
let newObj = oldObj { member = newMember }
In Java, you use the builder pattern, which is not built in. So you use Lombok to generate it for you, and then you get in a fight with your team lead about not using Lombok because he heard about this-one-guy-who-had-a-problem-with-it-just-do-your-own-research-I-can't-be-bothered-explaining-it-to-you, so then you roll (and maintain) your own Builder object for every value class.[1]:https://v2.ocaml.org/manual/objectexamples.html#s%3Afunction...
- Dataclasses w/ the `frozen=true` kwarg - namedtuple
They add some headroom to programs, but I find they help _immensely_ for readability and fewer bugs.
Bonus, each works out of the box for equality testing (two objects are "==" if they're of the same type and the values of each of their fields are equal)
Better late than never. Just use function and what you need is really just Data structure + Algorithm.
DDD, Design patterns,.. or complicated things just born to fix the OOP methodology.
DDD is not inherently tied to OOP: the whole strategical part isn’t about code at all, and the tactical part is applicable to FP as much as to OOP.
WRT design patterns, they exist in functional programming as well, they’re just different.
Nowadays there are even resources about DDD specific to the FP world, such as Domain Modeling Made Functional by Scott Wlaschin [0].
[0] https://pragprog.com/titles/swdddf/domain-modeling-made-func...
Lets hope your not in charge of building that new complex air traffic control system, bank lending system, or large inventory management system.
Did you learn nothing of over a decade of large projects using data-flow modeling (structured analysis)?
When in fact you can have architecturally separated modules without objects just fine, and all objects bring is the complexity of finite state machines talking to each other via complex message protocols.
Sometimes you indeed need some virtual dispatching, but most of the time procedure is a better abstraction, and function is even better. Instead of making complex parts of the system simpler, OOP usually makes simple parts complex.
Hence the need for frameworks. In functional or procedural worlds people just take libraries and glue them together. In OOP world it's nearly impossible because of how complicated object protocols are, so if you have objects from different libraries, you'll most likely need to write lots and lots of facades. Hence OOP evolved towards megalibraries which have it all.
> Lets hope your not in charge of building that new complex air traffic control system, bank lending system, or large inventory management system.
Many of these are doing fine being written in procedural style though.
That's not a direct result of OOP. Languages can have extension functions or type classes or delegation to address this. And it's not like the problem magically goes away for procedural code.
It's actually the core definition of objects. Objects are entities supporting open recursion, self-polymorphism and late binding.
There is a difference between "having type-classes" and "having object as a core formalism of computation"
> Languages can have
Languages can have objects, or actors, or other entities alike. This doesn't mean it's prudent to design your code around the idea of message-exchanging FSMs. Sometimes at some scale objects or actors are useful.
> And it's not like the problem magically goes away for procedural code.
This is empirically not true, and the frameworks example is the best proof of it. In languages like (functional) Scala, Go, OCaml, libraries do compose well enough, and gluing them is cheap.
Any hardcore OOP language eventually brings frameworks like Spring, Rails, etc, because gluing objects is expensive, and for no reason, if functional/procedural alternative is so much cheaper. Even natural transformations between monads are very cheap compared to objects' composition.
This Kay-esque focus on _messages_ has fudded the OO space for too long now. The so called "magical view" of OO - "it's all about messages".
The actual original inventors saw an object as a representation of a phenomena, real or imagined. They (particularly Kristen Nygaard) sought a way to model phenomena, concepts and the interactions between them. Consider that this is the design viewpoint that Bjarne Stroustrup actually sought to add to the C language. Step back from a focus on state machines, message protocols and a technical plumbing viewpoint.
FP has its place, particularly where the focus is on the transformation of data. But don't extrapolate that as applicable to all types of computing application.
As I mentioned elsewhere, the predominant design paradigm for large complex projects outside of computing and the data sciences for over a decade was Structured Analysis. That involved data modeling and typically separately modeling the functional processes that modified that data. Did we learn anything?
Sure, people nowadays tend to prefer writing procedural/functional code in OO languages, using objects as mere modules.
> But don't extrapolate that as applicable to all types of computing application.
Any formalism can be used for any kind of computation. Actors, objects or lambda calculus/simple referentially transparent FP lang are interchangeable. The only difference is the latter is the simplest, hence easier to reason about and compose. But any kind of system can be built on top of any of these.
> As I mentioned elsewhere, the predominant design paradigm for large complex projects outside of computing and the data sciences for over a decade was Structured Analysis.
This is not a paradigm, that's a methodology, and it's totally unrelated to what kind of language paradigm is used, or to original take about "programs = algorithms + data structures". Besides, most modern reactive systems are built on top of something like RX or Akka streams anyways, and functional in principle.
Functional programming in aviation: https://m.youtube.com/watch?v=0x3EIqBrZxw
Functional programming in banking: https://www.finextra.com/newsarticle/36297/nubank-buys-firm-...
Except for things like React which are more or less functional reactive programming, but in case of react its stateful. Functional reactive programming exist, and GUI is not obliged to be OO
Another big Clojure UI/graphics framework is Quil, which is based on Processing and does an even worse job of hiding it’s imperative/stateful core.
If you are really interesting in this topic, check out the following pages: https://clojure.org/community/success_stories
And Context (at least in my humble experience). Lately I got rid of OOP too and now all my functions accept `params` as a first argument which contains settings and the state of an object/subserver/etc. I find the ability to group functions into different files useful though (it’s js so I cannot do that like one does in c++).