2

I have just picked up John Hughes' essay Why Functional Programming Matters, and i'm trying to follow along using Scheme.

The first higher order function he defines is reduce, which i've defined in Scheme as follows:

(define u-reduce
  (lambda (ff init lst)
    (if (null? lst)
      init
      (ff (car lst) (u-reduce ff init (cdr lst))))))

I can re-create some of the applications of reduce in the essay using this function, however things break apart in the move from reduce to map.

The motivating example is doubleall = reduce doubleandcons nil, where doubleandcons num list = cons (2*num) list.

I am not sure how to translate this into scheme. I can see that the target result can be made with:

(define doubleandcons
  (lambda (lst)
    (if (null? lst)
      '()
      (cons (* 2 (car lst)) (doubleandcons (cdr lst))))))

(doubleandcons '(1 2 3))
> (2 4 6)

However can not see what to pass to reduce to get it to double each element of a list - perhaps because i keep jumping to a version of map (see u-map below) as the solution to the problem.

I can see that init = '(), but not what function to pass in to the ff place to make u-reduce behave like u-map.

(define u-map
  (lambda (ff lst)
    (if (null? lst)
      '()
      (cons (ff (car lst)) (u-map ff (cdr lst))))))

(u-map (lambda (x) (* 2 x)) '(1 2 3))
> (2 4 6)

Is this possible? Or perhaps i'm missing the point?

ricardo
  • 8,195
  • 7
  • 47
  • 69

3 Answers3

3

Common Lisp reduce is a general fold. In Scheme you have fold-right that does elements in order. Your u-reduce works just like a right fold with it's arguments in the same order. Thus the following should work in R6RS/R7RS:

(define (my-map fun lst)
  (fold-right (lambda (x a) (cons (fun x) a)) '() lst))

As you can see I'm using the passed function in an anonymous function to fold-right to do the cons as well.

In other versions of Scheme and Racket you get the same behavior by using SRFI-1: List library, Many implementation has support for it so look in it's documentation how you import/require it.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
2

You should define your reduce as explicitly lazy, since the article is all about the importance and utility of laziness.

The usual reduce is a (right) fold, i.e. a catamorphism (cf. Haskell's foldr), but let's define a paramorphism for generality:

(define (para f z lst)
   (if (null? lst)
      z
      (f lst (car lst)      ;; usually, f (cdr lst) (car lst) ...
         (lambda () (para f z (cdr lst))))))

(see also this). We use it like this:

;; doubleall = reduce doubleandcons nil
;;   where  doubleandcons num rest = cons (2*num) rest

(define (doubleall lst) 
   (para doubleandcons '() lst))

(define (doubleandcons lst num rest)   ; ignore input lst
   (cons (* 2 num) (rest)))            ; force the lazy rest

(define (member elt lst)
   (para (lambda (xs x r) 
            (if (equal? x elt) xs (r)))  ; conditionally force, or
         '() lst))                       ;  ignore the rest

(define (mymap f lst)
   (para 
      (lambda (xs x r) 
         (cons (f x) (r)))    ; cons the result of calling f w/ x,
      '() lst))               ;   onto the rest

(define (all pred lst)
   (para
      (lambda (xs x r) 
         (and (pred x) (r)))  ; possibly short-circuit
      #t lst))

You cannot short-circuit with your version because it evaluates the rest before passing it as an argument to the combining function (ff).

Community
  • 1
  • 1
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • your function `para` looks a little like _CPS_, but the lambda is that's passed isn't a function of anything - why is this? I plan on following your links, but an explanation of how this makes the function _explicitly lazy_ would be helpful. – ricardo Feb 20 '14 at 01:26
  • simply by encasing the expression inside a *lambda*, creates a function that *will* perform its action, *when* called. This is known as *delaying* the execution (of a piece of code). Under lazy evaluation model this happens automatically - *everything* gets delayed. Here we do it explicitly, for a certain part of a computation. So no, that's not CPS because the lambda has no arguments. Such lambdas are known as *"thunks"* BTW. – Will Ness Feb 20 '14 at 06:51
  • I don't understand the need to pass `lst` in the recursive step. I can re-write all of your example functions and re-write `para` without it in the recursive step `(f (car lst) (lambda () ...))` without it, and nothing seems to break. – ricardo Feb 20 '14 at 21:27
  • exactly right! (almost) :) -- `lst` is a part of "protocol" of paramorphism; without it, it'd be a catamorphism, i.e. a traditional fold; except that *`member` needs `lst`*, it can't be defined so easily with catamorphism (it can, but just not so easily and straightforwardly). that's why I said "for generality". :) but mostly it's unused. :) – Will Ness Feb 20 '14 at 23:14
  • ... and it's not "in the recursive step", on the contrary, it's in the re-combination step. :) – Will Ness Feb 20 '14 at 23:16
  • are there practical cases where it's an important distinction? i haven't read widely on the subject just yet, but it seems mostly of theoretical interest (to do with the links to category theory). Is a _paramorphism_ a more general class of `fold` than a _catamorphism_? – ricardo Feb 21 '14 at 00:33
  • 1
    between cata and para, you mean? but I am showing the practical difference there, in the impl of `member`. :) With catamorphism, we'd have to wriggle it into `(define (member elt lst) (cata (lambda (xs r) (if (equal? (car xs) elt) xs (r))) '() (maplist (lambda (x) x) lst)))`, with [`maplist` as in Common Lisp](http://www.lispworks.com/documentation/HyperSpec/Body/f_mapc_.htm), `(define (maplist f lst) (if (null? lst) '() (cons (f lst) (maplist f (cdr lst)))))`. – Will Ness Feb 21 '14 at 10:26
  • User "pigworker" wrote in an answer here on SO about general technique of simulating para with cata by recreating the (copy of) the original list, which is even more cumbersome. Para does give more info to the combining function, so does it mean it's more "general"? Perhaps. It's just two different tools. – Will Ness Feb 21 '14 at 10:29
  • my eyes are just starting to open -- can you recommend introductory materials? Also, most of what i can find uses haskell: is haskell the lingua franca of this community? – ricardo Feb 22 '14 at 02:35
  • Haskell has much more visual syntax, much less noise, to me, though YMMV. I was interested in Prolog and Scheme first. Haskell is pretty much unavoidable. :) Before you get into their "monads" stuff, be sure to read http://squing.blogspot.com/2008/01/unmonad-tutorial-io-in-haskell-for-non.html. It talks about IO but is applicable for any monad. Practically, "monadic" code is just programs in some DSL, each type of monad defining their own DSL with its special functions - in addition to "how to perform one command after another". e.g. with or w/out exceptions, w/ or w/out error messages, etc. – Will Ness Feb 22 '14 at 07:47
  • usually recommended are "LYAH haskell" (search) and "RWH haskell" and haskell.org. You're already reading WFPM. :) But, there's no such good documentation as there's for Racket. -- Also, there's Friedman and Wise's TR44 and TR19, and Steel and Sussman's "lambda" papers - the foundational stuff, IMHO. (predates Haskell per se). Haskell is more or less just lazy Scheme with syntax and types (and type inference). It's all related. :) – Will Ness Feb 22 '14 at 08:10
1
(define u-map
  (lambda (ff lst)
    (reduce (lambda (x y) (cons (ff x) y)) '() lst)))

Folding cons across a list gives you a copy of that list.

WorBlux
  • 1,423
  • 11
  • 20
  • +1 / accepted: `(u-reduce (lambda (x y) (cons ((lambda (x) (* 2 x)) x) y)) '() '(1 2 3))` was exactly the bridge i needed. – ricardo Feb 20 '14 at 01:24