70 comments

[ 4.1 ms ] story [ 111 ms ] thread
"==" considered harmful? :)

Maybe we should explicitly say O.referenceEquals(O2) or O.fullDepthEquals(O2) or O.itemsReferencesEqualInAnyOrder(O2). etc.

Reference equals doesn't make sense for a value object anyway.
Unless you are using immutable objects. Then efficient equality check is referenceEquals() || deepEquals()
That should be in the guts of the implementation of == for value types, not something exposed outside of it.
Yes I agree, in Haskell it is derived, for example.
1 is an immutable object, and in languages such as Java or C++ it's not a reference-type object.

You meant heap-allocated immutable objects, like some kind of a Haskell list, or maybe small stack-allocated immutable objects, like a tuple.

Common Lisp does this. EQL means reference-equals, and EQUAL means value-equals (recursive). If you need to custom behavior, then EQUALP is a generic function that can be overridden for your data types.

...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.

> If you need to custom behavior, then EQUALP is a generic function that can be overridden for your data types.

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.

In Python, (a == b) is always value equality, and (a is b) is always reference equality. The only unfortunate aspect is that == is defined for all types - IMO it really should default to throw, and only classes for which value equality makes sense should define it.
(comment deleted)
Defaulting to False is the next best thing.

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.

It doesn't quite default to False, since every object is still equal to itself. The consequence is that people use == for identity equality sometimes - often not fully intentionally, e.g. by using objects as dict keys directly without using id().

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.

Why would one care about reference equality of anything in Javascript? It's not like memory addresses are a concept on that language.
I really wanna understand this but I feel like it’ll take me a while.
Why make a string when you can create a new StringValue class, StringValueFactory, StringValueFactoryIterator, StringValueFactoryIteratorFactory, StringValueTemplateDecorator...
Learning a functional language could help. You get to explore immutable data structures and how to work with them. Much of what is being discussed here are concepts that are first class citizens in a functional language.
If your takeaway is "I should strive to make objects immutable" then you are on the right path.
Programming is all about abstractions and the ways they interact. Specifically this article talks about what it means for two instances of an object to be "equal", which depends very much on what kind of equal you care about.

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.

The way is see ValueObjects is as the most convenient tool when working with a very well-defined space. For example, integers on the number line. There’s no uncertainty— 2 is 2 is 2. Same goes for a 2D coordinate within a 2D space, or 3D coordinate… etc. The “date space” is quite well-defined these days, as long as you’re working with a Gregorian calendar and typical timezones. It’s at the limit of “coordinate within a space” but as programmers and with training we can handle it.

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.

> not all problems can be mapped to strings

I think that statement is at odds with the Turing-Church hypothesis, but I understand that is pedantic

> What is a “phone number space”?

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.

> The way is see ValueObjects is as the most convenient tool when working with a very well-defined space.

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.

A valuable object might make references to other things. For instance, a route generated by an application that explains how to get from one location to another would be a value object. It would represent a snapshot of the locations along a particular path, but this suggested route would not be identifiable, despite possible internal references to things like cities, roads, etc.
Nice little introductory essay on the concept. One wrinkle I've often pondered -- once you get copy by value semantics, do you really need immutability? I mean immutability is more pure but it's also often a nuisance implement if an object has a lot of members and you need to do a lot of "copy but change this one member" operations.
It's a double-edged sword - by having your data immutable, you can also avoid copies. Or, better yet, the compiler can avoid them for you.
I use Haskell, where the syntax to "copy but change one member" is:

    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.
This feels like a roundabout discovery of data oriented programming.
Wasted 10 years programming because of OOP.

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.

I don’t see your point.

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.

DDD is not hightly coupled to OOP, the problem is that most popular books (red and blue one linked in Fowler's blog for example) were written in time when OOP was very popular and authors also used languages that propagates OOP. If the authors would used purely functional languages then people wouldn't assume it is about OOP.
> what you need is really just Data structure + Algorithm

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)?

Can you elaborate? I'd love to have have more arguments for why FP is not the holy grail it's sometimes made out to be!
Email me if you want to chat about it. I'm loathe to write a treatise here sorry.
The core illusion of OOP is that it makes the design of large-scale system simpler by hiding away the complexity behind the facade of objects exchanging messages.

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.

> 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.

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.

> That's not a direct result of OOP.

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.

> makes the design of large-scale system simpler by hiding away the complexity behind the facade of objects exchanging messages.

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?

> This Kay-esque focus on _messages_ has fudded the OO space for too long now.

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.

Data structure + algorithm is a valid way of building large programs. That is basically how Clojure is designed, and it works pretty well for big applications.

Functional programming in aviation: https://m.youtube.com/watch?v=0x3EIqBrZxw

Functional programming in banking: https://www.finextra.com/newsarticle/36297/nubank-buys-firm-...

Can you show one large-ish GUI software written in Clojure? Something like, idk, Blender, DaVinci Resolve, Unreal Engine...
The problem with that is that (nearly?) every GUI toolkit is object oriented so you can't escape it if you want to write GUI programs.
clojure was introduced in 2007, there have been many GUI toolkits that have been implemented entirely from scratch in other languages since then. QML+QtQuick, Dart+Flutter, Nuklear in C, Nana or neoGFX in C++, etc etc
> The problem with that is that (nearly?) every GUI toolkit is object oriented

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

The Defold game engine IDE is written in Clojure, using cljfx, a React-like wrapper around JavaFX:

https://defold.com/

looks very neat, but it doesn't really get more OO than JavaFX, does it ? would you call this code functional & idiomatic in any relevant sense of the term? https://github.com/defold/defold/blob/dev/editor/src/clj/edi...
I was mostly supplying an example rather than taking a side in the philosophical argument, to be clear. I’d call that idiomatic Clojure, but I’m a heathen that very much appreciates Clojure being on top of the JVM and offering Java interop.

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.

Functions are design patterns to simulate jumps after all.
It's the opposite, lambda is the ultimate GOTO.
Genuine question: What OO languages have you worked in?
C++, Java, Ruby, Python
Thanks for replying. Useful data to me.
what you need is really just Data structure + Algorithm

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++).

Now C# has records which is exactly that.
Most of the languages are adopting the same patterns that clojure adopted in 2012. Python now has dataclass Javascript it planning to introduce records etc.
Python has two great ways to write immutable "ValueObjects":

- 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)

Basically this is a very limited case of operator overloading. Redefine `==` so that it means value equality and not reference equality.

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!

I wanted that too until I realized they are usually used only in a couple of files in a project, only a handful of times in each. Operator overloading looks nice but it isn’t a life changer.

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.

In languages like C, C++, Rust, Swift, and C#, either not all types have to be heap-allocated reference types, or the runtime will handle copy-on-write for you, so it's convenient to create and work with small value types. It's unfortunate that so many popular languages don't do this. Having everything on the heap is not good for performance and creates an unnecessary choice between mutability and aliasing.
there is no copy-on-write in the C language
But in C, the first half of the 'or'-dtatement applies.
(comment deleted)