DEV Community

Discussion on: You don't need null

Collapse
 
romeerez profile image
Roman K

Wondering how no one still mentioned 'in' operator:

const a = {}
const b = { key: undefined }

console.log('key' in a) // false
console.log('key' in b) // true
Enter fullscreen mode Exit fullscreen mode
Collapse
 
martinpham profile image
Martin Pham

Btw it’s fun when

  • Access a variable which is not defined: we have ReferenceError
  • Access an attribute which is not defined: no error, it returns undefined
 
loucyx profile image
Lou Cyx • Edited

Yup, it kinda sucks, but is preferable to return undefined for something that isn't defined ... you can still check if a variable exists before trying to access it, by using typeof:

console.log(foo); // ReferenceError
console.log(typeof foo !== "undefined" ? foo : "Nope"); // "Nope" πŸŽ‰
Enter fullscreen mode Exit fullscreen mode

Accessing properties is a better DX, and with ?. and ?? is even better:

const obj = {};
console.log(obj?.foo ?? "Nope"); // "Nope" πŸŽ‰
Enter fullscreen mode Exit fullscreen mode
 
martinpham profile image
Martin Pham

I’m not talking about the check. I’m just trying to point the inconsistency between two cases about the same thing.

 
loucyx profile image
Lou Cyx

But that's the thing, they aren't the same thing. One is trying to access something not declared in the scope, while the other is trying to access a property of something that is declared in the scope. You can test both with typeof === "undefined", but if you try to access something undeclared you get an error, which sucks, but that's how it works. Nowadays is very difficult to end up in this scenario tho, because editors will let you know if you're trying to access an undeclared value.

Collapse
 
martinpham profile image
Martin Pham

You don’t need β€œin” /s

 
loucyx profile image
Lou Cyx

lol

Collapse
 
loucyx profile image
Lou Cyx • Edited

Several reasons, I guess:

  1. That would make the argument saying "there is no way of differentiate between intentionally set and unintentionally set" for undefined invalid, because with in you can figure out if it was intentionally set as undefined, or is actually missing from the object.
  2. Because if you set it to null, in will return true as well, so that doesn't make it better than undefined, nor different. So another argument in favor of "they are the same".
  3. Because there are other ways to check if something exists than in.

TL;DR: Because is an argument in favor of the article, I guess I could even update it with that :D

 
martinpham profile image
Martin Pham
  1. So you’re saying, with the β€œin”, when you intentionally set the value to β€œundefined”, it means the value is β€œdefined”?
 
loucyx profile image
Lou Cyx

You should explain that to me. I mean you're the ones saying "null means something is defined but intentionally empty", so you're the ones saying there's a need for that kind of thing ... I'm just saying that using in you can achieve the same thing with undefined ... in both cases we are saying something is "defined but not quite", just with different wording ... and I still don't get why you need that distinction 🀣

 
romeerez profile image
Roman K

and I still don't get why you need that distinction

Well, I tried to find an example but cannot :)

And in this whole long holywar I cannot see a single example why null is better. JSON is one example, but let's imagine we delete all null keys from objects after getting data. Okay now we have libraries which are requiring null, and we have to convert undefined to null when using them. And that's all.

Maybe null make life easier when you are not using TypeScript, I don't know. With TypeScript you can see type in the code, and without it people are probably doing console.log(data) all the time to see data shape, and if values are omitted this is confusing.

Good article, thanks for writing, please keep doing it!

 
martinpham profile image
Martin Pham

Well not only me, also other people here were trying to explain to you, but seems you just didn’t want to get it.

In my experience, consistency is one of the most important thing. Example: my service exchanges data with other services, they will have the same schema where they agree how an object looks like.
When other service sends me a kebab request with β€œketchup” is null, I understand this kebab should be made without ketchup.
If the β€œketchup” key is undefined, I understand that this kebab is on hold, because its definition is incomplete.
But if the β€œketchup” key is missing? Wtf? Maybe the request was broken in the middle? Maybe the other service updated new schema and changed the key? Maybe the β€œketchup” key is deprecated?
And it turned out it was omitted because some guy thought it’s cool to save some characters…

I understand that’s there is always a way to handle it. But ask yourself: Would you like to write inconsistent code with confidence, or you’d prefer monkey patches here and there with thousands of conditions?

Happy Befana btw

 
loucyx profile image
Lou Cyx • Edited

Maybe null make life easier when you are not using TypeScript ...

You can use TS without using TS, by typing with JSDocs. Try this out in VSCode:

// @ts-check

/**
 * @typedef User
 * @property {string} id - The user's unique ID.
 * @property {string} name - The user's name.
 * @property {string} [email] - The user's email address (optional).
 */

/**
 * Get a user by ID.
 * @param {string} id 
 * @returns {User}
 */
// Red squiggly here, because we are missing `name`:
// But if you miss `email` is ok because that one is "optional"
const getUser = id => ({ id });
Enter fullscreen mode Exit fullscreen mode

Thanks a lot for that last line, Roman. I greatly appreciate some good vibes in this comment section ❀️

 
loucyx profile image
Lou Cyx

In my experience, consistency is one of the most important thing

If you really think about it, we can have that same consistency with undefined instead of null. At work we have contracts between the API and the client. We translate the type definitions from the back-end in .d.ts files we can use from TypeScript and JavaScript. When something is optional in the back-end, the type is Type?, not Type | null or Type | null | undefined, so we know with confidence that we can send a value or undefined (namely, not send it). Same applies for the things we get of the back-end, if something we fetch could be missing, then the type is Type? again, which means it can be undefined, and we deal with that either with default values, optional chaining and all that sweet tech we have to deal with nullish values.

Long story short, you have a convention to use null and undefined, that convention could use only undefined as well and work ... and this still doesn't negate the fact that you can use undefined everywhere, except when you talk with the back-end.

I updated the article adding some other folks that have a similar opinion to mine (even a feature request in consideration to update Angular so it only uses undefined).

 
romeerez profile image
Roman K • Edited

Offtopic, I was reading your previous article and wondered why to compose functions in this way update('user')({ ...data })(user), looks like you are intentionally keeping all functions with one argument, and I don't understand why not to have a function with 3 arguments instead. I'd love to read a new article from you "You don't need second argument" explaining this pattern. Looks crazy tbh.

Also "You might not need" is a way better naming, because people need classes in Angular, indeed they need mutation in Vue, null in response from database.

 
loucyx profile image
Lou Cyx • Edited

Is a pattern called currying, it allows you to create functions from functions and make everything more reusable:

const update = property => value => object => ({
    ...object,
    [property]: value,
});

const updateName = update("name");
const blankName = updateName("");
const nameFoo = updateName("foo");

const blanks = [
    { id: 1, name: "Luke" },
    { id: 2, name: "Roman" },
].map(blankUser); // Returns the array with `name` on both set to ""

blanks.map(nameFoo); // Returns the array with `name` on both set to "foo"
Enter fullscreen mode Exit fullscreen mode

Maybe is not the best example, but basically you can create updateX and reuse it all you want, with different values for different objects ... compared to doing the same with 3 arguments:

const update = (object, property, value) => ({
    ...object,
    [property]: value,
});

const blanks = [
    { id: 1, name: "Luke" },
    { id: 2, name: "Roman" },
].map(object => update(object, "name", ""));

blanks.map(object => update(object, "name", "foo"));
Enter fullscreen mode Exit fullscreen mode

We need to write update and pass every argument with every use. Currying is one of those things that when it "clicks", you never go back. A popular library that uses this a lot is Rambda.

Also "You might not need" is a way better naming, because people need classes in Angular, indeed they need mutation in Vue, null in response from database.

I considered that rename a few times, but my point is that you don't need it in your own creations, that's the main reason I have my articles set so that is more for "almost experts" than "beginner". Beginners using other folks tools need to learn all this stuff, way later we learn about the things we don't need to code, but might need to interact with somebody else's code.

Screenshot of a experience level set to 7 of 10

 
romeerez profile image
Roman K • Edited

Thanks, but still more in depth article would be awesome. By your example I can see that in plain JS carrying is worst thing to happen: if you see somewhere in the code nameFoo, you'll probably assume it's a simple string, while actually it's a function which takes object and returns new object with updated name property. With TypeScript it's still very confusing, okay we can hover and see the type, but I assume it will show a complex barely readable type. And I hardly imagining how it is even possible to write all this types, how much complex generics will be needed for this, how much unions.

In TS function "update" has to take a union of all table names. And what if for one table update should have different implementation?

While in true FP languages there is a function overloading and you can define them separately for different tables, which is removing all downsides and sounds awesome. But JS/TS is not such language so it's a good question if the same approaches really worth to be used here? (I didn't ever worked with FP language)

I assume you are following this carrying approach on real projects and you know how to deal with difficulties on this way, so let me repeat myself, isn't it bright idea for article? There are a lot of articles on the topic ofc, but they only sharing basics and explaining nothing useful about how to deal with it in real world, when to use and when to avoid. Not insisting of course, maybe there are such articles and need to search harder and play with it myself

 
loucyx profile image
Lou Cyx • Edited

Sure thing! I'll create an article about currying and mention you on it for pushing the idea if you don't mind ^_^ ... about the TS implementation, that update function could look something like this:

const update =
    // Generic for the property name
    <Property extends PropertyKey>(property: Property) =>
    // Generic for the value
    <Value>(value: Value) =>
    // We expect the "Source" object to be an object of unknown properties
    <Source extends Record<PropertyKey, unknown>>(
        object: Source,
    // We return that Source combined with the new property and value
    ): Source & { [property in Property]: Value } => ({
        ...object,
        [property]: value,
    });
Enter fullscreen mode Exit fullscreen mode

I do love to do TS + FP, so creating an article about this might be interesting for other folks as well πŸ˜„

 
romeerez profile image
Roman K

Awesome! No one yet wrote article in my honor, I'm flattered :)

I wish it could explain "Why" and "When to use" no less than "How to use", so pros and cons.

So far carrying looks like a lego and there is something in it, definitely there is something about it.

Some comments have been hidden by the post's author - find out more