Jeroen
Jeroen

Reputation: 16825

Procedure works only if defined twice

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?

Upvotes: 0

Views: 138

Answers (2)

John Clements
John Clements

Reputation: 17203

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!

Upvotes: 4

Barmar
Barmar

Reputation: 781068

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.

Upvotes: 1

Related Questions