user4285147
user4285147

Reputation:

Basic if-s in Racket

I am attempting to write a basic program that will compare a given parameter to a certain character. I realized that the operator = compares only two numbers, so I tried to use character-comparing operators.

At first I did:

(define test
   (lambda testChar
     (if (char=? testChar 'a) 3 2)
   )
)

But I received an error saying I'm not using the proper types of expressions. The message is:

char=?: contract violation expected: char? given: '("a") argument position: 1st other arguments...:

So I tried to use a different operator:

(define test
   (lambda testChar
     (if (equal? testChar 'a) 3 2)
   )
)

This time I was able to run it. But when running it on (test 'a) I received 2, which means the condition in the if did not apply. Same happens if I use eqv? instead of equal?.

I believe I am missing something basic here, either with characters in Racket, or with the if condition. What is it?

Edit: I also tried the eq operator, but it still returns false.

Upvotes: 0

Views: 1548

Answers (1)

Kieron Hardy
Kieron Hardy

Reputation: 751

There are several issues with the OP's question and (self-)answer.

In an attempt to make concrete, to anyone stumbling upon this question, the comments made by others attempting to point out to the OP those problems ...

Comparing Integers

As in many other languages, in Racket one way to denote an integer-type number literal is to use a string of digits.

To use an if form to compare one integer to another, one could use the = operator:

#lang racket

(define num-val 65) ;; code-point for upper case letter 'A'

num-val

(define test-num
  (lambda (testNumber)
   (if (= testNumber 65)
       "test-num using = gives true"
       "test-num using = gives false")))

(format "test against the defined num-val: ~a" (test-num num-val))
(format "test against number value 66: ~a" (test-num 66))

which gives the following, when run in DrRacket:

65
"test against the defined num-val: test-num using = gives true"
"test against number value 66: test-num using = gives false"

Comparing Characters

In Racket, one way to denote character-type constant literals is with the sequence #\, e.g. #\A, #\Z, #\a, #\A, #\0, #\9, etc.

To use an if form to compare one character to another, one could use the char=? operator, e.g.:

#lang racket

(define num-val 65) ;; code-point for upper case letter 'A'
(define char-val (integer->char num-val))

char-val
(char->integer char-val)

(define test-char
  (lambda (testCharacter)
   (if (char=? testCharacter #\A)
       "test-char using char=? gives true"
       "test-char using char=? gives false")))

(format "test against the defined char-val: ~a" (test-char char-val))
(format "test against upper case letter 'A': ~a" (test-char #\A))
(format "test against lower case letter 'a': ~a" (test-char #\a))

which gives (again in DrRacket):

#\A
65
"test against the defined char-val: test-char using char=? gives true"
"test against upper case letter 'A': test-char using char=? gives true"
"test against lower case letter 'a': test-char using char=? gives false"

Comparing Symbols

In Racket, a symbol is a separate thing, and one way to denote a symbol is with a single leading apostrophe, e.g. 'A, 'Apple, 'bbb, 'xyz, etc..

One cannot compare characters and symbols (without some sort of conversion):

(test-char 'A) ;; BAD - the symbol 'A is not a char: gives contract violation error:
#|
char=?: contract violation
  expected: char?
  given: 'A
  argument position: 1st
  other arguments...:
|#

Comparing Character Strings

As in other languages, Racket string-type constant literals are delimited with double quotes, e.g. "Apple", "Apples, "1", "999", etc..

As with symbols, strings cannot be compared with characters:

(test-char "A") ;; BAD - the string "A" is not a char: gives contract violation error:
#|
char=?: contract violation
  expected: char?
  given: "A"
  argument position: 1st
  other arguments...:
|#

Comparing using eq? and equal?

Other comparison operators exist in Racket, e.g. eq?, eqv?, and equal?.

But be careful when comparing characters (or anything else for that matter) using eq? and equal?. One normally wants a comparison of the objects' values (i.e. using equal?), and not a comparison of the objects' identities (i.e. using eq?). (An object's identity value can be visualized as the address where that object is stored in memory whereas an value is the thing that particular object represents.)

equal? characters are sometimes also eq?

In the case of the character-type, Racket documentation states that for the first 256 characters, one should expect eq? and equal? to return the same value for characters returned by integer->char. This implies that integer->char is expected to use some sort of cache for the first 256 characters.

#lang racket

(define test-char
  (lambda (testChar)
   (if (char=? testChar #\A)
       "test-char using char=? gives true"
       "test-char using char=? gives false")))

(define test-char-eq
  (lambda (testChar)
   ; Warning: eq? compares object identity, compare with test-char-equal
   (if (eq? testChar (integer->char 65))
       "test-char-eq using eq? gives true"
       "test-char-eq using eq? gives false")))

(define test-char-equal
  (lambda (testChar)
   ; Warning: equal? compares object values, compare with test-char-eq
   (if (equal? testChar (integer->char 65))
       "test-char-equal using equal? gives true"
       "test-char-equal using equal? gives false")))

(format "test an ASCII object (code-point 65) identity: ~a" (test-char-eq (integer->char 65)))
(format "test an ASCII object (code-point 65) value: ~a" (test-char-equal (integer->char 65)))
(format "test an ASCII object (code-point 66) identity ~a" (test-char-eq (integer->char 66)))
(format "test an ASCII object (code-point 66) value: ~a" (test-char-equal (integer->char 66)))

which gives:

"test an ASCII object (code-point 65) identity: test-char-eq using eq? gives true"
"test an ASCII object (code-point 65) value: test-char-equal using equal? gives true"
"test an ASCII object (code-point 66) identity test-char-eq using eq? gives false"
"test an ASCII object (code-point 66) value: test-char-equal using equal? gives false"

Denoting Unicode characters

Another way to denote integer-type constant literals in Racket, perhaps especially useful for e.g. non-ASCII character constant literals, is with the sequence #x, e.g. #x221e, etc.. Character-type constant literals can also be specified in Racket using a sequence like #\u, e.g. #\u221e, etc..

#lang racket

(define num-val-non-ASCII #x221e) ;; code-point for the infinity symbol
(define char-val-non-ASCII (integer->char num-val-non-ASCII))

num-val-non-ASCII
char-val-non-ASCII

(define test-char-eq
  (lambda (testChar)
   (if (eq? testChar (integer->char #x221e))
       "test-char-eq using eq? gives true"
       "test-char-eq using eq? gives false")))

(define test-char-equal
  (lambda (testChar)
   (if (equal? testChar (integer->char #x221e))
       "test-char-equal using equal? gives true"
       "test-char-equal using equal? gives false")))

(format "test against the non-ASCII character identity: ~a" (test-char-eq char-val-non-ASCII))
(format "test against the non-ASCII character value: ~a" (test-char-equal char-val-non-ASCII))

#\u221e
(char->integer #\u221e)

(format "test a non-ASCII character constant identity: ~a" (test-char-eq #\u221e))
(format "test a non-ASCII character constant value: ~a" (test-char-equal #\u221e))

This gives:

8734
#\∞
"test against the non-ASCII character identity: test-char-eq using eq? gives false"
"test against the non-ASCII character value: test-char-equal using equal? gives true"
#\∞
8734
"test a non-ASCII character constant identity: test-char-eq using eq? gives true"
"test a non-ASCII character constant value: test-char-equal using equal? gives true"

Note: - 8734 decimal is 221E hexadecimal - eq? and equal? return different values for characters returned by integer->char for this code-point - both eq? and equal? return true when comparing the character object resulting from the #\u notation (at least with the current implementation of Racket on Windows).

Extended Unicode Characters

More characters from the Unicode character set can also be used:

#lang racket

(define unicode-num-val #x1f47a) ;; code-point for the "Japanese Goblin" symbol
(define unicode-char-var (integer->char unicode-num-val))

unicode-num-val
unicode-char-var

(define test-char-eq
  (lambda (testChar)
   (if (eq? testChar (integer->char unicode-num-val))
       "test-char-eq using eq? gives true"
       "test-char-eq using eq? gives false")))

(define test-char-equal
  (lambda (testChar)
   (if (equal? testChar (integer->char unicode-num-val))
       "test-char-equal using equal? gives true"
       "test-char-equal using equal? gives false")))

(format "test an unicode object identity: ~a" (test-char-eq (integer->char unicode-num-val)))
(format "test an unicode object value: ~a" (test-char-equal (integer->char unicode-num-val)))

This gives:

128122
#\👺
"test an unicode object identity: test-char-eq using eq? gives false"
"test an unicode object value: test-char-equal using equal? gives true"

Note: - 128122 decimal is 1F47A hexadecimal - eq? and equal? and give different values for this Unicode character (at least the current implementation of Racket on Windows)

equal? strings are not always eq?

Recall that in Racket string-type constant literals are delimited by double quotes, e.g. "Apple", "Apples", etc., but string-type constant literals can also be created with the string form, e.g. (string #\A #\p #\p #\l #\e), (string #\A #\p #\p #\l #\e, #\s), etc..

#lang racket

(define string-val "Apple")

string-val

(define test-str-eq
  (lambda (testString)
   ; Warning: eq? compares object identity (contrast with test-str-equal)
   (if (eq? testString "Apple")
       "test-str-eq using eq? gives true"
       "test-str-eq using eq? gives false")))

(define test-str-equal
  ; Warning: equal? gives value comparison (contrast with test-str-eq)
  (lambda (testString)
   (if (equal? testString "Apple")
       "test-str-equal using equal? gives true"
       "test-str-equal using equal? gives false")))

(format "test against the defined string-val object identity: ~a" (test-str-eq string-val))
(format "test against the defined string-val object value: ~a" (test-str-equal string-val))

(format "test against the constant string \"Apple\" object identity: ~a" (test-str-eq "Apple"))
(format "test against the constant string \"Apple\" object value: ~a" (test-str-equal "Apple"))

(format "test against the constant string \"Apples\" object identity: ~a" (test-str-eq "Apples"))
(format "test against the constant string \"Apples\" object value: ~a" (test-str-equal "Apples"))

(define created-string-val (string #\A #\p #\p #\l #\e))

created-string-val

(format "test against the created string object identity: ~a" (test-str-eq created-string-val))
(format "test against the created string object value: ~a" (test-str-equal created-string-val))

This gives:

"Apple"
"test against the defined string-val object identity: test-str-eq using eq? gives true"
"test against the defined string-val object value: test-str-equal using equal? gives true"
"test against the constant string \"Apple\" object identity: test-str-eq using eq? gives true"
"test against the constant string \"Apple\" object value: test-str-equal using equal? gives true"
"test against the constant string \"Apples\" object identity: test-str-eq using eq? gives false"
"test against the constant string \"Apples\" object value: test-str-equal using equal? gives false"
"Apple"
"test against the created string object identity: test-str-eq using eq? gives false"
"test against the created string object value: test-str-equal using equal? gives true"

Note that equaL? strings may not always be eq?.

Differences in the result of eq?, when comparing character-type objects and string-type objects that are created differently in code, is likely an artifact of the current implementation of Racket. Mistaken use of eq? might lead to bugs that could be especially difficult to find.

Use equal? vs eq?

Usually one would want to compare against an object's value (i.e. using equal?), rather than compare against the object's identity (i.e. using eq?), even though they sometimes give the same result.

Comparing objects of different types

Racket is untyped and eq? and equal? accept different types of objects and comparisons using objects of different types will always result with false:

#lang racket

(define test-str-eq
  (lambda (testString)
   ; Warning: eq? compares object identity (contrast with test-str-equal)
   (if (eq? testString "Apple")
       "test-str-eq using eq? gives true"
       "test-str-eq using eq? gives false")))

(define test-str-equal
  ; Warning: equal? gives value comparison (contrast with test-str-eq)
  (lambda (testString)
   (if (equal? testString "Apple")
       "test-str-equal using equal? gives true"
       "test-str-equal using equal? gives false")))

(test-str-eq 'Apple) ;; BAD - symbol 'Apple is not a string: returns "false" 
(test-str-equal 'Apple) ;; BAD - symbol 'Apple is not a string: returns "false" 

Which gives:

"test-str-eq using eq? gives false"
"test-str-equal using equal? gives false"

See Typed-Racket for a Racket variant that adds type-checking.

Comparing the characters of a character string

The individual characters of a string can be accessed with string-ref and those characters can be compared with char=? as above:

#lang racket

(define string-val "Apple")

string-val
(string-ref string-val 0)
(string-ref string-val 1)

(define test-char
  (lambda (testCharacter)
   (if (char=? testCharacter #\A)
       "test-char using char=? gives true"
       "test-char using char=? gives false")))

(format "test against first character of string \"Apple\": ~a" (test-char (string-ref string-val 0)))
(format "test against second character of string \"Apple\": ~a" (test-char (string-ref string-val 1)))

Which gives:

"Apple"
#\A
#\p
"test against first character of string \"Apple\": test-char using char=? gives true"
"test against second character of string \"Apple\": test-char using char=? gives false"

Note that the first character of a string is obtained with a string-ref of 0, the second character of a string is obtained with a string-ref of 1, etc..

Comparing Byte Strings

Racket also has a byte-string type (a series of bytes) different from the regular (character-) string type. Byte-string constant literals are string constant literals prefixed with a hash (a.k.a. pound) symbol.

Unsurprisingly, comparing a character-string value with a byte-string value will return false:

#lang racket

(define string-val "Apple")
(define byte-string-val #"Apple")

string-val
byte-string-val

(define test-str-equal
  ; Warning: equal? gives value comparison (contrast with test-str-eq)
  (lambda (testString)
   (if (equal? testString string-val)
       "test-str-equal using equal? gives true"
       "test-str-equal using equal? gives false")))

(format "test against the defined byte-string-val: ~a" (test-str-equal byte-string-val))
(format "test against the byte-string #\"Apple\": ~a" (test-str-equal #"Apple"))
(format "test against the byte-string #\"Apples\": ~a" (test-str-equal #"Apples"))

Which gives:

"Apple"
#"Apple"
"test against the defined byte-string-val: test-str-equal using equal? gives false"
"test against the byte-string #\"Apple\": test-str-equal using equal? gives false"
"test against the byte-string #\"Apples\": test-str-equal using equal? gives false"

Comparing byte-string object values yields the expected results:

#lang racket

(define byte-string-val #"Apple")

byte-string-val

(define test-bytes-eq
  ; Warning: eq? gives identity comparison, contrast with test-bytes-equal?
  (lambda (testByteString)
   (if (eq? testByteString #"Apple")
       "test-bytes-eq using eq? gives true"
       "test-bytes-eq using eq? gives false")))

(define test-bytes-equal
  ; Warning: equal? gives value comparison, contrast with test-bytes-eq
  (lambda (testByteString)
   (if (equal? testByteString #"Apple")
       "test-bytes-equal using equal? gives true"
       "test-bytes-equal using equal? gives false")))

(format "test against the defined byte-string-val object identity: ~a" (test-bytes-eq byte-string-val))
(format "test against the defined byte-string-val object value: ~a" (test-bytes-equal byte-string-val))

(format "test against the byte-string #\"Apple\" object identity: ~a" (test-bytes-eq #"Apple"))
(format "test against the byte-string #\"Apple\" object value: ~a" (test-bytes-equal #"Apple"))

(format "test against the byte-string #\"Apples\" object identity: ~a" (test-bytes-eq #"Apples"))
(format "test against the byte-string #\"Apples\" object value: ~a" (test-bytes-equal #"Apples"))

Which gives:

#"Apple"
"test against the defined byte-string-val object identity: test-bytes-eq using eq? gives true"
"test against the defined byte-string-val object value: test-bytes-equal using equal? gives true"
"test against the byte-string #\"Apple\" object identity: test-bytes-eq using eq? gives true"
"test against the byte-string #\"Apple\" object value: test-bytes-equal using equal? gives true"
"test against the byte-string #\"Apples\" object identity: test-bytes-eq using eq? gives false"
"test against the byte-string #\"Apples\" object value: test-bytes-equal using equal? gives false"

Upvotes: 2

Related Questions