Hacker News new | past | comments | ask | show | jobs | submit login

> "4. Objects must always be initialized to a valid state. Not doing so is a compile-time error."

So this essentially means there can never be a NULL object assigned to anything... how is this possible? is it avoided by throwing exceptions instead of returning NULL?

If you forget everything you ever learned about programming, and then I ask you for an integer, you're never in a million years going to reply "null"; the existence of "null" is something you had to learn. A language could simply not have a concept of "null". Even an OO language could have no concept of "null".

In practice, it's useful to be able to answer "I'm sorry, Dave, I can't do that". Some languages do that by silently and implicitly allowing this special value "null" in a return type. Some languages allow you to throw an exception (again without ever having declared that this is something you might do). But some languages take one of those approaches and make it explicit: in the two cases, you have the "option"/"maybe" type, or checked exceptions. Either of these explicit methods let a function declare that it may fail, and moreover the language forces you to handle the possibility of failure in any function which may fail.

The "reasonability" comes from the fact that the language forces you to be explicit about the possibility of failure, rather than implicitly allowing all functions to fail.

Null is just an invalid pointer target that you can verify easely. It is just abi convenience and removes the need for a flag.

On the other hand I unironicly write goto:s and use globals as much as a please (think GNU Bison/Flex) so who I am to judge.

At ABI level you might want to use all-bits-zero to represent something other than a valid instance of your type, sure; that's how Rust's option type works. But making your language semantics say that that has to be allowed everywhere a valid instance could be passed is a mistake; for most functions it's an error to call that function without passing valid argument values, so it's better if the language has a way of enforcing that rather than forcing you to write it out by hand every time.

Ye some "always valid kinda a pointer"-type is nice. I wish there was some standardized compiler support for that in C, like MS kinda silly empty macros in function signatures to annotate input and output pointers etc.

I don't really view that as being relevant. F# compiles `Option.None` down to a null under the covers; what matters is that the type system is null-aware.

You can still have “the absence of a value”, but it’s codified in the type system. Most languages with this feature implement an Optional or Maybe wrapper type, that is either Some<T> or None (i.e. no value).

The difference is, because you’re now receiving a different type, you are forced to deal with the case where there’s no value.

Rather than your function returning a Customer that could sometimes be null—and if it’s only null infrequently, you’re much more likely to forget to handle it—it instead returns Optional<Customer>. To operate on that Customer object, you are forced to unwrap the Optional by checking whether the underlying value is there.

How well you handle the None case is up to you, but the compiler won’t let you forget (generally anyway; for example Swift allows you to force-unwrap optionals with a crash on the None case, but eh).

You use sum types / optional<T> / Maybe / Option<T> / whatever your language calls them. Such a value can have two states: 'None', where it calls no value, and 'Some(t)',where it carries a value 't'.

For example, a division function could return (1) 'None' for 'div(20, 0)' because division-by-zero is undefined, and return (2) 'Some 10' for 'div(20, 2)' because 20/2 is defined and is equal to '10'.

Three is a great article about option tyle in the context of F# here: https://fsharpforfunandprofit.com/posts/the-option-type/

Not quite. There can be a null value if the object type allows for it, think Optional<T>. What you can't have is an undefined object, as you could in Javascript for example.

You can represent the possible absence of value with your type system. You can use a Maybe/Option type for example.

Not only that, it implies that there must be a valid null-state for every type, which is also a bad idea.

No it doesn't; the whole point is to avoid every type having a null state. You only define null states for those types for which it's semantically valid. For most types, you just don't have a null state and don't allow them to be constructed in an invalid state.

I'm not entirely sure what you're getting at here. Are you saying the author is arguing a call like

  new Customer()
should return something valid? I think the FP way would be to disallow constructor overloads with no arguments, as really, there's no reasonable thing to be done here. If you don't have all the necessary information to initialize a customer yet, you don't get to initialize one, and you pass around a partially applied constructor function instead.

ie, The argument is that (in pseudocode):

  Customer(name = "Jimmy", address = null)
is not really a customer if address is a necessary piece of information, so we should distinguish at the type level by passing around a function that takes the address and returns the correctly initialized Customer like so

  \addr -> Customer(name = "Jimmy", address = addr)

Admittedly, this prevents you from accessing name until you provide an address, but I don't think there are many valid use cases where you want to access data before the Customer value is corrrectly initialized. I'd consider this a code smell to be refactored.

> Admittedly, this prevents you from accessing name until you provide an address, but I don't think there are many valid use cases where you want to access data before the Customer value is corrrectly initialized. I'd consider this a code smell to be refactored.

If you really needed to do that you could just define a partial customer with no address field and pass are that around.

No, it implies that there must be a generic two-valued datatype to wrap every state:

Just t | None

You can have your null case, and avoid the null-pointer exception, default keyword, chained ? null-check hellscape in which we currently reside.

No, it implies that you can use your type system to surface the fact that a variable may have no values.

>So this essentially means there can never be a NULL object assigned to anything... how is this possible?

I believe Crystal doesn't let anything be null either. It's quite interesting.

> So this essentially means there can never be a NULL object assigned to anything…

No, it means that you can't leave bits off yet claim to have a valid object, either the object is fully initialised or it's illegal. This is mostly a concern in older languages like C where you can reserve space for a struct then not fill in anything.

In some more recent languages (Java, Go, C# historically though that's changing) everything is zeroed by default so you avoid the "random garbage" situation, but it means you need to assume anything can be zero even if that's not intentional, and depending on the specifics you might "double write" each location even when that's not actually necessary.

In the more functional slate of languages, you simply can't let any members off. There might be a concept of "default value" but that tends to be opt-in, the baseline is that if you define a type you must fill in every member, explicitly, before the compiler will accept it.

Initialising items to null is orthogonal.

Now since the rest is predicated on a misunderstanding I've a version of each for "incomplete initialisation" and a version for "null".

> how is [forcing complete initialisation] possible?

The compiler just requires that every member of the structure be specified.

> how is [things never being NULL] possible?

Don't make NULL an implicit member of every type.

> is [incomplete initialisation] avoided by throwing exceptions instead of returning NULL?

it's avoided by the compiler refusing to compile your code if you leave off members.

> is [things never being null] avoided by throwing exceptions instead of returning NULL?

It's avoided by NULL being a member of a completely different type than anything else, whether that type is a special case or not e.g. in Rust "null" is one member of the Option "wrapper type" so to indicate that, say, a user is optional you write `Option<User>`, then that can either be `None` (no user, ~ null) or `Some(user)`.

If you just type something as `User` then it's necessarily a valid user and nothing else, because there is no null value you could put into it, you'd get a type error.

* Rust playground which fails to compile because one of the struct members was left off: https://play.rust-lang.org/?version=stable&mode=debug&editio...

* Rust playground which fails to compile because trying to put a null (None) in a non-nullable field: https://play.rust-lang.org/?version=stable&mode=debug&editio...

* Version of the previous with an optional (nullable) member: https://play.rust-lang.org/?version=stable&mode=debug&editio...

* Version with opt-in default (through Rust's Default trait, the default derivation of the Default trait will recursively default each field, which usually means some sort of zeroing): https://play.rust-lang.org/?version=stable&mode=debug&editio...

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact