Request for feedback: changing `unit` from `{}` to `undefined`

Full context at purescript-prelude#266.

Right now, unit's JavaScript runtime representation is an empty record (e.g. {}). It really should be undefined.

So, in two weeks from the time of this posting (i.e. Aug 17), we’ll be changing it from {} to undefined (purescript/purescript-prelude#267).

If no one complains (meaning, there’s nothing that would cause problems for them), we’ll merge this as a non-breaking change. (And it probably won’t cause problems)

If there is a problem, please post it here and we won’t merge this until the next breaking changes release.

6 Likes

Awesome news, but interesting

  1. Will it speedup programs somehow?
  2. Will programs take less memory? (I suppose yes)

I don’t know. I would imagine a small perf gain, but I don’t know how small that would be.

I don’t like the idea of requiring unit’s runtime representation to be undefined. it could potentially break a lot of ffi code, not in the sense that it would cause problems immediately, but because if anyone, anywhere, returns {} from an ffi function that should return Unit, you won’t be able to rely on Unit's runtime rep being undefined. I imagine it would be tricky to raise an error for, also.

I think its fine, and makes sense, that the runtime representation of unit is defined to not be anything specific, and that you can’t rely on it

I actually like the undefined choice. It matches what JavaScript functions return when there is not return value which would make it more compatible with FFI-ing to various JS projects (although probably messing with FFI in a few existing PureScript projects). I would venture to guess the most common use for Unit is for effects, Effect Unit.

console.assert((function (){})() === undefined)
//=> undefined

The effect of console.assert results in undefined as well as no assertion error showing effect showing empty functions are undefined. A lot of the FFI I have done is around OO-style interfaces which do effects on instances without returns.

2 Likes

Unit type values across all the program (that came not from FFI) is the reference to the same empty object currently, and equality op just returns true always, as well as other functions that do not perform any operations and checks over the values. So there should be nothing to do with memory and performance changes.

I believe the justification for this change would be that it makes it possible to check the referentially stable value of the Unit type in FFI with less hassle.

@JordanMartinez You stated: “It really should be undefined.”. What is the general justification for this? I know there is a particular case you described, but could you formulate the general reason why it really should be undefined?

@wclr See @toastal’s answer above.

Unit in FP languages functions more like void in C-family languages. The fact that an empty object {} is even returned in the first place is technically wrong. One can still “do” something with {} (e.g. access its prototype), but the purpose of Unit is to be useless as a value. undefined seems to satisfy this better than null and isn’t a potentially misleading value like NaN…?

2 Likes

but the purpose of Unit is to be useless as a value. undefined seems to satisfy this better than null and isn’t a potentially misleading value like NaN

Ok, NaN is not referentially stable, it’s like {} in these terms, can not compare values defined in different places.

As Unit type - Wikipedia stands:

The purpose of a Unit type is to be the type that may have only one value and thus not hold no information. Null and undefined are both considered to be unit types in Javascript.

Undefined/null here is just a more technically convenient and safe value for the FFI, as you can simply compare without fear two underlying values that possible came from different FFI modules.

I don’t really think it makes it wrong to be any special or unspecial value, Unit is Unit because it has one inhabitant, we could define the single inhabitant to be all JavaScript values and it’d still be Unit. Just because JavaScript’s builtin equality can tell the difference between those values, it doesn’t mean there’s more than one inhabitant from the PureScript perspective. It’s more about the observation of Unit than the construction - specifically that we don’t observe anything (not because we can’t, we choose not to, and that’s what makes it useful).

I think in theory it’s still possible to tell between two constructions of undefined, as it could be passed between Realms which would cause them to not be equal under ===? Maybe not as I think it’s a primitive

That being said I’m not against it being undefined, just that I don’t see it as any better than {} really

I agree, I don’t think {} is wrong, at least not from a theoretical perspective. Initially, Unit was defined as newtype Unit = Unit {}, which makes perfect sense to me personally, because the defining aspect of the Unit type is that it has exactly one inhabitant, and the empty record type {} has exactly one inhabitant (which is also written {}). The reason we started using undefined instead was mainly from a more practical perspective: it just happens to be relatively expensive to construct an empty object in JS.

To me, the reason to make this change is that:

a) almost every FFI function which returns Effect Unit or similar already, such as console.log, uses undefined for the Unit it returns, and making Prelude.unit consistent with this makes it less likely for people to run into nasty surprises, and

b) it can be useful to have a data type whose JS representation is guaranteed to be undefined in cases where that matters, which it does occasionally when interfacing with JS code. (However, we haven’t yet decided whether we want to say that Unit is in fact guaranteed to have a JS representation of undefined, so if we decide against this then this point is irrelevant).

4 Likes

Welp, I’m wrong! :sweat_smile: Yes, Unit is a type that only has a single value. Thanks for the corrections.

Harry summarized the reason for this change well, so I don’t think anything else needs to be said.

1 Like

Somewhat off topic but I realise I’ve been possibly conflating Top with Unit:

newtype Bottom = Bottom (forall a. a) -- 0 inhabitants
newtype Top = Top (exists a. a) -- maximum number of inhabitants
newtype Unit = Unit (forall a. a -> a) -- 1 inhabitant

From the perspective of PureScript, Top and Unit are used the same as there’s nothing you can know about the specific a chosen in Top, or how a -> a was implemented in Unit. However in an OOP language, you might be able to do a check for what type a was assigned to and could recover information about it.

2 Likes