In my Scheme interpreter written in JavaScript, I want to modify the some
function that that checks if any of the elements in the list return true
. With implementation that allow to pass multiple lists.
I have a function in JavaScript
some: doc('some', function some(fn, list) {
typecheck('some', fn, 'function');
typecheck('some', list, ['pair', 'nil']);
if (is_null(list)) {
return false;
} else {
return unpromise(fn(list.car), (value) => {
return value || some(fn, list.cdr);
});
}
}, `(some fn list)
Higher-order function that calls fn on each element of the list.
It stops and returns true when fn returns true for a value.
If none of the values give true, some will return false.
Analogous to Python any(map(fn, list)).`),
That I want to replace with Scheme code.
I’ve implemented the code like this:
(define (%some fn lists)
"(%some fn lists)
version of some without typechecking."
(if (or (null? lists) (%some null? lists))
false
(if (apply fn (map car lists))
true
(%some fn (map cdr lists)))))
;; -----------------------------------------------------------------------------
(define (some fn . lists)
"(some fn . lists)
Higher-order function that calls fn on consecutive elements of the list of lists.
It stops and returns true when fn returns true. If none of the values give true,
some will return false. Analogous to Python any(map(fn, list))."
(typecheck "some" fn "function")
(typecheck-args (vector "pair" "nil") "some" lists)
(%some fn lists))
But the problem is that I can’t use %some
inside %some
to check if any of the lists is empty. It throws stack overflow.
I was trying to create a helper macro:
(define-macro (%any lists)
`(or ,@lists))
And use it like this:
(%any (map null? lists))
But this is totally wrong approach.
I was searching my notes and found this code that I’ve written long ago:
(define-macro (macron m)
(let ((x (gensym)))
`(lambda ,x
(eval `(,',m ,@,x) (interaction-environment)))))
(apply (macron or) (map null? '(() '(x) '())))
#t
Which maps macro to a function, but there should be a simple way.
So, how would you implement some
/any
procedure in Scheme, when you need the same procedure to test if any of the lists is not null?
7
You can avoid creating a helper function by detecting and special-casing the case of your some
function taking a single list argument. One way using case-lambda
:
;;; Tested with LIPS
(define some
(case-lambda
((f lst) ; single argument fast path
; lips gives an error when I try to call the variable list
(let loop ((lst lst))
(cond
((null? lst) #f)
((f (car lst)) => identity)
(else (loop (cdr lst))))))
((f . lists) ; more than one argument
(let loop ((lists lists))
(cond
((null? lists) #f)
((some null? lists) #f)
((apply f (map car lists)) => identity)
(else (loop (map cdr lists))))))))
There are tricks you can do like extracting the cars and cdrs of the lists in a single pass that also looks for empty lists, but I’m not convinced the number of arguments is ever going to be big enough for the extra complexity to be worthwhile. The SRFI-1 reference implementation version (Called any
) does this with an internal %cars+cdrs
function, for example.
1
I solved the issue by creating helper function that check if any of the list elements is null.
(define (%any-null? lst)
"(%any-null? lst)
Checks if any of elemets in the list is null."
(if (null? lst)
false
(if (null? (car lst))
true
(%any-null? (cdr lst)))))
(define (%some fn lists)
"(%some fn lists)
version of some without typechecking."
(if (or (null? lists) (%any-null? lists))
false
(if (apply fn (map car lists))
true
(%some fn (map cdr lists)))))