Closed boneskull closed 8 years ago
Good question! Generally I'm against this kind of APIs for several reasons:
(Observable | any) -> void
, but any
(any type) includes Observable as well, so we can try (any) -> void
, but this is also not good because we treat some values differently than others. A type with any
is good when all values treated exactly the same (for a function like x => [x]
). So it somewhat problematic from types perspective.I think these are good reasons for having to write a bit of boilerplate code.
I understand your first point about "types" up to this (though I'm unfamiliar with the "typing syntax" you're using):
A type with
any
is good when all values treated exactly the same
Can you provide an example of why it's "not good" if a function accepts any parameter, but treats a parameter of a certain type differently?
Off the top of my head, see Array.prototype.concat()
, which behaves differently if the first parameter is an Array
, but can accept any value.
But, as you said, JS is dynamically typed--using instanceof
to check if an Object
"is an" Observable
can/should be avoided. If an Object
can fulfill the contract (whatever that is) of an Observable
, then no coercion should be necessary.
Regarding the second point: It'd be expensive to add this behavior to plug()
only, because then it'd behave differently than other methods in Kefir.
However, if Kefir's behavior was to coerce a non-Observable
into an Observable
whenever it needed one, I'd wager the cognitive overhead would be slim.
So you know why I made this issue:
It's really all about balance. That "surface area" talk seems like a knee-jerk reaction (excuse the pun) to over-abstracted, "magic" APIs. It seems like he's encouraging the other extreme, which is just as painful (for different reasons).
So yeah, I'm sitting on the other end of the see-saw here.
Off the top of my head, see Array.prototype.concat(), which behaves differently if the first parameter is an Array, but can accept any value.
concat()
is a great example actually. I always avoid this shortcut. Suppose we have a code like this:
function foo(x) {
...
const baz = whatever(x)
...
return bar.concat(baz)
}
This code will work fine until baz
happen to be an array. API like that just force programmers to be more careful, and keep more info about the program in their heads in order to modify the program.
Regarding the second point: It'd be expensive to add this behavior to plug() only, because then it'd behave differently than other methods in Kefir.
Right, but if we make that change through all API, this will decrease drastically flexibility of API to further changes (third point). For example now we can safely extend plug method like this:
(Observable) -> void // current signature
(Number, Observable) -> void // new additionally supported signature
Don't know what we might want to pass as a number as first argument, but we just have ability to safely extend API like that, which is great.
I want to minimize the potential for uncaught exceptions; they are not friendly to consumers.
But wouldn't we just hide potential bugs this way? I mean if programmer thought they pass an Observable to plug
, but because of a bug it happen to be null
, and Kefir will just silently accept that value. So it even might seem to work as expected for some time, but then break in completely different place.
I'm all for balance too, and am trying to find a good balance in Kefir.
Probably won't happen closing for now to cleanup open issues.
For example, take
Kefir.pool()
:The following obviously fails:
Would it be sensible, if an observable was not passed, to wrap it in one? e.g.