Hacker News new | ask | show | jobs
by galaxyLogic 1289 days ago
> sequence of zero or more values

You can try this type of programming at home, say in JavaScript Make any result or argument be an Array. The problem with nulls goes away because "not there" is represented simply by an empty Array (a.k.a "sequence").

And you will not get null-errors because you can write:

   newValue =   someValue.filter(...) . map(...) ;

You don't need to test whether filter() returns an empty array or not, the map() will work for empty arrays too but simply do nothing.

This is in a sense "generalized programming" because you don't deal with individual values but always with collections of them. I sometimes wonder why I don't use this pattern more often.

4 comments

Because now you can't differentiate between an actual empty array or an error. That's horrible.

Or, you actually have to use nested arrays to describe that the result might be an error or an array. But now you have to deal with an array which might contain... zero errors or even multiple ones.

That's really not a great way of doing things. Instead, do it the other way around and make null treatable in the same way as arrays are. And if you do that, you end up with what languages like Haskell do.

> you can't differentiate between an actual empty array or an error

Empty array in this pattern does not represent an error. It represents the fact that no values you asked for can be found. It is like querying a database. It is not an error if your query finds no values.

The calling code that receives the empty array might decide it is an error or that it is not. And of course you can always throw an error, that can be done with the "throw" keyword.

Getting rid of using nulls by using arrays instead is not about handling errors, but about preventing errors, by changing the semantics of your functions slightly so they never need to return null.

You can just replace "error" in my post with "not there" and the meaning stays the same though.
No not really. If every result is an Array then such an array can contain an empty array and then it itself is NOT an empty array. It's all about how you specify the semantic contract of your function.

You can say a result was found and that result is an array, possibly an empty array. Or that no result was found which is indicated by returning TOP-LEVEL empty array.

Well yeah, that's why I said:

> Or, you actually have to use nested arrays to describe that the result might be an error or an array. But now you have to deal with an array which might contain... zero errors or even multiple ones.

In other words: now you have Array<Array<?>>

And now it is only convention that the outer array must only have 0 or 1 elements. But there is no guarantee and the compiler won't help you since it cannot know. That's why this is the inferior approach compared to turning it around and making it so that null can be mapped and flattened.

I had similar thoughts (I do JS/TS dev daily but I’m very interested in a lot of what this language appears to offer). But I’d caution trying this with JS for anything more than exploring the ideas presented by the pattern. The reality is that even adding a type checker like TypeScript, and as many lint rules as you can throw at a project, it’s still too easy to end up putting nullish values into your null-safe arrays.

And I similarly wondered why I don’t use this sort of pattern more often, but that wondering is what led me to add this caution. It’s a really compelling idea, but probably extremely hard to debug when it goes wrong in a language and idiomatic ecosystem designed for it to go wrong at any time.

I guess one reason is that it takes more code to return an Array when in most cases what the caller needs is always just a single value. Many such functions never return null so it is overhead to make them return an array.

But returning an array also makes your functions more general. Maybe in the future you want to modify it so that it does return arrays of length > 1. Evolving the program to that state is then easy if you didn't lock yourself into assumption that this function will never ever need to return more than a single value.

> I sometimes wonder why I don't use this pattern more often.

Because while handy, it's not universally applicable (just like everything else :-))

I (C programmer, mostly) use sequences more often than you'd expect for parameters[1], but that does not mean that it makes sense to always return sequences.

In a lot of cases, sure, a function should return a sequence of values. In many other cases, it makes no sense - `if (canProceed(x,y,z))` reads more sensibly than `if (canProceed(x, y, z)[0]`.

[1] My string functions mostly lend themselves well to unlimited number of parameters. For example concatenation takes unlimited parameters and concats all of them, which it returns. Same with substring searching - take a source string and an unlimited number of substrings to find.

I agree it often makes for more readable code to return a single value instead of an array. But it makes for more maintainable code to return an array.

Whatever you can do with a single value you can also do with an array containing just that single value. How practical that is depends on the language used. In JS after ES6 arrays are syntactically quite nice and easy to use.

I think this pattern is so useful that I've been working on a library to use it for more types of things. https://www.npmjs.com/package/schtate

This includes a state monad that allows you to apply .map just like an array and a maybe monad that let's you do the same on values that might be nullish.

Keep us posted