Search code examples
lambdaschemelispracketanonymous-recursion

Generating powerset in one function, no explicit recursion, and using only simplest primitives in Racket


Note: this is a bonus for homework, but I have spent way too long on trying things to no avail. Help is much appreciated, but not necessary I suppose.

Premise: generate a powerset for a list of numbers, but without using any helpers, explicit recursion, looping, or functions/constants other than cons, first, rest, empty?, empty, else, lambda, and cond, while using only one define on the language level Intermediate Student with Lambda. The order of the powerset does not matter.

What I've tried so far: I have discovered the Y-combinator and anonymous recursion thanks to this post (author has the same end goal but we have different approaches, so the information in his post does not solve my problem), and the powerset code in this answer, and with that I have written the following:

(define (powerset aL)
  (((lambda (X)
      ((lambda (proc)
         (proc proc))
       (lambda (proc)
         (X (lambda (arg)
              ((proc proc) arg))))))
    (lambda (subset)
      (lambda (lst)
        (cond
          [(empty? lst) (list empty)]
          [else (combine (first aL) (powerset (rest aL)))])))) aL)

(define (combine a r)
  (cond
    [(empty? r)  empty]
    [else (cons (cons a (first r)) (cons (first r) (combine a (rest r))))]))

I'm testing this code by running:

(check-expect (powerset '(1 2 3)) 
(list '(1 2 3) '(2 3) '(1 3) '(3) '(1 2) '(2) '(1) '()))

This code runs and produces a correct result, but, as you can see, I still rely on an external helper function, combine, and I have no idea how to convert that into a lambda since to my knowledge, the Y-combinator only works with one parameter and combine needs 2. Perhaps my logic or approach to this problem is flawed. I have limited experience with lambda so I could be missing knowledge as well.

What I need help with: any suggestions as to next steps, helping me intergrate combine into powerset, providing hints/clues to correct logic/approach, or a solution would be much appreciated.

Thanks in advance!


Solution

  • I find the trick below easier to understand than using Y. I think it's sort of related to U (which I also find easier to understand than Y).

    It's possible that this isn't enough to meet the requirement of 'not being explicitly recursive', although I think it is.

    If you have some function which 'wants' to use itself freely so it can recurse, such as:

    (define powerset
      (λ (set)
        (cond [(empty? set)
               (list empty)]
              [else
               (combine (first set)
                        (powerset (rest set)))])))
    

    Then you can turn that into a function which takes an additional argument, which it calls:

    (define powerset/c
      (λ (ps/c set)
        (cond [(empty? set)
               (list empty)]
              [else
               (combine (first set)
                        (ps/c ps/c (rest set)))])))
    

    The /c names are because when I discovered this trick I was thinking about the argument as a continuation, but I think that's because I didn't know what continuations were really.

    And now (with a definition for combine), (powerset/c powerset/c '(x y z)) will compute the power set of (x y z), and there is no explicit recursion.

    Well, that's ugly but this is easy to fix using

    (define powerset
      (λ (set)
        ((λ (powerset/c)
           (powerset/c powerset/c set))
         (λ (ps/c set)
           (cond [(empty? set)
                  (list empty)]
                 [else
                  (combine (first set)
                           (ps/c ps/c (rest set)))])))))
    

    Then the trick is to write combine this way as well, and then use it locally, with

    (define powerset
      (λ (set)
        ((λ (combine)
           ((λ (powerset/c)
              (powerset/c powerset/c set))
            (λ (ps/c set)
              (cond [(empty? set)
                     (list empty)]
                    [else
                     (combine (first set)
                              (ps/c ps/c (rest set)))]))))
         <combine defn here>)))