I wonder that there is any better way to treat the result of test condition like below. (let ((res (do-something)))
(if res
res
(do-fallback))) I want to (do-fallback) if (do-something) is #f, otherwise return the result of (do-something).
for more concrete code: (let ((idx (index-of lst1 sym)))
(if idx
idx
(index-of lst2 sym)))
(or (index-of lst1 sym) (index-of lst2 sym))
Another possibility:
(cond
[(index-of lst1 sym)]
[else (index-of lst2 sym)])
The first one is more “idiomatic”
Yeah, I wouldn’t really recommend the second one for this particular problem :wink:
a case for resyntax?
Sometimes I wish or is not a control flow operator
for what cases?
Well, actually, let me clarify what I meant by that:
I actually have no problem with or as a control flow operator. The short circuiting behavior is perfectly fine.
But when I read code, at least for most programming languages, I would expect an or expression to produce a boolean.
Using (or rather, abusing) or to produce a non-boolean (that is to be used as a non-boolean value) hinders readability, IMO. It’s clever when you see this trick for the first time, but when it’s used all over the place, I think it creates a headache.
Just to be clear, I’m not against an operator that has or semantics. I just wish it’s not named or.
ah, so what you want is for or to actually produce a boolean?
Not really. Especially if you want operands of or to be in the tail position, I don’t think you can enforce booleanness. And I don’t mind or resulting in a non-boolean, as long as the result is used as if it’s a boolean.
It’s just a social problem. There should be another operator that does what or does, and people should use it instead of abusing or when they want to produce non-boolean.
Agree, in fact I feel that or is more like search (I often use for/or to search for a value).
or first-not-false
or or-else, idk
With a doubly mutable version or-else!! for naughty booleans
I think I’ve got one like that for and already, so if I don’t have one for or it would be a fine addition
I had forgotten about that aspect of cond, and the test-expr => proc-expr one also, so thanks!
The Scheme/Racket’s or is not just logic or, it is something like a Maybe Alternative or MonadPlus with Left Catch in Haskell.
I like, that the short name or is used for the most common operation (short circuiting or). So I am fine with having to write logical-or or boolean-or for the version that returns a boolean.
I mean (and v #t) is the essence of “boolean-ize v” (make it #t or #f). So you can always wrap that around the or:(and (or ___) #t)`.
But whether that is beautiful, or, the sort of thing @sorawee wants to minimize…?
I think this aspect of programming is a “know your audience”. Either is fine if some team prefers it. It probably also helps to be consistent within some unit like a project or file or at least a specific function definition. :slightly_smiling_face:
Maybe just don’t add a logical-or named oar because it’s clever because “the paddle has two sides”, and otherwise I’ll be fine with anything personally. :stuck_out_tongue:
Combining and and or seems very useful, because it can be seen as a path through a tree. (and (or (and (goal1)
(or (goal2)
(goal3))
(goal4))
(and (or (goal5)
(and (goal6)
(goal7)))
(goal8)
(goal9)))
(goal10)
(goal11)) Similar to miniKanren’s all and conde.
@greg to be clear, I’m perfectly fine with truthy values in Racket that is not #t, as long as it’s used as a boolean.
Say, foo :: number? -> (or/c number? #f). I’m perfectly OK with:
(if (foo 5) 1 2) What I have problem with is something like:
(+ (or (foo 5) 1) 2)
Totally agree the latter is not great.
(I wonder how often that arises from punning #f to mean “failure”, as opposed to using an option type or raising an exception.)
(I’m not saying punning #f to mean “failure” or “N/A” is always bad. It’s a thing many scheme/racket functions do. I’m just saying it seems to “set the stage” for things like the second example, or make it more defensible.)
I tried some other approaches: (with-handlers ([exn:fail:index-of?
(lambda (exn)
(index-of lst2 sym)
)])
(index-of lst1 sym)) Or (define (index-of/kk l s fallback succeed)
(cond
((index-of ls s) => succeed)
(else (fallback succeed))))
(index-of/kk lst1 sym
(λ (sk)
(index-of/kk lst2 sym
(λ (sk) #f)
(λ (v) (sk v))))
(λ (v) v)) Neither of them are ideal… The and and or approach is still a good option, especially when combining and and or to construct a path tree. The root cause is that the use case needs to consider failure paths, but usually everyday programming only considers a happy path and throws all the possible errors to the top-level.
what if foo is number->string?
Sometimes I use match where #f is one of the match patterns. This feels closer to a pattern match on an option type (where #f plays the role of None). It does nothing to help with #f needing to be a valid, non-“None” value. But at least the intent seems clearer… maybe? Don’t have a strong opinion about this.