As someone who has only played with Ruby briefly, and at that many years ago, I feel that this article could have done more to explain the author's concepts to those not familiar with Ruby.
From what I can see, how is this much different than a Null check? I agree it looks a bit better, but what is the real difference? Isn't there still a check for if the Person object exists?
In terms of C++ or C#, is it correct to imagine this as an iterator over one object where inside my iteration loop I am passing my object into a lambda? (Seems overly complicated so I am guessing this is the wrong interpretation?)
Indeed a post at the bottom of the page mentions using foreach, but since sane languages have closures now days (thankfully! Life is so much better now!), how would I apply this to, say, C++?
The difference is that the null check is front and center, instead of hidden.
In C# or Java, the member access expression "a.b" contains a null check. There's no way to do a member access that doesn't do a null check (for reference types)! It's impossible to distinguish member accesses that were expected/assumed by the writer to not fail from member accesses where the check is relied upon.
With an option type / a maybe monad / 'using telling instead of finding', there are no implicit null checks. Only explicit checks, and only when necessary. Either you use Option.map, to do a check and propagate nulls, or you don't, in which case there's no null path. You don't have to worry about null reference exceptions, making a check where you shouldn't, or missing a check where you should have, because the compiler prevents you from making those mistakes.
The difference seems to be that instead of checking for whether a single object is returned, to actually check for the size of the collection returned, which (depending on your language, of course) the method used to iterate over that collection does automatically.
> From what I can see, how is this much different than a Null check? I agree it looks a bit better, but what is the real difference?
The difference is that the "teller" function incorporates both the function of the finder (which may actually still exist and be used under the hood) and the function of the null check, so that you don't have the boilerplate null-check code every time you need to do a null check.
(It may not actually be doing a null check per se, the finder-equivalent may be returning a set of 0 or 1, or 0 to many if using non-exclusive criteria, and the teller iterating over it, but for inherently unique criteria it could be a finder that returns a single object or null underneath. While whether the criteria inherently will always return at most one or potentially multiple objects to operate on may be important to the caller, the underlying implementation isn't.)
> In terms of C++ or C#, is it correct to imagine this as an iterator over one object where inside my iteration loop I am passing my object into a lambda?
The most general teller implementation would operate as an iteration over a collection returned by the finder; for finders with inherently unique criteria, that collection would always have either zero members or one member.
Particular teller implementations may or may not use that general implementation. For unique cases, the teller could just be a call to the finder with a null check guarding a call to the lambda that passes the non-null result of the finder.
which returns the result or a failure indication to:
if_object_in_database_do { this_with_the_object }
The handling of a failed search is now internal to the API instead of in the client code. In this example, 'this' is simply not executed if the search fails. The client code is passing instructions on what to do when the search succeeds instead branching on success or not.
The idea of passing code in addition to data is a powerful one and when the language makes it easy (Ruby blocks, Javascript function objects, lisp functions) it really does change the way you think about the design of APIs.
I didn't mean to come off sarcastic. Though, reading it back, I can see how you could have thought that. I'm enthusiastic whenever anyone inadvertently discovers functional concepts.
The underlying pattern is captured by the Maybe monad. Its bind rule takes (1) a computation producing a value of type "Maybe a" and (2) a function accepting an "a" and producing a computation producing a value of type "Maybe b". It then returns a new computation that will produce "Nothing" if the first computation produces "Nothing"; otherwise, it will take the value it received from the first computation ("Just x") and pass the value "x" to the function (2) to get a new computation, which it will then perform. Thus if either the first computation or the second produces Nothing, the result will be nothing. Further, if the first produces Nothing, the second will be short-circuited away and never evaluated.
These semantics are basically what you implement by hand when you join together computations that may produce NULL results and check for NULLs in between. But now the computer is doing the checking for you, so the checking is hidden and yet never gets forgotten.
For example, here's some Haskell code the provides a simple lookup database from names (type "a") to phone numbers (type "b"):
-- name phone number
persons = [("Joe", "123-555-7890"),
("Tom", "432-555-0987")]
The lookup service for this database may return a result, thus it returns a value of the Maybe type to indicate that it may not be able to find a "b" for every "a" you give it:
lookup :: Eq a => a -> [(a, b)] -> Maybe b
But since Maybe is a monad and its bind rule takes care of the didn't-get-a-result checking for us, we can safely string together lookup computations without having to do any checks by hand. Nevertheless, we can be assured that all the checks will be done.
For example, let's create a function that takes two persons' names, looks up their phone numbers, and (if both are found) connects them with a (simulated) call.
-- try to connect person a's phone to person b's phone
connect a b = do
phonea <- lookup a persons
phoneb <- lookup b persons
return $ "connected " ++ phonea ++ " to " ++ phoneb
If we try to connect two names that are known to our lookup service, the call goes through as expected:
*Main> connect "Tom" "Joe"
Just "connected 432-555-0987 to 123-555-7890"
But if either or both of the name lookups fail, no call is made:
The pattern as presented doesn't actually use the monad instance, just the functor instance, because there's no sequencing of actions involved here, there's just one action (update or not).
Why? What is so bad about having your conditionals be explicit?
What happens when you need an "else" branch?
Is not having a person for the given ID an error, is it a "do nothing", or does it mean to create a new person?
What does the callback version do if you forget to make your index unique, and end up with multiple people with the same ID; does it get run multiple times? The version that returns a Maybe<Person> at least has an obvious proper response (throw an error).
Then you use one, but null checks generally aren't cases of if/else conditionals, they're more along the lines of "either I have one or mayday mayday". Sometimes slightly softened into "either I have one or use this default thing"
more along the lines of "either I have one or mayday mayday".
That would be the Perl way of
my $person = $data_source->get_person($id) or die "No person available";
ie, there's code to be executed (to raise the error) in the case that there is no person. The callback structure from the article doesn't allow for this.
Coming from working with node.js, this feels very strange. How do I call a callback in the else case? It looks nice and all, but I don't see a way to make this work if db calls are asynchronous.
We make something explicit when we want to call attention to it. Otherwise, we should make it implicit.
Attention is a limited commodity, and I think it's worth preserving for the important things. Sometimes it really is worth reminding people, "Hey, this could be null!" But a lot of the time that is boring.
To me this often comes back to favoring meaning over mechanism. The higher up I go in code, the more I want it to be about what the system means, not the particular mechanism I've chosen to make it work. E.g., Computers may be implemented via impure silicon, but most of the time we can forget that. I try to apply that to any sort of mechanism.
Why? Because people won't write null checks some of the time even if they need to. Of course, this solution to that problem introduces a bunch of other problems that you've pointed out, and it would be much better for one's sanity to just use an option/maybe.
Because once it's possible for an object to be null defensive programming means that you need to check damned near everywhere. After every method call, for example. And that's very, very cumbersome to maintain and very non-DRY.
I tend to consider "if" statements to be a bit like "goto" statements - that is, they can sometimes be useful, but mostly the intent of your code will be clearer if you can avoid them. Use with parsimony.
An idiomatic Java/C++ OO approach is to have an interface to the datastore that eliminates the need for the NULL. That way you don't need to know the internal details of the datastore object.
datastore.ChangePhone( personId, number );
could return a bool to indicate success/failure or throw a "personId not found" exception according to taste.
This is normal Java style and is in line with the article's recommended "tell-don't-get" approach.
Requires boilerplate inside datastore object, but hey, that's not exactly news for OO.
OO doesn't mean boilerplate. Just because there's a correlation in languages like C++ and Java doesn't mean there's causation. Most of their boilerplate derive from the type system.
Anybody who's written much JavaScript without a jQuery-like function should appreciate this too -- used to be much more common to have code littered with checks to see if a dom element existed, then do an operation if it did. Performing an operation over a set (including empty) of elements seems easier.
If you did this everywhere your program would be slow as shit due to all the closure allocation in inner loops. Of course, this doesn't matter much for Ruby, which is already slow as shit, but C# and Java people would be pissed.
This is the response I was looking for. We keep making computers 2x faster, and then making software architecture 4x more wasteful (given, it's also 20x faster dev time).
"if (ptr == NULL)" is usually two instructions and an instruction pipeline flush. That sucks, but I can't even imagine what the processor ends up executing for the Ruby lambda callback. Slightly more readable code at what cost?
> That sucks, but I can't even imagine what the processor ends up executing for the Ruby lambda callback.
That's not a very good question, `if object == nil` will already be a pretty huge number of instructions in ruby.
Now if the question is "could you have this pattern compile down to little more than a null check", the answer is why not? A bit of flattening/inlining should be able to handle it correctly.
And in most fields, the gain in safety way outweighs the almost unnoticeable (against background noise) loss in efficiency.
> The answer for C# and Java, which this issue was directed at, is no.
I'm not sure what you're talking about, you focused on java and C# for odd and unknown reasons, TFA merely uses java as an example of syntactically heavy (or even missing) closures language to illustrate the pattern's gains and differences.
> I think it is safe to assume the article is about Java.
No. The article is about a pattern of explicit null-check avoidance, java is an example of "a language without blocks or lambdas" but the article is no more about java than it is about ruby, you're not even missing the forest for the trees you're missing the forest for a fallen leaf.
> "if (ptr == NULL)" is usually one instruction and an instruction pipeline flush. That sucks, but I can't even imagine what the processor ends up executing for the Ruby lambda callback.
Probably a lot more than the null-check, and yet still undetectable against the background of the db call.
It's not the extra 1000 instructions of this call that is concerning. It's the extra 1000 instructions per thing-that-would-have-just-been-a-null-check summed over every running process on your machine if this actually took off as a standard idiom.
If we keep adopting readability tricks with orders-of-magnitude differences in speed compared to what we're replacing, we'll just keep getting slower and slower code.
> It's not the extra 1000 instructions of this call that is concerning. It's the extra 1000 instructions per thing-that-would-have-just-been-a-null-check summed over every running process on your machine if this actually took off as a standard idiom.
I think for most cases where what you want to do isn't throwing an error on null, most of the time null checks are addressing external interfaces (database or otherwise) where the IO is going to be enough bigger than the pattern overhead that if you are able to deal with the load from the basic functionality, the difference between null check and using this "teller" style implementation is going to be negligible.
And, obviously, where you want to error-on-null (whether or not an external interface is involved), this isn't the pattern to apply.
> If we keep adopting readability tricks
Factoring out commonly used boilerplate into a library function isn't a "readability trick", though it does benefit readability and maintainability of code.
No, Rust solved this problem by using non-nullable references. Closure allocation was a separate consideration.
The question of whether you can invent some arbitrary language that will enable your scenario isn't an interesting one. The problem is languages like Java and C#, which are heavily used and DO have nullable references. This "fix" is not one.
> No, Rust solved this problem by using non-nullable references.
1. its option types provide a number of combinators which use closures, it wouldn't surprise me at all to see this kind of stuff in rust (might even be more efficient, no need to allocate and return an option)
2. and blocks are heavily pushed for use in Rust, all iteration is closure-based for instance
> The problem is languages like Java and C#
I've no idea where you got that from, TFA merely used java to illustrate the "language without syntactically lightweight functions". COBOL being a turd didn't stop people from investigating objects.
I've no idea where you got that from, TFA merely used java to illustrate the "language without syntactically lightweight functions".
Really? We're investigating nullable reference semantics and C# and Java aren't the target? Hell the initial example -- the one that spurred the discussion -- is in Java.
Anyway, "syntactically lightweight" is a red herring -- syntax is surprisingly easy to add.
And in the likely event that your dataSource is at the other end of a network link, or even in a different process on the same machine, the network overhead will completely swamp whatever memory allocation can't be optimized away.
This dovetails into the classical "tell, don't ask": without good support for anonymous functions or blocks, for a number of operations you'll have to ask an object for parts of its internal state, munge that state then shove it back into the object. Said object may return a command protocol instead, but it's generally verbose and exceedingly rare.
Blocks provide a conduit to provide compound and possibly complex commands while leaving the object itself in charge.
This is a strange article. The biggest problem with null checks isn't that they make code difficult to read - it's that people forget to add them and that can cause bugs and security vulnerabilities. I've never known anybody to complain that they made code difficult to read. Unless they're mixed in with "real" program logic, like "if (obj != NULL && obj->value()<20)" then they're really not too hard to ignore.
And the author doesn't seem familiar with many programming languages. Using his "finders and tellers" idiom isn't as special as he thinks, and doesn't require lambdas or blocks. Here's a similar idea in Python, not using its lambdas:
for person in data_source.person(id):
person.setPhoneNumber(phone_number)
data_source.update_person(person)
# And for other situations:
with open("whatever.txt") as inf:
for line in inf:
print(line)
C++ has had similar functionality in the STL using iterators since before C++98.
Lambdas and blocks are convenient and nice features to have, but they're not required for this.
The issue here is that the semantics are weird, an iteration block is expected to iterate 0..n times, not 0..1. I know I'd never expect a loop primitive to be used as a conditional.
And the context manager can't handle this case, as it's not able to skip the block body.
I do this in my PL/SQL code occasionally, when I'm feeling lazy and either don't want to handle errors properly or want to take advantage of auto-defined iteration variables.
Since it's a nasty dirty hack that tends to misbehave on bad data instead of failing cleanly, I generally restrict it to experimental/throwaway code. Seeing it recommended as a good idea is a bit WTF.
I didn't mean to imply it's a good idea or not. In my own code I'd use the null check and throw an exception. It's suspicious that it's trying to update a non-existent record, so I'd usually want it to break so I can fix the underlying logic bug.
I was simply pointing out that the technique is available in other languages without using lambda and/or blocks.
> Since it's a nasty dirty hack that tends to misbehave on bad data instead of failing cleanly
This assumes that an empty result set of a finder is "bad data"; there are times when this is true (in which case, the basic "teller" idiom as illustrated here isn't a good choice), but there are lots of times where it isn't, too.
The teller strategy can work for those; that's all checking that can be done in the teller (or, in the case of teller-implemented-on-top-of-finder) in the finder. There's no reason to repeat the logic checking for this case all over the application.
It seems like the reason for the author recommending the use of blocks/lambdas is to put the null-checking code in the `data_source` library, rather than user code. That is, if `data_source.getPersonById` doesn't find a person for that id, the block/lambda doesn't get executed at all, and there only had to be a null-check/not-found-check in one place. In your first Python example, both `.setPhoneNumber` and `.update_person` would be executed, and both would have to be smart enough to not persist anything from a "null object" (assuming data_source.person(id) returned a valid `Person` object with null fields).
No, data_source.person(id) will return a collection of 0 or more Person objects, and the for loop will iterate over them and call .setPhoneNumber and .update_person methods on each them. The null check is replaced by iteration over a possibly empty collection.
The author's example code appears to be doing the same thing, but iterating using Ruby's block syntax:
data_source.person(id) do |person|
person.phone_number = phone_number
data_source.update_person person
end
I'm assuming data_source.person(id) would return 0 or more Person objects.
To an extent yes. I'd fully expect the Ruby version to run more code after the block has been executed, the Python version much less so (even though it is possible) for instance.
> The author's example code appears to be doing the same thing, but iterating using Ruby's block syntax
Ruby's block syntax is essentially a method of passing an anonymous function as an argument to a method call. It is used by the standard libraries iteration method (e.g., Enumerable#each), but its use here is not iteration (though the teller could be implemented with iteration under the covers.)
data_source.person(...) appears to be a method that takes an id, attempts to retrieve the person with the ID, and, if such a person object exists, yields the person object to the block. From the code, it doesn't look like it is designed to be a collection (it could be implemented on top of a finder that returned a collection, or a finder that returned either a record or nil.)
To illustrate that this is not iteration, assuming the existence of a DataSource class with a private find_person(id) finder that returns either a person object or nil, the method being called here could be implemented thusly:
class DataSource
def person(id)
p = find_person(id)
p && yield p
end
end
> Many codebases are littered with them [null checks] and,
> as a result they [codebases] are often very hard to understand.
I wager they're referring to null checks complicating the codebase (introducing uncertainty into method signatures and argument values down the chain).
And that's what creates a ripe environment for such bugs and vulnerabilities that you mention.
Anyways, my big a-ha moment in programming was when I started thinking in terms of data-structures and mapping functions/transformations across them.
If you're particularly bothered by this you can just make your query interfaces uniformly return iterables. Which is what already typically happens in your average low-level DB API, in languages lambdaful and lambdaless.
This is a pretty neat pattern. One nice bonus is that the code invoking the closure has a chance to do work after the closure. This lets you do nice "scoped" behavior automatically. For example, in his Ruby code:
data_source.person(id) do |person|
person.phone_number = phone_number
data_source.update_person person
end
I would take that `update_person` call and have the `person(id)` method do that implicitly:
data_source.person(id) do |person|
person.phone_number = phone_number
end
(I'd likely optimize for the case where the person wasn't actually modified too.) This way, the caller doesn't have to remember to explicitly update the person.
Another way to look at this pattern is as a poor-man's pattern match. For example, using the pattern-matching syntax of my language[1], you could do:
match dataSource person(id)
case person is Person then
person phoneNumber = phoneNumber
dataSource updatePerson(person)
end
Granted, that's more verbose here, but it lets you have other cases if that makes sense for your problem. Magpie has blocks too, so a literal translation would be:
dataSource person(id) as person do
person phoneNumber = phoneNumber
end
For very short blocks, you can use an implicit parameter name similar to Scala:
dataSource person(id) do _ phoneNumber = phoneNumber
The `do` notation is just syntactic sugar for passing a function as the last argument, so you can also do:
dataSource person(id, fn(person) person phoneNumber = phoneNumber)
Sounds great except often something needs to be done when a value is null, like giving user feedback. If the answer then is to provide another block, just go back to the if statement.
I love the Object Mentor guys' blog articles, but they get a new blog every 2 years so it's really hard to find their articles in one place.
I remember reading one, I think by Michael Feathers, that was about how sometimes your code gets larger when you refactor and that's expected and good. He compared code to an orange. The rind is the class/method signatures. The pulp is the implementation. If the rind get larger by reducing the size and DRY violations of the pulp, you can consider that worthwhile. If anyone can find this article I'd be grateful. I haven't been able to track it down myself.
He compared code to an orange. The rind is the class/method signatures. The pulp is the implementation. If the rind get larger by reducing the size and DRY violations of the pulp, you can consider that worthwhile.
That sounds very very wrong, as you're making your code's clients do more work / be more complex just so you can simplify your implementation.
Wouldn't simply returning a list and then using your language's favourite foreach construct have functionally the same semantics without the confusion of passing code-blocks around?
Never saw "passing code blocks around" as a source of confusion, and in many languages (Ruby, for a salient example) "passing code blocks around" is the same mechanism as is used in the languages main foreach construct.
I don't know why, but take it as a signal that it this isn't as interesting to the HN community.
If you think about it, it's not avoiding null checks, it's just moving it to a different place. The null check still needs to happen in order to know when to call the block, it just happens to be in the datasource.whatever code.
The point is to move the check out of the library user's way, and out of his ability to forget about it. Same as block-scoped resource patterns (RAII, context managers, unwind-protect, whatever).
This is just the Church encoding of Haskell's Maybe type. It is indeed a powerful way to simulate algebraic sum types when you only have product types in your language.
This does not seem to be fixing null checks. It seems to be fixing lack of proper error handling. The problem is not that you have to check for null, the problem is that you don't know if null is a valid value, and even if you know it's not, you don't know why the method failed. This is simply a lack of a channel for returning errors.
There are a lot of ways to fix this problem... Many modern languages use exceptions for this. Some languages use out parameters. Languages with multiple return values often return a value and an error. They're all essentially equivalent, and yes, they're all better than just returning null.
I don't really see a difference between:
data_source.person(id) do |person|
person.phone_number = phone_number
data_source.update_person person
end
and
data_source.updatePhoneNumber(id, phone_number)
The latter one encapsulates the find, the error handling, and the setting. Yes, you might have a null check in there. Or you might have a try/catch, or you might be checking an out value or a multiply returned error value.... there's really no difference. You've changed the API to signify that the person might not exist and this function might not actually do anything. And that's fine, if that's fine. However, most of the time, if I go to set someone's phone number, I want to know if the system thinks that person doesn't exist.
Somewhere there has to be a check to see if the person with that id exists, and if so, change its phone number. It could be a SQL statement running in the db, it could be a null check, or a try/catch or it could be inside data_source.person(id) do |person|
I'm just saying, it has nothing to do with nulls and everything to do with error handling. This teller pattern handles errors by implicitly ignoring them. If that's your intended behavior, that's fine. It also evidently has some nice language support in ruby, and that's cool. But it doesn't really "solve" any "problem". Mostly because there's no problem in the first place. Error handling is not a problem, it's a fact of life in software development.
Exceptions for null handling? I admit there's room for healthy debate about using exceptions for flow control in various situations, but for null handling it sounds like an absolute nightmare. Instead of considering null at each point it might occur in the code, you have to consider all the possible places a null value might be stacked above you.
Exceptions for error handling... this is just error handling, that was my point. Returning a sentinel value (null) is just one (very suboptimal) way of handling an error.
Instead of dataSource.getPersonById(id) returning null, it could throw a NotFoundException. This is better because it's a specific error (as opposed to a MalformedIdException, for example).
Exceptions can be used in strategic places to ensure that a value returned can never be null. Ex:
public Person findPerson(String name)
{
for (Person p : personList)
{
if (p.getName().equals(name))
{
return p;
}
}
throw new PersonNotFoundException(name);
}
We can be sure that the return value of findPerson will never be null, as it must either return the correct value or throw an exception. Whether this is better, worse, or the same as a null check is debatable.
Can we please dispense with the whole "How did these moron's get anything accomplished before I came along?" pattern?
The article, comments on the article's page, and a lot of comments here are all variations on "This perfectly acceptable practice makes my eyes bleed, and is the calling card of shitty programmers everywhere. After I switch some semantics, rearrange the deck chairs, etc, its now perfect in every way, invulnerable to bugs or misuse."
No pattern is universally applicable.
There are lots of ways to accomplish a task.
Coming up with a different solution does not invalidate what came before.
Surprised no one has mentioned objective-c's sending message to nil which is relied on throughout apple frameworks which is basically what the article says is a lot of work to implement but given to you for free in obj-c: http://stackoverflow.com/questions/156395/sending-a-message-...
C# kinda does this for nullable types, it is referred to as null lifting.
Sadly, it's not too consistent there. For example, this is OK:
[[obj find] doThing]
But this will crash on nil:
[array addObject: [obj find]]
Nop-on-nil can be convenient, but without APIs that also nop on nil parameters, it can be a little annoying when you still have to check half the time.
It may be I'm missing something here. Do we really need to tack block handling onto every method that can change some state? I don't see any benefit over the following idiomatic Smalltalk:
(self dataSource getPersonById: id) ifNotNil: [:person |
"operate on person here"
].
(There's also ifNil:ifNotNil: for where you want to handle both cases.)
The problem I have with his example is that I would probably want to do this:
Person person = dataSource.getPersonById(personId);
if (person != null) {
person.setPhoneNumber(phoneNumber);
dataSource.updatePerson(person);
} else {
user.notify("That's an invalid ID.");
}
So I need an "else", in effect, regardless of whether it's implemented as a conditional, block, lambda, exception, or whatever.
you could have pretty much the same code in any language by doing something like:
List personList=database.personListForID(id)
for(person in personList)
{
person.setPhoneNumber(phoneNumber)
}
Personally I prefer that idiom over null checks, it is a lot easier to read.
I do like the OP names for 'Finder' vs 'Teller' though, that is a good way to describe the difference, that I haven't heard before.
106 comments
[ 4.1 ms ] story [ 195 ms ] threadFrom what I can see, how is this much different than a Null check? I agree it looks a bit better, but what is the real difference? Isn't there still a check for if the Person object exists?
In terms of C++ or C#, is it correct to imagine this as an iterator over one object where inside my iteration loop I am passing my object into a lambda? (Seems overly complicated so I am guessing this is the wrong interpretation?)
Indeed a post at the bottom of the page mentions using foreach, but since sane languages have closures now days (thankfully! Life is so much better now!), how would I apply this to, say, C++?
In C# or Java, the member access expression "a.b" contains a null check. There's no way to do a member access that doesn't do a null check (for reference types)! It's impossible to distinguish member accesses that were expected/assumed by the writer to not fail from member accesses where the check is relied upon.
With an option type / a maybe monad / 'using telling instead of finding', there are no implicit null checks. Only explicit checks, and only when necessary. Either you use Option.map, to do a check and propagate nulls, or you don't, in which case there's no null path. You don't have to worry about null reference exceptions, making a check where you shouldn't, or missing a check where you should have, because the compiler prevents you from making those mistakes.
Applying it to C++: https://github.com/simonask/simonask.github.com/blob/master/...
Applying it to C# (as an augment over null): http://twistedoakstudios.com/blog/Post1130_when-null-is-not-...
Applying it to Java (as an alternative to null): http://java.dzone.com/articles/java-optional-objects
The difference is that the "teller" function incorporates both the function of the finder (which may actually still exist and be used under the hood) and the function of the null check, so that you don't have the boilerplate null-check code every time you need to do a null check.
(It may not actually be doing a null check per se, the finder-equivalent may be returning a set of 0 or 1, or 0 to many if using non-exclusive criteria, and the teller iterating over it, but for inherently unique criteria it could be a finder that returns a single object or null underneath. While whether the criteria inherently will always return at most one or potentially multiple objects to operate on may be important to the caller, the underlying implementation isn't.)
> In terms of C++ or C#, is it correct to imagine this as an iterator over one object where inside my iteration loop I am passing my object into a lambda?
The most general teller implementation would operate as an iteration over a collection returned by the finder; for finders with inherently unique criteria, that collection would always have either zero members or one member.
Particular teller implementations may or may not use that general implementation. For unique cases, the teller could just be a call to the finder with a null check guarding a call to the lambda that passes the non-null result of the finder.
The idea of passing code in addition to data is a powerful one and when the language makes it easy (Ruby blocks, Javascript function objects, lisp functions) it really does change the way you think about the design of APIs.
Twasn't so hard, as long as you don't make your data API return singletons.
Independently discovering Option.map and realizing its usefulness is good. Point at the existing work instead of making fun.
http://en.wikipedia.org/wiki/Option_type (well.. maybe a bit more introductory than that..)
Sounds like programmable semicolon, or a monad, to me.
The example given in the article is:
You can abstract this pattern a little by putting it in a function: Now consider the type of this function: A monad is any type for which you can define bind and return. They have the Haskell types: "maybe_bind" is very similar to "bind"; just replace "m" with "nil or" and uncurry. "return" is trivial to implement.These semantics are basically what you implement by hand when you join together computations that may produce NULL results and check for NULLs in between. But now the computer is doing the checking for you, so the checking is hidden and yet never gets forgotten.
For example, here's some Haskell code the provides a simple lookup database from names (type "a") to phone numbers (type "b"):
The lookup service for this database may return a result, thus it returns a value of the Maybe type to indicate that it may not be able to find a "b" for every "a" you give it: But since Maybe is a monad and its bind rule takes care of the didn't-get-a-result checking for us, we can safely string together lookup computations without having to do any checks by hand. Nevertheless, we can be assured that all the checks will be done.For example, let's create a function that takes two persons' names, looks up their phone numbers, and (if both are found) connects them with a (simulated) call.
If we try to connect two names that are known to our lookup service, the call goes through as expected: But if either or both of the name lookups fail, no call is made:What happens when you need an "else" branch?
Is not having a person for the given ID an error, is it a "do nothing", or does it mean to create a new person?
What does the callback version do if you forget to make your index unique, and end up with multiple people with the same ID; does it get run multiple times? The version that returns a Maybe<Person> at least has an obvious proper response (throw an error).
Then you use one, but null checks generally aren't cases of if/else conditionals, they're more along the lines of "either I have one or mayday mayday". Sometimes slightly softened into "either I have one or use this default thing"
That would be the Perl way of
ie, there's code to be executed (to raise the error) in the case that there is no person. The callback structure from the article doesn't allow for this.Any ideas?
Attention is a limited commodity, and I think it's worth preserving for the important things. Sometimes it really is worth reminding people, "Hey, this could be null!" But a lot of the time that is boring.
To me this often comes back to favoring meaning over mechanism. The higher up I go in code, the more I want it to be about what the system means, not the particular mechanism I've chosen to make it work. E.g., Computers may be implemented via impure silicon, but most of the time we can forget that. I try to apply that to any sort of mechanism.
datastore.ChangePhone( personId, number );
could return a bool to indicate success/failure or throw a "personId not found" exception according to taste.
This is normal Java style and is in line with the article's recommended "tell-don't-get" approach.
Requires boilerplate inside datastore object, but hey, that's not exactly news for OO.
Modern compilers for languages like JS that heavily use closures optimize for this.
"if (ptr == NULL)" is usually two instructions and an instruction pipeline flush. That sucks, but I can't even imagine what the processor ends up executing for the Ruby lambda callback. Slightly more readable code at what cost?
That's not a very good question, `if object == nil` will already be a pretty huge number of instructions in ruby.
Now if the question is "could you have this pattern compile down to little more than a null check", the answer is why not? A bit of flattening/inlining should be able to handle it correctly.
And in most fields, the gain in safety way outweighs the almost unnoticeable (against background noise) loss in efficiency.
The answer for C# and Java, which this issue was directed at, is no.
Email me if you want details.
I'm not sure what you're talking about, you focused on java and C# for odd and unknown reasons, TFA merely uses java as an example of syntactically heavy (or even missing) closures language to illustrate the pattern's gains and differences.
The other day, I saw this example on StackOverflow:
This looks like a typical case where we need a null check.If the canonical example under discussion is in Java, I think it is safe to assume the article is about Java.
No. The article is about a pattern of explicit null-check avoidance, java is an example of "a language without blocks or lambdas" but the article is no more about java than it is about ruby, you're not even missing the forest for the trees you're missing the forest for a fallen leaf.
Probably a lot more than the null-check, and yet still undetectable against the background of the db call.
If we keep adopting readability tricks with orders-of-magnitude differences in speed compared to what we're replacing, we'll just keep getting slower and slower code.
I think for most cases where what you want to do isn't throwing an error on null, most of the time null checks are addressing external interfaces (database or otherwise) where the IO is going to be enough bigger than the pattern overhead that if you are able to deal with the load from the basic functionality, the difference between null check and using this "teller" style implementation is going to be negligible.
And, obviously, where you want to error-on-null (whether or not an external interface is involved), this isn't the pattern to apply.
> If we keep adopting readability tricks
Factoring out commonly used boilerplate into a library function isn't a "readability trick", though it does benefit readability and maintainability of code.
And its safety and correctness.
Rust does exactly this.
> your program would be slow as shit due to all the closure allocation in inner loops.
The closure can be stack-allocated or even inlined. It doesn't need to be more expensive than a C block.
> C# and Java people would be pissed.
Meh.
No, Rust solved this problem by using non-nullable references. Closure allocation was a separate consideration.
The question of whether you can invent some arbitrary language that will enable your scenario isn't an interesting one. The problem is languages like Java and C#, which are heavily used and DO have nullable references. This "fix" is not one.
1. its option types provide a number of combinators which use closures, it wouldn't surprise me at all to see this kind of stuff in rust (might even be more efficient, no need to allocate and return an option)
2. and blocks are heavily pushed for use in Rust, all iteration is closure-based for instance
> The problem is languages like Java and C#
I've no idea where you got that from, TFA merely used java to illustrate the "language without syntactically lightweight functions". COBOL being a turd didn't stop people from investigating objects.
Really? We're investigating nullable reference semantics and C# and Java aren't the target? Hell the initial example -- the one that spurred the discussion -- is in Java.
Anyway, "syntactically lightweight" is a red herring -- syntax is surprisingly easy to add.
Blocks provide a conduit to provide compound and possibly complex commands while leaving the object itself in charge.
And the author doesn't seem familiar with many programming languages. Using his "finders and tellers" idiom isn't as special as he thinks, and doesn't require lambdas or blocks. Here's a similar idea in Python, not using its lambdas:
C++ has had similar functionality in the STL using iterators since before C++98.Lambdas and blocks are convenient and nice features to have, but they're not required for this.
And the context manager can't handle this case, as it's not able to skip the block body.
Since it's a nasty dirty hack that tends to misbehave on bad data instead of failing cleanly, I generally restrict it to experimental/throwaway code. Seeing it recommended as a good idea is a bit WTF.
I was simply pointing out that the technique is available in other languages without using lambda and/or blocks.
This assumes that an empty result set of a finder is "bad data"; there are times when this is true (in which case, the basic "teller" idiom as illustrated here isn't a good choice), but there are lots of times where it isn't, too.
The author's example code appears to be doing the same thing, but iterating using Ruby's block syntax:
I'm assuming data_source.person(id) would return 0 or more Person objects.It's not iterating.
Conceptually, they both say "update this field for every entry having this ID" and at a quick glance the code for each language looks almost the same.
To an extent yes. I'd fully expect the Ruby version to run more code after the block has been executed, the Python version much less so (even though it is possible) for instance.
Ruby's block syntax is essentially a method of passing an anonymous function as an argument to a method call. It is used by the standard libraries iteration method (e.g., Enumerable#each), but its use here is not iteration (though the teller could be implemented with iteration under the covers.)
data_source.person(...) appears to be a method that takes an id, attempts to retrieve the person with the ID, and, if such a person object exists, yields the person object to the block. From the code, it doesn't look like it is designed to be a collection (it could be implemented on top of a finder that returned a collection, or a finder that returned either a record or nil.)
And that's what creates a ripe environment for such bugs and vulnerabilities that you mention.
Anyways, my big a-ha moment in programming was when I started thinking in terms of data-structures and mapping functions/transformations across them.
Another way to look at this pattern is as a poor-man's pattern match. For example, using the pattern-matching syntax of my language[1], you could do:
Granted, that's more verbose here, but it lets you have other cases if that makes sense for your problem. Magpie has blocks too, so a literal translation would be: For very short blocks, you can use an implicit parameter name similar to Scala: The `do` notation is just syntactic sugar for passing a function as the last argument, so you can also do: How did I get derailed talking about Magpie?[1]: http://magpie-lang.org/
I remember reading one, I think by Michael Feathers, that was about how sometimes your code gets larger when you refactor and that's expected and good. He compared code to an orange. The rind is the class/method signatures. The pulp is the implementation. If the rind get larger by reducing the size and DRY violations of the pulp, you can consider that worthwhile. If anyone can find this article I'd be grateful. I haven't been able to track it down myself.
That sounds very very wrong, as you're making your code's clients do more work / be more complex just so you can simplify your implementation.
I believe Perl has something similar but it was less readable.
However this only gets topicalised in bare for & while but not if.
So you could do...
... as long as something_exists doesn't return a list :)More common approach I've seen in Perl (and other languages where variable declarations return its value) is this:
If you think about it, it's not avoiding null checks, it's just moving it to a different place. The null check still needs to happen in order to know when to call the block, it just happens to be in the datasource.whatever code.
There are a lot of ways to fix this problem... Many modern languages use exceptions for this. Some languages use out parameters. Languages with multiple return values often return a value and an error. They're all essentially equivalent, and yes, they're all better than just returning null.
I don't really see a difference between:
and The latter one encapsulates the find, the error handling, and the setting. Yes, you might have a null check in there. Or you might have a try/catch, or you might be checking an out value or a multiply returned error value.... there's really no difference. You've changed the API to signify that the person might not exist and this function might not actually do anything. And that's fine, if that's fine. However, most of the time, if I go to set someone's phone number, I want to know if the system thinks that person doesn't exist.I'm just saying, it has nothing to do with nulls and everything to do with error handling. This teller pattern handles errors by implicitly ignoring them. If that's your intended behavior, that's fine. It also evidently has some nice language support in ruby, and that's cool. But it doesn't really "solve" any "problem". Mostly because there's no problem in the first place. Error handling is not a problem, it's a fact of life in software development.
Instead of dataSource.getPersonById(id) returning null, it could throw a NotFoundException. This is better because it's a specific error (as opposed to a MalformedIdException, for example).
The article, comments on the article's page, and a lot of comments here are all variations on "This perfectly acceptable practice makes my eyes bleed, and is the calling card of shitty programmers everywhere. After I switch some semantics, rearrange the deck chairs, etc, its now perfect in every way, invulnerable to bugs or misuse."
No pattern is universally applicable. There are lots of ways to accomplish a task. Coming up with a different solution does not invalidate what came before.
>> After I switch some semantics, rearrange the deck chairs, etc, its now perfect in every way, invulnerable to bugs or misuse
I don't see anybody claiming that.
C# kinda does this for nullable types, it is referred to as null lifting.
you could have pretty much the same code in any language by doing something like:
Personally I prefer that idiom over null checks, it is a lot easier to read. I do like the OP names for 'Finder' vs 'Teller' though, that is a good way to describe the difference, that I haven't heard before.