News for hackers. Stuff that matters.
Re:Use Your Type System by munificent on Thursday July 24, 2025 @01:17PM (#44673321)
> This is where the concept of “Correct by construction” comes in.

This is one of the basic features of object-oriented programming that a lot of people tend to overlook these days in their repetitive rants about how horrible OOP is.

One of the key things OO gives you is constructors. You can't get an instance of a class without having gone through a constructor that the class itself defines. That gives you a way to bundle up some data and wrap it in a layer of validation that can't be circumvented. If you have an instance of Foo, you have a firm guarantee that the author of Foo was able to ensure the Foo you have is a meaningful one.

Of course, writing good constructors is hard because data validation is hard. And there are plenty of classes out there with shitty constructors that let you get your hands on broken objects.

But the language itself gives you direct mechanism to do a good job here if you care to take advantage of it.

Functional languages can do this too, of course, using some combination of abstract types, the module system, and factory functions as convention. But it's a pattern in those languages where it's a language feature in OO languages. (And as any functional programmer will happily tell you, a design pattern is just a sign of a missing language feature.)

Re:Use Your Type System by hombre_fatal on Thursday July 24, 2025 @02:52PM (#44674598)
I don't see this having much to do with OOP vs FP but maybe the ease in which a language lets you create nominal types and functions that can nicely fail.

What sucks about OOP is that it also holds your hand into antipatterns you don't necessarily want, like adding behavior to what you really just wanted to be a simple data type because a class is an obvious junk drawer to put things.

And, like your example of a problem in FP, you have to be eternally vigilant with your own patterns to avoid antipatterns like when you accidentally create a system where you have to instantiate and collaborate multiple classes to do what would otherwise be a simple `transform(a: ThingA, b: ThingB, c: ThingC): ThingZ`.

Finally, as "correct by construction" goes, doesn't it all boil down to `createUUID(string): Maybe<UUID>`? Even in an OOP language you probably want `UUID.from(string): Maybe<UUID>`, not `new UUID(string)` that throws.

Re:Use Your Type System by munificent on Thursday July 24, 2025 @03:48PM (#44675193)
> Even in an OOP language you probably want `UUID.from(string): Maybe<UUID>`, not `new UUID(string)` that throws.

One way to think about exceptions is that they are a pattern matching feature that privileges one arm of the sum type with regards to control flow and the type system (with both pros and cons to that choice). In that sense, every constructor is `UUID.from(string): MaybeWithThrownNone<UUID>`.

Re:Use Your Type System by 9rx on Thursday July 24, 2025 @04:25PM (#44675631)
The best way to think about exceptions is to consider the term literally (as in: unusual; not typical) while remembering that programmers have an incredibly overinflated sense of ability.

In other words, exceptions are for cases where the programmer screwed up. While programmers screwing up isn't unusual at all, programmers like to think that they don't make mistakes, and thus in their eye it is unusual. That is what sets it apart from environmental failures, which are par for the course.

To put it another way, it is for signalling at runtime what would have been a compiler error if you had a more advanced compiler.

Re:Use Your Type System by SAI_Peregrinus on Thursday July 24, 2025 @11:02PM (#44679124)
Unfortunately many languages treat exceptions as a primary control flow mechanism. That's part of why Rust calls its exceptions "panics" and provides the "panic=abort" compile-time option which aborts the program instead of unwinding the stack with the possibility of catching the unwind. As a library author you can never guarantee that `catch_unwind` will ever get used, so its main purpose of preventing unwinding across an FFI boundary is all it tends to get used for.
Re:Use Your Type System by 9rx on Thursday July 24, 2025 @11:12PM (#44679178)
> Unfortunately many languages

Just Java (and Javascript by extension, as it was trying to copy Java at the time), really. You do have a point that Java programmers have infected other languages with their bad habits. For example, Ruby was staunchly in the "return errors as values and leave exception handling for exceptions" before Rails started attracting Java developers, but these days all bets are off. But the "purists" don't advocate for it.

Re:Use Your Type System by SAI_Peregrinus on Friday July 25, 2025 @02:06PM (#44686250)
Python as well. E.g. FileNotFoundError is an exception instead of a returned value.
Re:Use Your Type System by lock1 on Thursday July 24, 2025 @01:49PM (#44673770)
I find regular OOP language constructor are too restrictive. You can't return something like Result<CorrectObject,ConstructorError> to handle the error gracefully or return a specific subtype; you need a static factory method to do something more than guaranteed successful construction w/o exception.

Does this count as a missing language feature by requiring a "factory pattern" to achieve that?

Re:Use Your Type System by 0x457 on Thursday July 24, 2025 @04:09PM (#44675425)
This is why constructors are dumb IMO and rust way is the right way.

Nothing stops you from returning Result<CorrectObject,ConstructorError> in CorrectObject::new(..) function because it's just a regular function struct field visibility takes are if you not being able to construct incorrect CorrectObject.

Re:Use Your Type System by henry700 on Thursday July 24, 2025 @02:20PM (#44674179)
The natural solution for this is a private constructor with public static factory methods, so that the user can only obtain an instance (or the error result) by calling the factory methods. Constructors need to be constrained to return an instance of the class, otherwise they would just be normal methods.

Convention in OOP languages is (un?)fortunately to just throw an exception though.

Re:Use Your Type System by Conscat on Thursday July 24, 2025 @05:21PM (#44676343)
In languages with generic types such as C++, you generally need free factory functions rather than static member functions so that type deduction can work.
Re:Use Your Type System by Jensson on Thursday July 24, 2025 @09:39PM (#44678507)
> You can't return something like Result<CorrectObject,ConstructorError> to handle the error gracefully

Throwing an error is doing exactly that though, its exactly the same thing in theory.

What you are asking for is just more syntactic sugar around error handling, otherwise all of that already exists in most languages. If you are talking about performance that can easily be optimized at compile time for those short throw catch syntactic sugar blocks.

Java even forces you to handle those errors in code, so don't say that these are silent there is no reason they need to be.

Re:Use Your Type System by Yoric on Friday July 25, 2025 @06:16AM (#44681562)
Indeed, OOP and FP both allow and encourage attaching invariants to data structures.

In my book, that's the most important difference with C, Zig or Go-style languages, that consider that data structures are mostly descriptions of memory layout.

Re:Use Your Type System by Tainnor on Friday July 25, 2025 @07:22AM (#44681933)
> Functional languages can do this too, of course, using some combination of abstract types, the module system, and factory functions as convention

In Haskell:

1. Create a module with some datatype

2. Don't export the datatype's constructors

3. Export factory functions that guarantee invariants

How is that more complicated than creating a class and adding a custom constructor? Especially if you have multiple datatypes in the same module (which in e.g. Java would force you to add multiple files, and if there's any shared logic, well, that will have to go into another extra file - thankfully some more modern OOP languages are more pragmatic here).

(Most) OOP languages treat a module (an importable, namespaced subunit of a program) and a type as the same thing, but why is this necessary? Languages like Haskell break this correspondence.

Now, what I'm missing from Haskell-type languages is parameterised modules. In OOP, we can instantiate classes with dependencies (via dependency injection) and then call methods on that instance without passing all the dependencies around, which is very practical. In Haskell, you can simulate that with currying, I guess, but it's just not as nice.

Re:Use Your Type System by mrkeen on Friday July 25, 2025 @02:21PM (#44686447)
You have it backwards from where I'm standing.

'null' (and to a large extent mutability) drives a gigantic hole through whatever you're trying to prove with correct-by-construction.

You can sometimes annotate against mutability in OO, but even then you're probably not going to get given any persistent collections to work with.

The OO literature itself recommends against using constructors like that, opting for static factory pattern instead.

Re:Use Your Type System by munificent on Sunday July 27, 2025 @06:17PM (#44705234)
Nullability doesn't have anything to do with object-oriented programming.
Re:Use Your Type System by mrkeen on Wednesday August 06, 2025 @02:30AM (#44808427)
Yes, yes, "No true OO language ..." and all that.

But I'm going to keep conflating the two until they release an OO language without nulls.