0

I have defined a map procedure and a square procedure. The square procedure works fine, but the map one only works if it is defined twice.

Given the following code:

; Squares a number.
(define square (lambda (n)
    (* n n)
))


; Applies function f on all elements of l.
(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

This program crashes when executed:

> (map '(1 2 3) square)
; mcar: contract violation
;   expected: mpair?
;   given: #<procedure:square>
; [,bt for context]

Given the following code, however, the program works as expected. The only difference is that map is now defined twice.

; Squares a number.
(define square (lambda (n)
    (* n n)
))


; Applies function f on all elements of l.
(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

This version works fine:

> (map '(1 2 3) square)
{1 4 9}

What is causing this issue, and how should it be solved?

Jeroen
  • 15,257
  • 12
  • 59
  • 102

2 Answers2

4

I'm unable to reproduce your problem. Specifically, I run this program in DrRacket:

#lang r5rs

; Squares a number.
(define square (lambda (n)
    (* n n)
))


; Applies function f on all elements of l.
(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

And then, in the interactions window, I run

> (map '(3 4 5) square)

... and get the result:

(mcons 9 (mcons 16 (mcons 25 '())))

Can you give a little more information to help reproduce your problem? (Your mention of mcons makes it clear that you're running this code using racket, using the command-line, but I'm guessing there's something funny going on with the way you're loading and running your code.)

EDIT: okay, I'm now able to reproduce something like this, by just pasting these expressions one by one into the REPL. It's obviously been a long time since I used the top-level REPL. Regardless of the answer to your question, the higher-level answer is this: don't paste expressions into the REPL. In the words of Matthew Flatt (primary implementor of Racket), "The top level is hopeless." Using DrRacket is the easiest way to solve this problem.

EDIT 2: As I suspected, the TL; DR: 1) the top level is hopeless. 2) put all of your code in modules.

I summarized some of this confusion in a posting to the Racket-Users list. Specifically, the fundamental question: how can the right-hand-side of the binding not be in the scope of the binding itself?

Here's an excerpt from Matthew's answer:


That's the essence of the problem. Which things are in the scope of a top-level definition?

For example, is the reference to f in the scope of a binding of f in

(define (g x) (f x))
(define (f x) x)

?

How about in

(begin
  (define (g x) (f x))
  (define (f x) x))

?

Or in

(expand '(define (f x) x))
(define (g x) (f x))

or

(begin
 (expand '(define (f x) x))
 (define (g x) (f x)))

?

The rule in Racket is that a top-level define doesn't change the binding of an identifier until the define is evaluated. So, in

(define (map x) ... map ...)

the reference to map is expanded/compiled at a point where map refers to a module import, not a variable named map. By the time the definition is evaluated, it's too late to redirect the meaning of map as a reference to an import.

There are other choices, but I don't think there are going choices that end up being significantly better or more consistent. The top level is hopeless.

Modules behave significantly better, in part because the scope of a definition is clear: from the start of the module to the end.


At this point, you might plausibly ask how you are supposed to interact with Racket. There are a couple of good choices:

1) Use DrRacket. I can't recommend this highly enough. 2) Use the command-line, and "require" rather than "load". "load", not to put too fine a point on it, is a semi-broken holdover from older versions of the language.

So, for instance, I can put this code in a file called 'a.rkt':

#lang r5rs

; Squares a number.
(define square (lambda (n)
    (* n n)
))


; Applies function f on all elements of l.
(define map (lambda (l f)
    (cond
        ((null? l) '())
        (else (cons (f (car l)) (map (cdr l) f)))
    )
))

Then, I start up racket and 'require' this module, then use ',enter' to enter the module:

hardy:/tmp clements> racket
Welcome to Racket v6.11.0.6.
> (require "a.rkt")
> ,enter "a.rkt"
"a.rkt"> (map '(3 4 5) square)
(mcons 9 (mcons 16 (mcons 25 '())))
"a.rkt"> 

Let me repeat yet again that you sidestep all of these problems by simply using DrRacket.

So... I learned a bunch today! Thanks!

John Clements
  • 16,895
  • 3
  • 37
  • 52
  • I don't paste these lines into a REPL. :) I load the file into racket in interactive mode, and then write only the expression I want to run in the REPL. This is the easiest way to test code I found. – Jeroen Jan 23 '18 at 20:54
  • Okay, I added a bunch more text. Let me know if you have more questions, or if I didn't answer your question! – John Clements Jan 23 '18 at 22:17
  • That did answer my question, but I already settled with renaming the procedure. At least now I get why the error was super vague. Also, I don't use a load statement, Racket automatically imports the file from the command. Not sure what exact command it is I am running (I use an alias) and I can't check atm. – Jeroen Jan 23 '18 at 23:08
  • Does the second syntax `(define (map l f) ...)` change the behavior? Would using an explicit `letrec` help? – coredump Jan 24 '18 at 12:32
1

map is a standard Scheme function. When you define the function the first time, it's apparently trying to call the standard function, not your redefinition of it. Since the standard function takes its arguments in the opposite order (map function list), it gets an error. The second time you define it, it finds your function in the environment, so things work.

The best solution is to use a different name that doesn't conflict with a standard function.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Oh! That was indeed the issue. I didn't think of that just because the behaviour is so odd. – Jeroen Jan 23 '18 at 19:08
  • A R5RS library function can be overridden only if it is backwards compatible with the original. – Sylwester Jan 23 '18 at 19:55
  • something else is going on here. There's no reason that the use of 'map' within the definition wouldn't be inside the scope of the definition. – John Clements Jan 23 '18 at 20:15
  • @JohnClements Yeah, that confuses me, too. But it seems like the only explanation. – Barmar Jan 23 '18 at 20:19
  • For those interested, John Clements explained in his answer why it generates such an odd error. – Jeroen Jan 23 '18 at 23:09
  • 1
    @JeroenBollen Although my answer is basically right and came first, you should accept John's because it's more thorough. – Barmar Jan 24 '18 at 15:30