27

I'm primarily a C++ (thus an OO/imperative) programmer and I find it quite bizarre that you can only have one statement per evaluation in a conditional statement such as an if-statement in Scheme, a functional language.

For example:

 (let ((arg1 0) (arg2 1))
   (if (> arg1 arg2)
       arg1
       arg2)))

Erroneous example:

(let ((arg1 0) (arg2 1))
  (if (> arg1 arg2)
      (arg1 (display "cool"))
      (arg2 (display "not cool"))))

gives me an error of a type "procedure application: expected procedure, given: 2; arguments were: #void"

That can be solved by placing that said conditional statement into different statements within a body of a defined function for example, with the conditional statement's body having separate statements every time as follows:

(if (condition) statement1a statement2a)
(if (condition) statement1b statement2b)

and so on...

It goes without saying that it's not too practical. Not to mention the duplicated code overhead.

Am I missing anything here or is there really no other way?

Asumu Takikawa
  • 8,447
  • 1
  • 28
  • 43
Alex D.
  • 427
  • 1
  • 5
  • 14

5 Answers5

28
(let((arg1 0)(arg2 1))
  (if (> arg1 arg2) 
      (begin
        (display arg1)
        (newline)
        (display "cool"))
      (begin
        (display arg2)
        (newline)
        (display "not cool"))))

when you say (arg1 (disply "cool")) you are implying that arg1 should be a proceure.

Rajesh Bhat
  • 980
  • 6
  • 8
  • True, I should've spotted that myself since the syntax to calling function is (function_name args ...) and a function itself can be a variable in Scheme. Not to mention the error message said the exact same thing. Thanks for a swift reply. – Alex D. Jun 29 '12 at 18:12
11

One thing you may be missing is that in Scheme there is no such thing as a "statement". Everything is an expression and things you might consider statements also return a value. This applies to if, which is typically used to return a value (e.g., (if (tea-drinker?) 'tea 'coffee). Unlike C++, most uses of conditionals are not going to be for mutating a variable or printing values. This reduces the need for having multiple expressions in an if clause.

However, as Ross and Rajesh have pointed out, you can use cond (recommended) or use begins in your if clauses. Note that if you have many side effecting computations in a conditional, you might not be using Scheme idiomatically.

Asumu Takikawa
  • 8,447
  • 1
  • 28
  • 43
  • That made me think: I probably should seek out a proper way to implement functions in scheme. It's an entirely different approach to programming than C-based languages. Indeed, even the use of recursion is discouraged in C++ (a possibility of stack overflow, ironically), whilst in Scheme and Lisp-based languages it is a way of life. Oh and thanks by the way. – Alex D. Jun 29 '12 at 18:15
  • 2
    Have you looked at [How to Design Programs](http://www.htdp.org/)? It's a textbook that guides you through a process for designing functions in a functional style (though at the very end it talks about using mutation too). There is also a work-in-progress [2nd edition](http://www.ccs.neu.edu/home/matthias/HtDP2e/index.html) with substantial improvements. – Asumu Takikawa Jun 29 '12 at 19:50
  • Also, to make clear what `begin` is doing, if I understand it correctly, it just creates a lamdba and immediately applies it. That is, `(begin one two three)` is the same as `((lambda () one two three))`. – d11wtq Jun 30 '12 at 05:40
  • I see, that makes sanse. Asumu, I kinda learned it on my own without reading design books by improving my own designs, my adequate knowledge of math helped a great deal. I shall take a look at it however, once I'm finished with general syntax of Scheme. – Alex D. Jun 30 '12 at 11:30
  • 1
    In a compiled scheme a simple program such as (begin (set! x 5) (+ x 6)) wouldn't create a lambda. This example would just be mov statement and then an add (disregarding boilerplate and return statement). At least that is how we had to handle it for my compilers class at IU (course material created by creator of Chez Scheme compiler) – Ross Larson Jul 01 '12 at 00:35
  • I should probably learn assembly as well. That shall be my next target after Scheme. I read somewhere that people behind Scheme and now Racket are trying to make it even more functional by deprecating set! statement in the future versions of the language. I wonder what'll come of it. – Alex D. Jul 01 '12 at 12:27
7

@RajeshBhat gave a good example of using begin with an if statement.

another solution is the cond form

(let ([arg1 0] [arg2 1])
  (cond
    [(< arg1 0) (display "negative!")]
    [(> arg1 arg2) (display arg1) (newline) (display "cool")]
    [else (display arg2) (newline) (display "not cool")]))

Each line in the cond form has an implicit begin which you can actually see if you look at the implementation of the cond.

(link is to the Chez Scheme documentation, might (read: probably) not be same implementation you are using as it is proprietary, though Petite Chez is free (no compiler in petite version))

http://scheme.com/tspl4/syntax.html#./syntax:s39

Edit: Important note about begin forms and therefore all expressions that have implicit begin's.

the following code

(+ 2 (begin 3 4 5))

evaluates to 7. This is because the return value of a begin form is its last expression. This is just something to keep in mind when using begins. However, using side-effects and things like displays will work just fine in the positions where the 3 and 4 are.

Jan Bodnar
  • 10,969
  • 6
  • 68
  • 77
Ross Larson
  • 2,357
  • 2
  • 27
  • 38
  • Thank you: I shall keep that in mind. Both if+begin and cond seem to be doing the same job, I'll be using them both on a regular basis whilst I'm teaching myself Scheme. – Alex D. Jun 29 '12 at 18:21
1

Since you are already using an iterative process in the "inner" procedure, why not use this definition using named let

(define (fact n)
  (let inner ((counter 1) (result 1))
    (if (> counter n)
        result
        (inner (+ counter 1) (* result counter)))))

Since the state of the process can be determined with just 2 variables, it will not use that much memory.

for example (fact 6) is computed like this

(inner 1 1)
(inner 2 1)
(inner 3 2)
(inner 4 6)
(inner 5 24)
(inner 6 120)
(inner 7 720)
720

Here is the letrec version of the same procedure:

(define (fact n)
  (letrec ((inner
            (lambda (counter result)
              (if (> counter n)
                  result
                  (inner (+ counter 1) (* result counter))))))
    (inner 1 1)))
Rajesh Bhat
  • 980
  • 6
  • 8
  • I've only seen three let forms so far: let, let* and letrec. I am yet to see how fluid-let and named-let work. Your code is obviously more compact than mine, but are you sure that the memory costs are also lower? My function uses 3 variables as well as mutation. – Alex D. Jun 30 '12 at 14:07
  • edited to add the letrec version for your convinience . It works exactly the same way. A recursive process requires a lot of memory since it has to remember the previous states. The functions i have defined above may be recursive, but the **process is iterative**. it does not require memory of previous states. – Rajesh Bhat Jun 30 '12 at 14:28
  • I see, I didn't think it was iterative. But now on the closer inspection I can see that its iteration is happening in inner function's second argument, bypassing the need of the set! (assignment) statements. Thank you for providing an elegant alternative. – Alex D. Jun 30 '12 at 14:48
1

If you feel restricted by Scheme's syntax, you can always change the syntax by defining a macro. A macro is like a lambda, except it generates code at compile-time (like a C++ template) and its arguments don't get evaluated before the macro is invoked.

You can easily make a macro to let you use the syntax that normally means procedure-application, like (arg1 "cool"), to mean "display everything inside the parentheses with a newline after each item". (It'll mean that only inside the macro, of course.) Like this:

(define-syntax print-if
  (syntax-rules ()
    [(_ c (true-print ...) (false-print ...))
      (if c
          (display-with-newlines true-print ...)
          (display-with-newlines false-print ...))]))

(define-syntax display-with-newlines
  (syntax-rules ()
    [(_ e)
      (begin (display e) (newline))]
    [(_ e0 e* ...)
      (begin (display-with-newlines e0) (display-with-newlines e* ...)) ]))

(let ([arg1 0] [arg2 1])
  (print-if (> arg1 arg2)
            (arg1 "cool")
            (arg2 "not cool")))

Output:

1
not cool

Don't worry if you don't understand how the macro definitions work right now. If you're just trying out Scheme after mastering C++, no doubt you're experiencing a lot of frustration. You should have a wee glimpse of the kind of power and flexibility Scheme really has.

A big difference between Scheme macros and C++ templates is that in a macro, the entire Scheme language is available to you. A macro tells, using Scheme, how to transform an s-expr into Scheme code, in any completely arbitrary way that you like. The compiler then compiles the Scheme code output by the macro. Since Scheme programs are themselves s-exprs, there are essentially no restrictions (other than lexical scoping and needing to enclose everything in parentheses).

And don't let anyone discourage you from using side-effects if you want to. The glory of Scheme is that you can do whatever you want.

Ben Kovitz
  • 4,920
  • 1
  • 22
  • 50
  • Cool, thanks! I should be able to do all kinds of crazy stuff with it, provided I master Scheme's macro system. – Alex D. Jul 05 '12 at 13:26