1.3. Scheme Basics

GUI elements in Fluent are written in an implementation of the Scheme programming language. To load a Scheme file into Fluent, click File, select the Read submenu, and select Scheme.... Scheme statements are written in parenthesis, and statements are often nested within other statements. Comment lines in Scheme begin with a semi-colon, and variable names and Scheme commands are not case sensitive. In this section, you will learn about some basic Scheme statements and how you can use them as you create a GUI for your UDF. For a more complete guide to the Scheme language, visit http://www.scheme.com/tspl4/.

1.3.1. Data Types

Scheme allows for the use of a number of important data types. Those provided in this section are the types that Fluent allows you to pass to your user defined functions.

1.3.1.1. Boolean

(define isBool #t)
(set! isBool #f)
(display isBool)
(boolean? isBool)
  • In Scheme, booleans are initialized using the notation #t for true and #f for false.

  • In the example above, a new variable isBool is being initialized to true and then being set to false.

  • The display command is a console printing method that allows you to check the value of a variable. The line (display isBool) would output #f.

  • The boolean? command is used to check if the following argument is a Boolean or not. The line (boolean? isBool) would output #t.

1.3.1.2. Integers

(define isInt 1)
(set! isInt (+ isInt 1))
(display isInt)
(integer? isInt)
(number? isInt)
  • Integers in Scheme are similar to integers in most other languages.

  • In the example above, the new variable isInt is initialized to 1.

  • In the next line, it is incremented to 2. Remember that in Scheme the operation always comes before the data elements being operated on.

  • Using the display command, we can check that the value of isInt is indeed 2.

  • The integer? command will check to see if isInt is an integer and the number? command will check to see if it is one of any of the number data types in Scheme. The output of both of these commands will be #t.

1.3.1.3. Reals

(define isReal 1.2)
(set! isReal (- isReal .5))
(display isReal)
(real? isReal)
(number? isReal)
  • Real numbers in Scheme work in a similar way as the integers.

  • In the example above, the new variable isReal is initialized to 1.2.

  • In the next line, it is set to its own value minus 0.5. Remember that in Scheme the operation always comes before the data elements being operated on.

  • The display command allows us to verify that the value of isReal is 0.7.

  • The commands real? and number? will both return #t.

1.3.1.4. Characters

(define isChar #\a)
(set! isChar #\b)
(display isChar)
(char? isChar)
  • Characters in Scheme are always preceded by the notation #\.

  • In the example above the variable isChar is initialized to the value a. In the next line, it is set instead to the value of b.

  • By using the display command, we can check that the value of isChar is indeed b.

  • The result of the char? command will be #t.


Note:  Although the example above proves that variables can be set to character values, you will usually just be working the character itself, as it makes little sense to assign a variable for a data type that is only 1 character.


1.3.1.5. Strings

(define isString "This is a string")
(set! isString "This is a different string")
(display isString)
(string? isString)
(string-length isString)
(string-ref isString 0)
(set! isString (string-append isString " plus more"))
  • Strings in Scheme are very similar to strings in most other languages.

  • In the example above, the variable isString is initialized to "This is a string". In the next line, it is set instead to the value "This is a different string".

  • Using the display command, we can confirm that the value of isString is "This is a different string".

  • The string? command will confirm that isString is a string by outputting #t.

  • The string-length command will return the number of characters in the string, in this case 26, and the string-ref command will return the character in the string at the position of the integer passed to it. In this case it would return the character at position 0, or #\T.

  • The string-append command adds two strings together and returns the result as a third string. The example above uses a string-append command to combine isString with " plus more". It also uses a set! command to save the result back to the variable isString. The new value of isString would now be "This is a different string plus more".


Note:  While the string-ref command asserts that a string starts a position 0, the string-length command will always treat strings as though they start at position 1. This makes sense, as a string with a string-length of 1 has one character at position 0.


1.3.1.6. Symbols

(define isSymbol 'abc)
(symbol? isSymbol)
(define isString "abc")
(eq? (string->symbol isString) isSymbol)
  • Symbols are atomic values that are somewhat similar to strings. Symbols and strings could be used for many of the same purposes, but there are a few important differences that give symbols their own niche in Scheme.

  • Multiple variable names that refer to the same symbol will be considered the same object, meaning that when using the eq? function on two different variables that refer to the same symbol content, you will get #t instead of #f like you would with strings.

  • This is seen in the example above. A symbol, isSymbol, is initialized to abc. The symbol? command confirms that the variable is a symbol. Then, a string isString is initialized to the same value, abc. The following line illustrates two important points. First, any string can be cast as a symbol using the string->symbol command. Second, any two symbols with the same contents are considered to be the same object and are equal. In this case the eq? statement would return #t.

  • Additionally, symbols are immutable, meaning that they cannot be altered once they are created. Strings on the other hand are mutable, meaning that they can be altered, for example, using string-append (see Strings).

  • This shows that while strings have more versatility as a data type, symbols are a lightweight and easily comparable data type that is preferable when you know that you will not need to make any alterations once you have created it. As a result, symbols are seen very often in Scheme code in places where strings simply are not necessary.

1.3.1.7. Pairs and Lists

(a . b)
(define aPair (cons 1 2))
(a . (b . (c . ())))
(define aList (list 1 2 3 4 5))
(car aList)
(cdr aList)
(list-ref aList 2)
(list-tail aList 1)
  • Pairs and lists are two related Scheme data constructs that are often used when multiple data items need to be stored together.

  • Pairs are two data items that can be accessed from the same variable name. Pairs are often represented as two data items with a period between them, such as the first line in the example above. Pairs are created using the cons statement, as seen in the second line of the example above.

  • Pairs can be used to create lists. Lists are pairs whose second element is another pair. This makes your list as long as you need it to be by having the right number of appended pairs.

  • The third line of the example above shows how a list looks conceptually, made up of individual pairs. The fourth line in the example above shows you how to create a list using the list statement.

  • The rest of the example above shows how to access certain pieces of data within the list. The car statement is used to access the first item in the list. In the example above, it would return 1. The cdr statement is used to access everything but the first item in the list. In the example above, it would return (2 3 4 5).

  • The list-ref statement is used to access an item in a list by the position it is within the list. The list-ref statement in the example above would return 3. The list-tail statement is used to return the remaining items in the list starting at the position you tell it to. In the example above, since it is passed position 1 as a starting point, the list-tail statement would return (2 3 4 5).

1.3.2. Important Concepts

There are a number of Scheme concepts that are important to understand when creating your own Fluent interface. This section discusses those concepts that you are most likely to see in examples throughout this guide and use as you build your own interface.

1.3.2.1. Define

(define isInt 1)
(define whoKnows)
  • The define statement is used to create a new variable in Scheme.

  • Variables in Scheme do not have types, but rather return a value. The return values themselves have types, but using the variable name itself is like calling a function that can return a value of any type. For example, the variable isInt in the example above returns the integer 1.

  • Scheme variables do not have to be initialized with a value when they are created. In the example above the variable whoKnows is empty and can be assigned to a value of any type.

1.3.2.2. Set!

(define someVariable)
(set! someVariable 1)
(set! someVariable 2.5)
  • The set! statement is used to assign a value to a variable that has already been created. If the variable has not been created via a define statement you will receive an unbound variable error.

  • Since there are no variable types in Scheme, a variable can be overwritten with multiple types of data. In the example above, the variable someVariable is initially empty.

  • After the first set! statement, someVariable will return the integer value of 1. After the second set! statement however, someVariable will return the real value of 2.5 instead.

  • This flexibility in variable use has its advantages, as it is easy to create a variable and begin working with it immediately. It can also be a problem if you aren’t diligent in keeping track of each variable’s return type.

1.3.2.3. Let

(let ((x 5))
  (let ((x 2)
        (y x))
    (+ y x)))
  • let statements define the scope of variable bindings that occur within them. They are similar to { } in C and C++.

  • The let statement is broken up into two parts. The first part begins with the parenthesis immediately following the word let. Within this set of parenthesis are the variable bindings that you want to define for the rest of the let statement.

  • For the outer let in the example above, the first part consists of the variable binding (x 5). For the inner let of the example above the first part consists of the variable bindings (x 2) and (y x).

  • The second part of the let statement is known as the body. Once the variable bindings are complete, this is where the operations using these variables occurs.

  • For the outer let in the example above, the body consists of everything that follows the first part, including the inner let, until the final parenthesis. For the inner let in the example above, the body includes the statement (+ y x).

  • When lets are nested, as in the example above, and both the outer and inner let bind a value to the same variable, the value bound by the outer let is the one used until the first part of the inner let is closed.

  • Therefore, the (+ y x) statement in the example above will use the value 2 for x as opposed to 5. The value for y in this statement, however, will still be 5 because when y was assigned the value of x, it didn’t see that x had been changed to 2 because the first part of the inner let wasn’t closed yet. This makes the result of the addition 7.

1.3.2.4. Lambda

(define doubleProcedure (lambda (x) (+ x x)))
(doubleProcedure 1)
  • The lambda keyword is used to create a new Scheme procedure.

  • When using the lambda keyword to make a new procedure, you must first provide a list of the variables that will be used in the procedure. Once the variables are declared, you can then specify the functionality of the procedure.

  • This process is seen in the example above. Once the keyword lambda is used to make doubleProcedure a procedure, the variable x is declared. Since x is the only variable being used in this procedure, we can now move on to the functionality, which consists of the (+ x x) portion of the line.

  • Now that the procedure has been created, we can call it using any number for x, as seen in the second line of the example above, which will return 2.

1.3.2.5. If

(define ifExample (lambda (x) (if (>= x 0) (+ x 1) (- x 1))))
(ifExample 1)
(ifExample -1)
  • The Scheme if statement has the same basic logic as if statements in most other programming languages, though the syntax is slightly different.

  • In Scheme, the if statement is made up of three parts. The first part is the test, which determines how you will proceed through the rest of the if code.

  • The second part is the consequent, which is the code block executed if the test is true.

  • The third part is the alternative, which corresponds to an else statement in other popular languages. This code block is executed if the test is false.

  • In the example above, a procedure called ifExample is created to show how the if statement works. For information about creating procedures in Scheme, see Lambda.

  • In the body of the procedure is a simple if statement. The test in this example is (>= x 0), the consequent is (+ x 1), and the alternative is (- x 1).

  • When passed a number, the ifExample procedure will increment the number if it is greater than or equal to 0 and decrement it if it is a negative number. Therefore, the function call (ifExample 1) will return 2 and the function call (ifExample -1) will return -2.

1.3.2.6. Map

(define halve (lambda (x) (/ x 2)))
(map halve (list 2 4 6 8 10)) 
  • The Scheme map statement applies a desired function to each element of a list. For more information on lists, see Pairs and Lists.

  • The example above uses a function called halve. For more information on creating functions with a lambda statement, see Lambda.

  • In the example above, the function halve is created to divide any number passed to it by 2.

  • In the second line, a map statement is used to apply the halve function to each number of the list (list 2 4 6 8 10). This map statement returns (1 2 3 4 5).

  • The map statement can be used with essentially any function as long as that function is compatible with the list items on which it will operate.