2

this is the original form:

(define (split-by l p k)
  (let loop ((low '())
             (high '())
             (l l))
    (cond ((null? l)
           (k low high))
          ((p (car l))
           (loop low (cons (car l) high) (cdr l)))
          (else
           (loop (cons (car l) low) high (cdr l))))))
 

and i'm trying to convert let, this is what I have tried:

(define (split-by l p k)
  (lambda (loop)     
    (cond ((null? l) (k low high))
          ((p (car l)) 
           (loop low (cons (car l) high) (cdr l)))
          (else
           (loop (cons (car l) low) high (cdr l))
           ((low '()) (high '()) (l l))))))

I don't know how to fix this so if anyone could help what I am doing wrong would be a great help.

Will Ness
  • 62,652
  • 8
  • 86
  • 167
Fatemeh Rahimi
  • 466
  • 5
  • 18

2 Answers2

3

If I understand correctly what you're doing, p is a predicate and you split the list l according to this, aggregating your two resulting lists with the aggregation function k; in pseudo-code:

(split-by l p k) => (k {x in l | !p(x)} {x in l | p(x)})

The problem when replacing your let is that the loop function is recursively defined. It is of the form:

(define (loop low high lst)
    (cond
        ((null? lst) <some value>)
        (<some predicate> (loop (cons (car lst) low) high (cdr lst)))
        (else (loop low (cons (car lst) high) (cdr lst)))))

You absolutely can use this directly in your function, defining the 'inner' recursive part, but this cannot be made using simple lambda without let: the function needs to refer to itself (since it is recursive), and you can only do that by assigning it a name. define will do that, let will let you do that, but no matter how you turn it, you need that self-reference. If you're clever and pass in a continuation:

(lambda (low high lst cont)
    (cond
        ((null? lst) (agg high lst))
        ((pred? (car lst)) (cont low (cons (car lst) high) (cdr lst) cont))
        (else (cont (cons (car lst) low) high (cdr lst) cont))))

You've removed that self-reference by making it explicit, but what do you pass as cont ? Well, if you assigned that via a let, you have a symbol refering to it:

(define (split-by2 lst pred? agg)
    (let ((f (lambda (low high lst cont)
                (cond
                    ((null? lst) (agg low high))
                    ((pred? (car lst)) (cont low (cons (car lst) high) (cdr lst) cont))
                    (else (cont (cons (car lst) low) high (cdr lst) cont))))))
        (f '() '() lst f)))

Or more concisely with a define, which does the exact same thing (without the need to pass in the continuation):

(define (split-by3 lst pred? agg)
    (define (f low high lst)
        (cond
            ((null? lst) (agg low high))
            ((pred? (car lst)) (f low (cons (car lst) high) (cdr lst)))
            (else (f (cons (car lst) low) high (cdr lst)))))
    (f '() '() lst))

All of them operate similarly:

(split-by '(1 2 3 4) (lambda (x) (> x 2)) list)
=> ((2 1) (4 3))   
(split-by2 '(1 2 3 4) (lambda (x) (> x 2)) list)
=> ((2 1) (4 3))   
(split-by3 '(1 2 3 4) (lambda (x) (> x 2)) list)
=> ((2 1) (4 3))

But you cannot get away with defining a symbol for your recursive function*.

As to why your example didn't work, it's working perfectly fine, except that it creates a function, taking as argument a function (which I called cont above) and applying your logic given that function loop. Since you then don't have any 'loop' to pass it (as you haven't bound it), it returns that function and proceeds to do nothing (in addition, in your returned lambda, low and high are not defined).

* This is not entirely true as you could do it using combinators on your lambda, but that would make it much more complex than it ought to:

(define Y
  (lambda (h)
    ((lambda (x) (x x))
     (lambda (g)
       (h (lambda args (apply (g g) args)))))))

(define (split-ycomb lst pred? agg)
    ((Y 
        (lambda(f)
            (lambda (low high l)
                (cond
                    ((null? l) (agg low high))
                    ((pred? (car l)) (f low (cons (car l) high) (cdr l)))
                    (else (f (cons (car l) low) high (cdr l)))))))
    '() '() lst))

Or for an even uglier purer version, with an inline combinator:

(define (split-ycomb2 lst pred? agg)
    (((lambda (h)
        ((lambda (x) (x x))
            (lambda (g)
                (h (lambda args (apply (g g) args)))))) 
        (lambda(f)
            (lambda (low high l)
                (cond
                    ((null? l) (agg low high))
                    ((pred? (car l)) (f low (cons (car l) high) (cdr l)))
                    (else (f (cons (car l) low) high (cdr l)))))))
    '() '() lst))

Which work as expected (thanks to the layers of lambdas):

(split-ycomb '(1 2 3 4) (lambda (x) (> x 2)) list)
=> ((2 1) (4 3))
(split-ycomb2 '(1 2 3 4) (lambda (x) (> x 2)) list)
=> ((2 1) (4 3))
Will Ness
  • 62,652
  • 8
  • 86
  • 167
val
  • 7,603
  • 26
  • 31
1

You could try writing

(define (split-by l p k)  
  (let ((loop 
          (lambda (low high l)
             (cond 
               ((null? l)
                  (k low high))
               ((p (car l))
                  (loop low (cons (car l) high) (cdr l)))
               (else
                  (loop (cons (car l) low) high (cdr l)))))))
    (loop '() '() l)))

but the trouble is that the lambda's body can't refer to the loop name yet, as it is being defined (you could just replace let with letrec, and then it'd work, but that's not what you're asking here).

The name loop being defined by let is not in scope inside the init expression for it. That is the meaning of let being non-recursive. Its recursive variant, letrec, does provide for the name being defined, to be in scope inside the init-expression (just that its value isn't allowed to be queried when the init-value is calculated).

There's a simple trick though (a kind of poor man's Y combinator), which emulates true self-reference through replication, which is achieved by self-application, as in

(define (split-by l p k)  
  (let ((foo 
          (lambda (loop low high l)
             (cond 
               ((null? l)
                  (k low high))
               ((p (car l))
                  (loop loop low (cons (car l) high) (cdr l)))
               (else
                  (loop loop (cons (car l) low) high (cdr l)))))))
    (foo foo '() '() l)))

and all's right again under the sun, i.e. the non-recursive let -- the loop name being referred to inside the lambda body, is just a lambda parameter now, thus in scope.

And since the let is plain, non-recursive, it is easy to re-write this with a simple lambda-application, as

(define (split-by l p k)  
  ((lambda (foo) (foo foo '() '() l))   ; (lambda (loop ...
   (lambda (loop low high l)            ;   is duplicated into the two foos
             (cond 
               ((null? l)
                  (k low high))
               ((p (car l))
                  (loop loop low (cons (car l) high) (cdr l)))
               (else
                  (loop loop (cons (car l) low) high (cdr l)))))))
Will Ness
  • 62,652
  • 8
  • 86
  • 167