9 ms·
If I may ask a related question, how do lispers deal with the absence of a real boolean type in CL? My mind absolutely boggles at the prospect of not being able
by s_kilk 9y ago
If I may ask a related question, how do lispers deal with the absence of a real boolean type in CL? My mind absolutely boggles at the prospect of not being able to tell the difference between null, false, and the empty list.
Am I missing something?
- taeric 9y agoI find in most cases, I just don't care about the difference. That said, pretty sure you can find the specific answer if you need it.
- juki 9y agoMost of the time there just isn't any need to make a difference between the three. There are basically just two common situations where you need to be able to tell the difference: 1. Telling apart optional arguments to a function that weren't given from arguments that were given and were NIL. Typically giving a default value for the argument is sufficient to avoid the problem, but you can also add an extra parameter that tells whether the argument was given. (defun foo (&optional (x :default-value xp)) ;; X holds the argument given by the caller, or the keyword ;; :DEFAULT-VALUE if nothing was given. ;; XP is T if the argument was given, or NIL if it was not given. ) 2. Finding an item that may be NIL. Functions such as GETHASH (which looks up a key in a hash table) usually allow you to specify a default value, and they will return a second value to indicate whether something was found. ;; Increment the value of KEY in TABLE, or if the key doesn't ;; exist, use 10 as a default, increment that and set the ;; result to the table. (incf (gethash key table 10)) (multiple-value-bind (value existsp) (gethash key table) ;; VALUE holds the value if it exists, or NIL if it doesn't. ;; EXISTSP is T if the key exists, or NIL if it doesn't. ) Notice that in CL additional return values can be ignored if you're not interested in them, so you only need to use MULTIPLE-VALUE-BIND when you actually need it. You could also wrap it in a trivial macro if you're using it a lot. (defmacro if-some-let ((var form) &body (then &optional else)) (let ((somep (gensym "SOMEP"))) `(multiple-value-bind (,var ,somep) ,form (if ,somep ,then ,else)))) (if-some-let (val (gethash key table)) (format t "Found: ~a~%" val) (format t "Didn't find.~%"))
- kazinator 9y agoYou can't make a difference; there is no three: it's all one object. If you want an empty list that isn't nil, you have to implement your own list type. If you want a symbol called "NIL" that isn't self-evaluating and Boolean false, you have to use your own package.
- dreamcompiler 9y agoIt can be a problem in advanced Lisp programming where you'll end up reinventing things like maybe monads. But it's never a problem in simple Lisp programs. (After all, C has the same issue with booleans.) Can you be more specific about your concerns?
- s_kilk 9y agoI'm most concerned about interacting with other systems, like databases, http services, whatever, where the data that's being worked on includes such values as `false`, `null`, and `[]`. In that context it's hard not to simply conclude that CL would be at a disadvantage.
- juki 9y agoIn that situation you could simply represent `null` as a keyword :NULL, `false` as NIL and `[]` as a vector (or some other non-list datastructure).
- dreamcompiler 9y agoExactly. Symbols are first-class runtime objects in CL, so just make up a symbol name for the various null-like values. Keywords are the easiest symbols for this purpose since they're package-agnostic.
- diogofranco 9y agoThis is a legitimate concern - I've built some production lisp services that handled JSON back and forth, and you need to address this specifically. My solution was to hook custom encoders and decoders to `cl-json`, converting null to :null, false to :false, and [] to nil. Other conversions are possible of course. This scheme has the advantage of allowing you to naturally iterate over JSON arrays as lists in application code, but you have to take care of the fact that :false evaluates to T, by for example writing your own macros for "if". This might read like a nightmare, but Lisp makes it really easy to customize everything about it self, which can be beautiful, if you're into that.
- aerique 9y agoIt's rarely an issue for me and more often a convenience. Perhaps because of the surrounding language?
- coldtea 9y ago>Perhaps because of the surrounding language? Probably not, since C also has the same issue, and it's not very important there either, while at the same time being totally different as a language from Lisp.
- coldtea 9y ago>My mind absolutely boggles at the prospect of not being able to tell the difference between null, false, and the empty list. What's so strange about it? That's how it has always been in C for example, Python didn't have a boolean type either for a good while, and lots of languages are like that.
- kazinator 9y agoFirstly, we have to consider that there are programming languages (some heavily advocated to the death) in which you simply cannot have a variable, such as a function argument, which at different times during the execution of the program can sometimes hold an empty list, sometimes a false value and sometimes a symbol. It is actually the same in most dynamic programs also. A given expression, variable or object slot is understood to be a list, boolean flag or a symbol. The fact that the domains of lists, Booleans and symbols happen to intersect is not relevant and doesn't really even come up. "Oh, this value a symbol too; nice, but we're not using it as one". The empty list being false keeps a fair bit of unnecessary verbiage out of list manipulating code. Variables and object slots being initialized to a default value of nil means that they serve as empty lists or false booleans, which is handy. A block of code which requires an initially stack can just be wrapped with (let (stack) ...). stack is ready to be pushed upon. Really, when we consider everything, there is actually one item that is sometimes a bother: in a nested list structure, there is ambiguity between nil being just a symbol and a nested list. These two objects are equivalent: (a nil b) and (a () b). If you're dealing with such data in which you need every possible word to map to a symbol that isn't an empty list, use a separate package, so that the "nil" token in the external data goes to mypackage:nil and not common-lisp:nil. (Treating such external data in a separate package is a good idea for other reasons, too).
- brudgers 9y agoTraditionally, in cases where it matters it is common to return two values, e.g. (values value present-p) For example, `get-hash`: http://clhs.lisp.se/Body/f_gethas.htm http://clhs.lisp.se/Body/f_gethas.htm