Also I have made use of improper lists in my code, usually when I need to do something with multiple values and I don't want to mess with multiple return values, or if I just want to create a list of tuples for some reason and don't feel like defining an actual struct (can't think of a good example right now...)
I get what you're saying, but isn't this just a shortcoming of the language? It's pretty common to want to write something like
(define (string-lookup-that-might-fail)
(if (random) <"string you were trying to look up" nil> <nil "It wasn't there.">))
(define <some-string error-msg> (string-lookup-that-might-fail))
without wanting to destructure a list or use the call-values hack. That's a bad example, just pretend there were multiple reasons the lookup could fail.I totally agree about there being pedagogical value in having a simple core. I'm torn between appreciating minimalism and believing that it's ok to introduce complexity to a language that people will use to get things done every day, so long as it's useful complexity and not just historical accident.
So I think that is actually a good argument for introducing option types (like Haskell's Either a b) into the language. I think Typed Racket has something like this, but in either case you'd want some nice syntax to handle it as well.
Hash tables make this sort of thing fairly easy though (at least in Racket)
(define h #hash{["a" . 3] ["b" . 5]})
(hash-ref h k "failed")
I think most lisps support this right? I remember seeing something similar in pg's bayesian spam filter code.
I need to look into Typed Racket more. Ever since I learned the tiny bit of Haskell that I know, I can't help but think that I need ADTs in my parenthesis.
Right at the top of the docs, though, it says that they designed the Typed Racket type system to provide static typing for existing untyped Racket programs. That makes me wonder what a statically typed lisp that wasn't trying to be backwards compatible would look like. I've heard of Shen and Qi but at first glance they (especially Qi) seemed to be drifting too far from Lispy prefix notation goodness for my taste.
> Hash tables make this sort of thing fairly easy though (at least in Racket)
It doesn't really help if you're trying to look up a string, though, because then you can't differentiate between a "good" string and an error string based on their types. That means it won't help for similar string fetching operations either, like making a network request and expecting either a response or a string indicating that the request timed out, the connection couldn't be made, etc.
According to the docs, you can also pass an error continuation to hash-ref instead of a failure string. That's interesting, though I have to admit I have trouble thinking in those terms.
This makes no sense. You want feature X (the ability to return multiple values) without having to either (a) pick apart a simple structure containing those values or (b) assign names to those values, letting the runtime system pick them apart (or never stick them together, its choice) for you. So what more do you want?!
You want an option type, right? Well what's an option type? Let's consider Haskell:
data Maybe a = Nothing | Just a
No matter what kind of cleverness you do in memory or whatever, in the end this type is a tuple of two values: (constructor, data). The only special thing going on is that the type system makes sure that (a) constructor == Maybe or constructor == Just, and (b) if constructor == Maybe the data field is meaningless (has no type, can't be read, does not effect equality, whatever).
But it's still a tuple. The cons cell is the exact same thing in the absence of a strict compile-time type system, except for your code example we have it flipped: (data, constructor) (well, actually you seem to have a flipped 'Either String a' going on there, but let's stick with option for now) where data is actual data or nil when meaningless, and constructor is like Just when nil and like Nothing when true (btw, lisp convention is the other way around; second value is true like Just and false/nil like Nothing).
Just like the original linked article, what your complaint boils down to is "non-compile-time-typed systems allow you to do things which would break compile-time-typed systems"—and while I love compile-time typing as much as the next Haskell aficionado, I don't demand compile-time typing in a language [family] whose very essence is its absence.
EDIT: if you could reply and explain what would resolve this "shortcoming of the language", maybe I could understand better what you're trying to say...
I was actually aiming for (b) here, just incorporated directly into the normal function call and return syntax. Why shouldn't you be able to return multiple values in registers or on a results stack without "tupling them up"? It's clearly supported by the hardware. By "call-values" (sp) hack, I meant that needing to use "call-with-values" and "values" is the hack. Lua, for instance, lets you (with very clean syntax) return multiple values from a function, and assign the results to separate variables, during which at no point is an intermediate table constructed:
thing, err = getthing()
if thing == nil then print(err) return nil end
do_stuff(thing)
I don't think I got it quite right with my angular brackets, but I was trying to think of a nice, clear syntax for multiple return values that wouldn't look out of place in a Lisp.Maybe I was being misleading by using an example that resembled option types. I wasn't trying to bring static typing into this, though I am interested in static type systems and curious about static Lisps, it's just that emulating simple option types for error handling purposes is a very common usage for multiple return values in dynamically typed languages (and, cough, static ones like Go that have lame type systems).
> data Maybe a = Nothing | Just a
> No matter what kind of cleverness you do in memory or whatever, in the end this type is a tuple of two values: (constructor, data).
Is that true? "Maybe" seems like the perfect candidate for a nullable pointer representation, or at least some kind of small tag. I'd be surprised if GHC represents Maybe as a full blown tuple.