DEV Community

Discussion on: You don't need null

 
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