mark.warren
2019-11-1 09:39:07

Hi all, I am trying to get my head around combining functions to create a new function, my question is, is there an easier way than the following to create a new function from two others?


mark.warren
2019-11-1 09:39:17
#lang racket

(define (plus-two)
  (λ (x) (+ x 2)))

(define (plus-three)
  (λ (x) (+ x 3)))

(define (combine l1 l2)
  (λ (x) ((l2) ((l1) x))))

((combine plus-two plus-three) 2)

mark.warren
2019-11-1 09:40:30

The final line uses the combine function to correctly give the result 7


notjack
2019-11-1 09:43:49

@mark.warren You don’t have to make plus-two and plus-three return functions, they can just be functions: (define (plus-two x) (+ x 2)) (define (plus-three x) (+ x 3)) (define ((combine l1 l2) x) (l2 (l1 x))) ((combine plus-two plus-three) 2) ;; results in 7


mark.warren
2019-11-1 10:10:05

@notjack Ah thanks, that’s probably easier.


badkins
2019-11-1 16:52:34

I proposed a simple programming assignment for fun in another slack which was to implement zip I initially came up with: (define (zipn . args) (let loop ([ lists args ][ result '() ]) (cond [ (ormap empty? lists) (reverse result) ] [ else (loop (map rest lists) (cons (map first lists) result)) ]))) Then someone posted a Clojure example: (defn zipn [& lists] (apply map vector lists)) which showed me I was missing a fundamental understanding :slightly_smiling_face: After a little digging, I came up with: (define (zipn . args) (apply map list args)) However it will fail if the lists are not all of equal length. Also, I’m not exactly sure how apply is being used here. I’ve always used it as (apply procedure list), so I was surprised to see both map and list there.


soegaard2
2019-11-1 16:57:52

Regarding the apply: the expression (map list '((a b) (1 2)) doesn’t work, but (map list '(a b) '(1 2) does. So (apply map list '((a b) (1 2))) turns into (map list '(a b) '(1 2)).


soegaard2
2019-11-1 16:59:27

(and (map list '(a b) '(1 2)) turns into (list (list 'a '1) (list 'b '2)) )


badkins
2019-11-1 17:05:26

Ok, so I guess I need a special version of map to not blow up when the lists are of unequal length.


badkins
2019-11-1 17:06:14

Thanks @soegaard2 - I have a better intuitive grasp now. I was overcomplicating apply in my head.


rokitna
2019-11-1 17:14:18

The first argument to apply (in this case map) is the function to be applied, and the last argument (in this case args) is a list of arguments to pass to it. If there are more arguments in the middle (in this case just one, list), then those are individual arguments that will be added to the beginning of the argument list (args) before it’s passed in.

In short, (apply map list args) is equivalent to (apply map (cons list args)).


badkins
2019-11-1 17:16:04

I think I prefer Clojure’s map which stops when any of the argument lists is exhausted.


rokitna
2019-11-1 17:16:45

Common Lisp map does that too


notjack
2019-11-1 17:17:52

using map for both single-sequence transforms and multiple-sequence zip-style transforms is confusing, IMO


rokitna
2019-11-1 17:21:17

I dunno if this is important in Racket these days, but at some point I started to avoid using apply for this, because I was using a language (either Racket or JS) where it seemed likr the argument pointers were put on the stack individually, resulting in a stack overflow if the list was long enough. As a result of that, I have a better feeling about your original zipn, which doesn’t apply a dynamically sized list.


badkins
2019-11-1 17:22:54

I hope it wasn’t Racket @rokitna !


rokitna
2019-11-1 17:23:29

My memory about this is very hazy :) don’t mean to scaremonger


badkins
2019-11-1 17:32:00

Two questions: 1) Does Racket have a map of another name that ends when the shortest arg list ends?, and if not, 2) What would be a good name for such a function?


notjack
2019-11-1 17:33:07

I think making a (zip f lst ...) function with an optional #:allow-uneven-lists? keyword argument would be a decent approach


badkins
2019-11-1 17:36:19

Interesting. Then the traditional zip would be (curry zip list)


rokitna
2019-11-1 17:40:32

(I bet it was JS. I remember stack size limits first really annoyed me when I moved to JS and kept using a tail-calling style when JS didn’t have TCE.)


soegaard2
2019-11-1 17:43:55

I wouldn’t be worried about the stack in Racket.


soegaard2
2019-11-1 17:45:41

However - in Scheme apply is required to make allocate a new list (of the arguments). I think Racket does the same: (let () (define xs (list '(a b c))) (eq? (apply values xs) xs))


soegaard2
2019-11-1 17:48:52

That is, using apply to implement zip is a non-optimal, since it allocates an unnecessary temporary list.


samdphillips
2019-11-1 20:23:05

Tangent: I don’t think there is such a requirement in RNRS, it just may be easier to implement that way. I seem to recall in the Dybvig paper about implementing call-with-values he talks about optimizing varargs to use primitives to directly access the stack/call frame instead of building a list (eg. when the varargs are actually a kind of optional variable from the caller.)


soegaard2
2019-11-1 21:24:26

Hmm. I think you are right.

R5RS says about apply: procedure: (apply proc arg1 ... args) Proc must be a procedure and args must be a list. Calls proc with the elements of the list (append (list arg1 ...) args) as the actual arguments. And it says this about append: The resulting list is always newly allocated, except that it shares structure with the last list argument.


soegaard2
2019-11-1 21:24:49

Does that mean that (eq? (apply values xs) xs)) is #t in a Scheme?


samdphillips
2019-11-1 21:40:17

My guess is that it is undefined behavior.


samdphillips
2019-11-1 21:40:21

:stuck_out_tongue:


samdphillips
2019-11-1 22:07:20

(apply values xs) would return multiple values so is probably a bad example?

Maybe (apply (lambda ys ys) xs) is more apropo.