Else: not allowed as an expression in: else

> (if #t 3 else)
. else: not allowed as an expression in: else

what is the real cause of the error?

> (if #t 3 toto)
3

in the context of a macro 'if' why is the 'else' reserved in scheme? (i would understand better the error with 'cond' that use 'else' as reserved keyword but not 'if')

note , in Kawa , it works:

#|kawa:1|# (if #t 3 else)
#|.....2|# 
3

is there a way to make the 'if' works like in Kawa ?

else is syntax used by cond and case is a syntax object that's defined as something that raises a syntax error when it's used outside of the context of a macro that uses it as a literal value. Identifiers in an expression being matched by the syntax-case pattern matcher are compared to literal ids with free-identifer=?, so have to be defined outside the macro. This means you can actually rename them and it automatically adjusts:

(require (only-in racket/base [else otherwise]))
(case 1
  ((2) 'a)
  (otherwise 'b))

Kawa defines else like so:

(define-syntax else
  (syntax-rules ()
    ((_ . rest)
     (syntax-error "invalid use of 'else"))))

Using it in a way that matches the pattern is a syntax error, but using it by itself like in your example just evaluates to a macro object:

#|kawa:1|# else
#<macro else>

Racket on the other hand defines it directly as a syntax transformer without wrapping it in syntax-rules:

(define-syntaxes (else)
    (lambda (stx)
      (raise-syntax-error #f "not allowed as an expression" stx)))

and anywhere it's used raises an error. If it was defined the same was as in Kawa, you'd get a different syntax error when it appears by itself.

2 Likes

Let’s see what Guile does:

$ guile
GNU Guile 3.0.9
Copyright (C) 1995-2023 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> else
While compiling expression:
Syntax error:
unknown file:1:0: else: bad use of 'else' syntactic keyword in subform else of else
scheme@(guile-user)> 
1 Like

thank for your complete answer. I'm trying to add feature in scheme and i do not want to rename 'else' with 'otherwise' as it would break back-ward compatibility with normal scheme code. I tried to improve the 'if then else' and keep the compatibility in all cases.

racket on the other hand defines it directly as a syntax transformer without wrapping it in `syntax-rules

yes i'm suppose that if 'else' was somewhere in the racket code wrapped like that:

(define-syntax if

  (syntax-rules (then else THEN ELSE)

it will no more create an error, but i'm not sure.

yes i had tested it with guile too. Understanding it is a preserved keyword.

but i can use 'else' if the scheme file is parsed before, it then allow some example like this:

(if test then
   ...
   ....
 else
   ....
  ....
 ....)

i think it can allow more readable code as 'then' and 'else' are convention in other languages and can replace the 'begin' when there is multiple statements in 'if' .

Of course it is still compatible with normal scheme 'if':

here is a real example:

;; a tail recursive version
(define (funct-unify-minterms-set-of-sets-rec-tail sos acc) ;; with accumulator
  
  (if (singleton-set? sos) then

      ;; singleton
      (reverse acc)

   else
      
      ;; at least 2 elements in set of sets
     {mt-set1 <+ (car sos)} ;; minterm set 1
	 {mt-set2 <+ (cadr sos)} ;; minterm set 2
	 {mt-set2-to-mt-setn <+ (cdr sos)} ;; minterm sets 2 to n
	 {weight-mt-set1 <+ (floor-bin-minterm-weight (car mt-set1))} ;; in a set all minterms have same weight
	 {weight-mt-set2 <+ (floor-bin-minterm-weight (car mt-set2))}
	 {delta-weight <+ {weight-mt-set2 - weight-mt-set1}}

	 (if {delta-weight = 1} then ;; if minterms set are neighbours

	     {unified-mt-set1-and-mt-set2 <+  (funct-unify-minterms-set-1-unit-future mt-set1 mt-set2)} 
		
	     (if (null? unified-mt-set1-and-mt-set2)
		        (funct-unify-minterms-set-of-sets-rec-tail mt-set2-to-mt-setn acc) ;; the result will be the continuation with sets from 2 to n
		        (funct-unify-minterms-set-of-sets-rec-tail mt-set2-to-mt-setn (insert unified-mt-set1-and-mt-set2 acc))) ;; end &
	  else
	     
	     (funct-unify-minterms-set-of-sets-rec-tail mt-set2-to-mt-setn acc)))) ;; continue with sets from 2 to n

       ;; this procedure returns a set of unified minterms of the current level and
       ;; and when there is no more minterms set to unify this procedure returns '() and perheaps
       ;; sort of '(()) or '(() () ...)

the code that allow this special parsing is:

;; usefull procedures and macro for the next part of code
(define (then=? arg)
  (or (equal? arg 'then) (equal? arg 'THEN)))

(define (else=? arg)
  (or (equal? arg 'else) (equal? arg 'ELSE)))



(define (call-parse-if-args Largs)

  (define lenL (length Largs))

  (when (< lenL 2)
	(error "if: too few arguments:" Largs))

  (define test (car Largs))
  (define e1 (cadr Largs))

  ; deal with the old 2 args 'if' but modified
  (condx ((= lenL 2) `(when ,test ,e1))
	 (exec (define e2 (third Largs)))
	 ((and (= lenL 3) (then=? e2)) `(when ,test
					      ,e2))
	 ((and (= lenL 3) (else=? e2)) `(unless ,test
						,e2))
	 ((= lenL 3) `(if ,test
			  ,e1
			  ,e2))

	 (else
	  
	  (define L-then '())
	  (define L-else '())
	  (define flag-then #f)
	  (define flag-else #f)
	  			   
	  (define (parse-if-args L)
	    
	    (condx ((null? L) (set! L-then (reverse L-then))
		              (set! L-else (reverse L-else)))
		   
		   (exec (define ec (car L))
			 (define rstL (cdr L)))
			 		   
		   ((then=? ec) (set! flag-then #t)
		                (parse-if-args rstL)) ; recurse
		   ;;(exec (display "before else=?") (newline))
		   ((else=? ec) (set! flag-else #t)
		                (set! flag-then #f)
		                (parse-if-args rstL)) ; recurse

		   ;;(exec (display "before flag-then") (newline))
		   (flag-then (insert-set! ec L-then)
		              (parse-if-args rstL)) ; recurse

		   ;;(exec (display "before flag-else") (newline))
		   (flag-else (insert-set! ec L-else)
		              (parse-if-args rstL)) ; recurse
		   
		   (else ; start with 'then' directives but without 'then' keyword !
		    ;; i allow this syntax but this is dangerous:  risk of confusion with regular scheme syntax
		    ;;(display "L-then=")(display L-then) (newline)
		    (insert-set! ec L-then)
		    ;;(display "flag-then=")(display flag-then) (newline)
		    (set! flag-then #t)
		    (parse-if-args rstL)))) ; recurse
	    
	    (define Lr (cdr Largs)) ; list of arguments of 'if' without the test
						    
	    (parse-if-args Lr) ; call the parsing of arguments
	    
	    (cond ((null? L-then) `(unless ,test
					   ,@L-else))
		  ((null? L-else) `(when ,test
					 ,@L-then))
		  (else `(if ,test
			     (let ()
			       ,@L-then)
			     (let ()
			       ,@L-else)))))))

i put it in the latest release of the parser available in Scheme+ for Racket v8.0:

| damien_mattei
April 3 |

  • | - |

but i can use 'else' if the scheme file is parsed before, it then allow some example like this:

(if test then
   ...
   ....
 else
   ....
  ....
 ....)

i think it can allow more readable code as 'then' and 'else' are convention in other languages and can replace the 'begin' when there is multiple statements in 'if' .

I have never seen "begin" used to cope with multiple statements in if.

The normal conditional in scheme is "cond". "if" can be used as a shorthand in functional code or anyway, when there are not multiple statements in one branch. If one has to insert begins then it's not a shorthand anymore. What's the point.

As far as I can see, "begin" is rarely used in hand-written code, except in macros. As a check, I searched in a program of mine consisting of 20180 lines. I found exactly 2 occurrence of begin, none of them used to cram more statements in an if branch.

@Olopierpa: begin can be used to cram multiple expressions in if branches, and that’s pretty normal (see this as an example). However, modern Racket code should not do this if + begin, and simply use cond instead.

Also, while begin can cram multiple expressions, it can’t cram “statements”, because if branches must be expressions. E.g., this will fail:

(if #t
    (begin
      (define x 1)
      x)
    #f)

cond, on the other hand, works:

(cond
  [#t 
   (define x 1)
   x]
  [else #f])

1 Like

| sorawee
April 4 |

  • | - |

@Olopierpa: begin can be used to cram multiple expressions in if branches, and that’s pretty normal (see this as an example).

That example shows clearly why it's a bad idea to use if + begin.
I change my "never seen" to "rarely seen".

@damien_mattei: your modification to if is ambiguous. What do you want the following program to evaluate to?

(let ((then 0))
  (if #t then
     10))

Both Scheme and Racket would evaluate the above to 0, but your extension would evaluate it to 10.

/////////////////////////

I also want to note this: it is really frustrating to help with anything related to the Scheme+ project.

Most people use Racket the way it’s intended to be used. For example, we write programs in modules. We use syntax-parse to easily write macros.

The Scheme+ project seems to have its own ways of doing things. E.g., everything is written outside modules. Unhygienic reader extensions are used to customize syntax.

As a result, I think the code in this project can barely be called “Racket code”. You will have trouble doing things. This means you will need more and more help. But people won’t be able to help you because they are not familiar with Scheme+. And this becomes a cycle.

To be honest, with how things currently are, my personal opinion is that this forum is not a good fit to ask these kinds of questions.

It also doesn’t help that the Scheme+ related posts always contain a lot of code that no one would have an idea what it is. E.g., no one would know what condx or exec is. I think communication really needs to be improved for anyone to understand what you are trying to do. E.g., provide us necessary context, but make things self-contained.

/////////////////////////

Here’s an example of a way to implement your if extension using Racket facilities.

#lang racket

(require syntax/parse/define)

(define-syntax (then stx)
  (raise-syntax-error #f "out of context use" stx))

(define-syntax-parser if
  [(_ test-expr:expr
      {~literal then}
      then-expr:expr ...
      {~literal else}
      else-expr:expr ...)
   #'(cond
       [test-expr then-expr ...]
       [else else-expr ...])]
  [(_ test-expr:expr {~literal then} then-expr:expr ...)
   #'(when test-expr
       then-expr ...)]
  [(_ test-expr:expr {~literal else} else-expr:expr ...)
   #'(unless test-expr
       else-expr ...)]
  [(_ test-expr:expr then-expr:expr else-expr:expr)
   #'(cond
       [test-expr then-expr]
       [else else-expr])])

(let ((then 0))
  (if #t then
     10)) ;=> 0

(let ((not-then 0))
  (if #t then
     10)) ;=> 10

That’s ~20 lines to create a hygienic macro. Contrast that with the ~80 lines of unhygienic reader extension.

Sorry for being blunt, but I think it’d be useful for you to get this feedback now rather than later.

3 Likes