I’ve been casually perusing Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp. One of the things I am noticing is that Lisp traditionally has a terrible lack of sentinels: special objects denoting some kind of meaning. Specifically in Common Lisp the empty list and false and nil are all the same thing. As a result there’s all these cases where you want to distinguish false from empty, especially when false represents a failure of some sort. In these AI examples, usually a failure to find something, while in many cases the empty list could mean "the thing is already found, no need to look". But there’s also lots of other examples when this causes problems.
More modern languages usually distinguish between these objects. Python for instance has [], False and None. They might all test as "falsish", but if you care to tell the difference it is easy to do; especially common is a test for x is None. Modern Lisps also stopped folding together all these notions (Scheme for example has #f for false as a completely distinct object, though null and the empty list are still the same). XML-RPC is an example of a language missing null… and though JSON is almost the same data model, it is a great deal superior for having null. In comparison no one seems to care much one way or the other about making a strong distinction between True/False and 1/0.
These are all examples of sentinels: special objects that represent some state. None doesn’t mean anything in particular, but it means lots of things specifically. Maybe it means "not found" in one place, or "give me anything I don’t care" in another. But sometimes you need more than one of these in the same place, or None isn’t entirely clear.
One thing I noticed while reading some Perl 6 examples is that they’ve added a number of new sentinels. One is *. So you could write something like item(*) to mean "give me any item, your choice". While the Perl tendency to use punctuation is legend, words work too.
I wonder if we need a few more sentinel conventions? If so what?
Of course any object can become a sentinel if you use it like that, None isn’t more unique than any other object. (None is conveniently available everywhere.)
Any seems useful, ala Perl’s *. But… there’s already an any available everywhere as well. It happens to be a function, but it’s also a unique named object… would it be entirely too weird to do obj is any? And there’s very few cases where the actual function any would be an appropriate input, making it a good sentinel.
No related posts.
Python’s equivalent of “*” might be Ellipsis – http://docs.python.org/library/constants.html#built-in-constants
Ellipsis always seemed like a weird syntactic afterthought. I’m not even sure how you encounter an Ellipsis. One problem with the name is it refers to the punctuation, not the intent.
Well, ellipsis means “lack of” (think an ellipse, also: it’s not a circle, it’s lacking :), so your worries about the meaning of the word can be reduced to only those that know the usage of the word in English, who listen the word “ellipsis” and think “…”. They can’t be that many, can they?
My preference has often been to create a unique sentinal value for any particular use. So if my list-sorting routine needs an end-of-list marker, I might create “end-of-list” as an arbitrary value for that purpose (typically an arbitrary Object or whatever equivalent the language has).
The advantage of this approach is that I can select a self-documenting name for the object and that I don’t have to worry about someone wanting to use my marker (I can imagine sorting a list of functions that happened to include “any”. I can’t imagine someone needing to sort a list containing the “end-of-list” object that I created within my own module.
The biggest disadvantage of this approach is that those who use my function must import or otherwise access the constant from my module.
One more: in SQL (at least, in Oracle), NULL is the empty string. So if you allow a VARCHAR column to be nullable, you have to be careful about inserting ” and getting back (from the Python-Oracle bridge) None when you expected a Python string.
I sometimes use a
missing
sentinel to explicitly denote the lack of a value in “polymorphic” contexts whereNone
means something different than “no value”.The
getattr
builtin provides a nice example of where this comes in handy. PassingNone
for the third argument means “useNone
as the default instead of raisingAttributeError
“, not “pretend I didn’t supply this argument”. If we wanted to implement the curiously-absentgetitem
and give it analogous behavior, we could default the third parameter tomissing
, which gives a nicer interface and implementation than playing*args
games:default
being a named argument enables other niceties, likepartial(getitem, default=-1)
.One gotcha with sentinels is pickling – you don’t ever want two distinct objects both pretending to be “the” sentinel, since comparisons will usually be identity-based.
__reduce__
to the rescue:The other way of course is to use
*args
, but that’s a tremendous pain in the butt. Somewhere I saw someone useKeyError
as the missing value (for something dictionary-like that raised a KeyError; AttributeError would be reasonable in other situations I imagine). This seemed fairly nice, and the class object for the exception is a very uncommon thing to pass in. Using a general object for “missing value” can at times leak through and cause unexpected behavior (which is a bug, but can be a hard bug to default); if you make custom values then it’s much less likely.That’s an argument for creating your own sentinels – you can see their meaning, and you’ll always need one more if there is meaning assigned to them. And that’s probably the reason for not having them in Lisp – Lisp is relatively small, let the developer create what is needed.
I’ve always used a module-level “_marker = []” for non-shared and non-persistent sentinels, specifically because it stays in the module.
I’ve needed an “infinite integer” sentinel for some graph theory work.
Consider the all-pairs shortest path problem, solved with the Floyd–Warshall algorithm. It initializes some of the unknown distances with an infinity.
As a reduce version, it’s one way to represent the bond distance between two points which are not connected.
It’s not a simple sentinel. It should be that x < InfinityInt for all finite x.
As you say, infinity is not so simple. In fact, I would probably not call it a sentinel – it should support arithmetic and comparison operations. Fortunately, Python already contains a suitable implementation, so long as you can live with mixing
float
s andint
s.(Goofy prompt and lack of a comparison example due to the Markdown interpreter double-escaping < and > inside of code blocks).
Well, there’s a reason I said I needed an “infinite integer” rather than “an infinite value” ;) In the ideal case (which I didn’t need), 1/InfInt = 0 and not 0.0 .
In any case it could be done as sentinel if more logic was done in the algorithm. It just means various ‘if’ checks in the right places.
Well, there’s a reason I said I needed an “infinite integer” rather than “an infinite value” ;) In the ideal case (which I didn’t need), 1/InfInt = 0 and not 0.0 .
In any case it could be done as sentinel if more logic was done in the algorithm. It just means various ‘if’ checks in the right places.
Indeed an infinite integer could be helpful for the implementation of some algorithms. Both a positive integer, and a negative integer, for that matter.
In C we could get away with INT_MAX as long as we knew everything was guaranteed to be in int range. In Python there’s no largest integer per-se, which is why such a sentinel would indeed be useful.
Other than that, I’m fine with None. Just like in Lisp, where nil is the only real sentinel (together with ()) and they manage just fine.
In regards to “words work too,” this is, of course, true. However, in this particular case, “*” has meant a wildcard glob since the introduction of the Bourne shell in the Unix world and since the dawn of time from the DOS/Windows perspective. That covers the three primary computing platforms of the modern world (Linux, Mac/OS and Windows) along with countless other Unix derivatives, VMS, Amiga/DOS, Domain/OS, etc.
Even Python uses “*” for its import syntax to mean exactly the same thing:
from foo import *
The real reason not to use “*” in this way is that it already has two very different uses in Python (three if you count *args and **kwargs as distinct) and it doesn’t need a third (fourth). I think that the lack of grammatical ambiguity is a more interesting distinction between Python and Perl than the ratio of punctuation to non-punctuation. While there are interesting arguments for which approach is more useful for what reasons, it’s clear that the correct solution is consistency with the existing language, and in Python’s case that shoots down “*” in this case much more effectively than any degree of punctophobia.
Hi Ian,
From your Perl6 example it seems that its use of ‘item(*)’ seems analogous to what would be ‘random.choice(item)’ in Python. I prefer the explicit Python version in this case.
No, you’re incorrect about what
*
does, as is the blog author, I think.*
in Perl 6 means “glob” in the shell sense, but taken to a functional/MMD extreme. For example,$foo .. *
would be similar to$foo .. Inf
, but is not specific to numeric types. What your type does with*
is a matter of your type’s semantics (and perhaps those of a parent type), but the user will reasonably expect some sort of “infinity” or “all in range” behavior. For subscripting,@foo[*]
would yield a shallow copy of@foo
much like[ x for x in foo ]
in python. However, for multi-dimensional arrays, this would be more handy:@foo[*; 1..8 ; *]
For method and function dispatch,
*
dispatches like the Any type, but has no defined value. Thus the recipient would have to determine what they wanted to do with it.But, in Python, I do think that a “just give me something” value might be interesting. For example generators might be able to do something useful with this, avoiding generating intermediate objects, but that’s not Perl 6′s
*
.For those interested in advanced uses,
*
also supports expression currying. For example:* + $x
curries up a function which returns
$x
plus its parameter. Thus:my $x = 1;
my $y = * + $x;
say($y(7));
should print “8″. Currying has all sorts of interesting uses, though this example is trivial.
See for more information.
Unfortunately my link got eaten. [Here is S02's info on *](http://perlcabal.org/syn/S02.html#line_907 “S02″)
Thanks Aaron for your comprehensive correction :-)
nil for false is a little strange, true, but Lisp gives you all ever need – & more – with symbols.