Differences between two similar definitions

2019-08-11 04:55发布

问题:

Is there any difference between

(define make-point cons)

and

(define (make-point x y)
  (cons x y))

?

Is one more efficient than the other, or are they totally equivalent?

回答1:

There are a few different issues here.

As Oscar Lopez points out, one is an indirection, and one is a wrapper. Christophe De Troyer did some timing and noted that without optimization, the indirection can take twice as much time as the indirection. That's because the alias makes the value of the two variables be the same function. When the system evaluates (cons …) and (make-point …) it evaluates the variables cons and make-point and gets the same function back. In the indirection version, make-point and cons are not the same function. make-point is a new function that makes another call to cons. That's two function calls instead of one. So speed can be an issue, but a good optimizing compiler might be able to make the difference negligible.

However, there's a very important difference if you have the ability to change the value of either of these variables later. When you evaluate (define make-point kons), you evaluate the variable kons once and set the value of make-point to that one value that you get at that evaluation time. When you evaluate (define (make-point x y) (kons x y)), you're setting the value of make-point to a new function. Each time that function is called, the variable kons is evaluated, so any change to the variable kons is reflected. Let's look at an example:

(define (kons x y)
  (cons x y))

(display (kons 1 2))
;=> (1 . 2)

Now, let's write an indirection and an alias:

(define (kons-indirection x y)
  (kons x y))

(define kons-alias kons)

These produce the same output now:

(display (kons-indirection 1 2))
;=> (1 . 2)

(display (kons-alias 1 2))
;=> (1 . 2)

Now let's redefine the kons function:

(set! kons (lambda (x y) (cons y x))) ; "backwards" cons

The function that was a wrapper around kons, that is, the indirection, sees the new value of kons, but the alias does not:

(display (kons-indirection 1 2)) 
;=> (2 . 1)  ; NEW value of kons

(display (kons-alias 1 2))
;=> (1 . 2)  ; OLD value of kons


回答2:

Semantically they're equivalent: make-point will cons two elements. But the first one is creating an alias of the cons function, whereas the second one is defining a new function that simply calls cons, hence it'll be slightly slower, but the extra overhead will be negligible, even inexistent if the compiler is good.



回答3:

For cons, there is no difference between your two versions.

For variadic procedures like +, the difference between + and (lambda (x y) (+ x y)) is that the latter constrains the procedure to being called with two arguments only.



回答4:

Out of curiosity I did a quick and dirty experiment. It seems to be the case that just aliasing cons is almost twice as fast than wrapping it in a new function.

(define mk-point cons)

(define (make-point x y)
  (cons x y))

(let ((start (current-inexact-milliseconds)))
(let loop ((n 100000000))
      (mk-point 10 10)
      (if (> n 0)
          (loop (- n 1))
          (- (current-inexact-milliseconds) start))))
(let ((start (current-inexact-milliseconds)))
(let loop ((n 100000000))
      (make-point 10 10)
      (if (> n 0)
          (loop (- n 1))
          (- (current-inexact-milliseconds) start))))


;;; Result    
4141.373046875
6241.93212890625
> 

Ran in DrRacket 5.3.6 on Xubuntu.



标签: scheme