It’s not silly. But you have some other possibilities, too. Some are more commonly used, I think.
- Return a single value, which is either a success value or an error value. In Racket docs you’ll see many functions return, say,
(or/c number? #f)
. If#f
among the valid success values, and/or if that’s too blunt, you could return symbols like'error-foo
'error-bar
to represent each kind of error. Regardless, it’s pretty easy to consume such functions usingmatch
.
- Return multiple values instead of a list. I suppose if you want to force callers to deal with an error/success code as well as the usual value (is this like go-lang?? idk) then you could do this.
- For an exceptional condition, throw an um exception.
Oh, one thing about what you said here: > the “natural” null of Lisp In many (most?) lisps, ()
or nil
is falsey (for if
etc.). But in Scheme and Racket, '()
or null
is truthy. i.e. There is no “nil punning” where the empty list means “false”.
The only false value (for if
etc.) is #f
(or an alias thereof like #false
).
^ @slack1
I think that’s good, because true and false are meant to be used in boolean contexts, and are also values
but I wanted the concept of “empty”
oh I see, I was kind of talking about a “functor”
wow, these docs explained that concept for the first time
that was always an opaque word to me
basically a type that admits an empty thing, and can have operations chained on due to its regular shape
but it implies that once you eat a nothing type
all following functions must expect it
hm
I have the intuition that once you have a “funny” condition in your app
all functions in your app can be divided into two sorts
the kind which has to check for the funny condition
and the kind which doesn’t
A functor sounds almost like a stream
or to enjoy the maybe type
every function must still check for it
A functor is just a thing with a map
operation for which (map identity x)
is a no-op and (map (compose f g) x)
is the same as (map f (map g x))
, no more and no less, but the “container” metaphor is a useful one all the same. But in your case, I think the specific datatypes (maybe and either) are probably more interesting than the interfaces they implement.
Generally, “every function must check for it” is an indication that you are doing something wrong, since you usually use data/monad
to chain together operations that produce optional values and short-circuit if any of them fail.
Alternatively, just use Racket exceptions.
I think I have slightly more understanding;
functions that return maybe types require a “framework” to operate around them
and when they do
you don’t have to double-check for empty
Yes, that’s essentially right.
The advantage of maybe or either over, say, exceptions, is that they are plain values, and you can do things like, for example, sticking them in a list.
But if you aren’t doing anything like that, and you just have some operations that ought to fail and short-circuit the whole program (or a significant portion of the program), just use exceptions.
I’m contemplating for a mini library
so I am trying to avoid exceptions
or I don’t know if people just expect it
and so they won’t mind
If it makes sense for a function to fail when given certain inputs, the idiomatic solution in Racket is to raise an exception. If the user is likely to want to regularly silence that exception, the idiomatic solution is to equip the function with an optional failure-thunk
argument, such as the third argument to hash-ref
. http://docs.racket-lang.org/reference/hashtables.html#%28def._%28%28quote._~23~25kernel%29._hash-ref%29%29