Click here to Skip to main content
Click here to Skip to main content

Playing with IronScheme

, 3 Feb 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
An introduction to IronScheme.

Introduction

The decision to learn Scheme was not a result of a need to solve any practical problem. Professionally, I have mostly worked with imperative programming languages such as C++, Perl, JavaScript, C#, and wanted to learn something radically different in my spare time. Scheme seemed to be a good choice: a minimalist functional, dynamic, Lisp-based language, used mostly in academia for teaching purposes.

The interesting thing about IronScheme is that it runs on top of the .NET runtime. It has been developed by a single person: Llewellyn Pritchard, better known as leppie. leppie originally started developing IronLisp, a non-standard dialect of Lisp, but then decided to switch to Scheme which is a simple and standardized version of Lisp. IronScheme attracted me mostly because I expected to be able to access the functionality of the .NET framework, and ideally use Scheme not only as a learning instrument, but also as a useful scripting language. Another potentially interesting aspect of using IronScheme - embedding Scheme scripts into .NET applications - was not something I wanted to explore at this point.

leppie was kind enough to review this article and give me some useful suggestions.

Installation and "Hello World"

To get IronScheme Beta 2, go to the IronScheme page at CodePlex, currently at http://www.codeplex.com/IronScheme, click the "Downloads" link, and pick the download labeled "IronScheme-1.0-beta2-setup.exe". That results in a pretty much standard installer that installs the package under "Program Files\IronScheme" and a nice "Lambda" shortcut on the desktop. No file associations or changes to environment variables seem to be introduced by the installer. Another option is to download the source and build IronScheme with Visual Studio, but I did not take that route.

Installer.jpg

Double clicking the "Lambda" shortcut opens a REPL (read-eval-print loop) window. REPL is an interactive environment familiar to anybody who has tried languages such as BASIC, Python, or SmallTalk. Type "Hello, World!" and press "Enter"; REPL will respond with "Hello, World!".

Repl.jpg

Now, let's stop cheating, and make a real "hello world" app with Scheme. Fire your favorite editor, create a file called HelloWorld.ss, and insert the following text:

(import(rnrs))
(display "Hello World!")

Save the file, and from the command line, type:

"\Program Files\IronScheme\IronScheme.Console.exe" HelloWorld.ss

Of course, adding "\Program Files\IronScheme" to the system path would save you some typing here.

We wrote a standard Scheme program. Let's do the same thing with a .NET library. Change your HelloWorld.ss to look like this:

(import
  (rnrs)
  (ironscheme clr))

;Define a function write-ln
(define (write-ln fmt . args)
  (clr-static-call System.Console WriteLine (clr-cast System.String fmt)
    (clr-cast System.Object[]
      (list->vector args))))

; And invoke it!
(write-ln "{0}" "Hello World!")

Here, we may start explaining what we are doing. First, we import the standard Scheme libraries - "rnrs" stands for "Revised N Report on the Algorithmic Language Scheme", where "N" is currently 6. Next, we import the IronScheme CLR library which is really a file "CLR" in the directory "IronScheme" of your IronScheme installation. In that file, we have several useful macros such as clr-static-call and clr-cast defined. Then, we have a comment, starting with ";" and extending to the end of the line. In the block after the first comment, we define a function write-ln that takes a "dotted pair" as an argument. The function evaluates to a static call to System.Console.WriteLine, the first value in the pair evaluates to a System.Object, and the second value in the pair to a vector of elements of unspecified type. list->vector is a conversion procedure that converts a list to a vector. Finally, we invoke the new function with two arguments: the first one is the format, and the second one the "Hello World!" string we want to get printed.

"Scheme" in IronScheme

IronScheme is an R6RS conforming implementation of the Scheme programming language. It is beyond the scope of this article to give any serious introduction to Scheme, but I will try to describe some experiences I have had learning it so far. While definitely subjective and incomplete, this description may be useful to developers who mostly use imperative static languages such as C, C++, Java, C#, and VB to get some taste of Scheme and help them find good resources to learn it.

Different people use different methods to learn, but if you like on-line books, let me recommend three of them:

  1. Teach Yourself Scheme in Fixnum Days, by Dorai Sitaram. This is the first Scheme book I used, and seems to be pretty popular.
  2. The Scheme Programming Language, Third Edition, by R. Kent Dybvig. This book is useful as a reference as well as a tutorial, and does not focus on a single Scheme implementation.
  3. Structure and Interpretation of Computer Programs, by Harold Abelson and Gerald Jay Sussman, with Julie Sussman. Also known as "SICP", it is a classic computer science textbook that uses Scheme as the introductory programming language.

To try out most samples and exercises from the mentioned books, REPL can be enough. However, my advice is to use a good text editor instead. It is pretty hard to match parentheses from REPL, and I even had problems distinguishing "<" from "(". An editor that recognizes Lisp syntax will help you indent code correctly, which is very important if you don't want to end up counting closing parentheses, which is a pretty frustrating experience. My personal choice is Vim (see the screenshot). Other editors, such as Emacs apparently have a pretty good support for Scheme as well, but I haven't tried them. If you like using an IDE, I would expect xacc.ide, developed by the IronScheme author leppie, to be the best choice; also, there is an IronScheme plug-in for Microsoft Visual Studio 2008 SP1.

VimScheme.jpg

One of the first things a newcomer will notice with the Lisp syntax is the prefix notation. For example:

> (+ 2 3)
5
> (* 4 3 2)
24
> (* (+ 2 3) (- 6 4))
10

Most people find it is more natural to read (2 + 3) * (6 - 4), than (* (+ 2 3) (- 6 4)). However, the prefix notation combined with parentheses does not allow any ambiguities, there is no need to worry about operator precedence rules, and it is easier to write meta-programs that generate code if the prefix notation is used. In fact, metaprogramming with macros is a very important concept in Scheme, which allows programmers to extend the minimalist language according to their needs. For instance, the "clr-" macros we saw earlier are a language extension that allows us to access .NET objects from Scheme. Another example would be object oriented programming - standard Scheme does not come with built-in support for OOP, but it is possible to add such support with macros.

Scheme is a functional programming language. More precisely, it supports functional programming although it is not a pure functional language, like Haskell. Anyway, for a functional programming language, a real "Hello world" program is more likely to compute a factorial. Let's try it:

(import (rnrs))

(define (factorial x)
  (if (<= x 1)
    1
    (* x (factorial (- x 1)))))

(display 
  (factorial 3))

Obviously, factorial is a recursive function. It takes one parameter, x, and if x is less or equal to one, we return 1. Otherwise, it multiplies x with factorial of x decremented by 1. The function works because for a positive integer x, each recursive call to factorial will be invoked with a smaller parameter until it reaches 1. Then, we go up the stack and perform all outstanding multiplications.

Scheme has a dynamic type system. There is nothing in factorial that would make a caller pass a positive integer value to the function. If we call it as: (factorial "foo"), the parameter will still be passed, and only at run time, the error will be caught. This is a general problem with dynamic typing, and although it is often argued that Unit Tests catch this kind of errors anyway, in my experience, dynamic typing becomes a problem for moderate and big projects. For smaller programs and scripts, benefits of dynamic type systems usually outweigh these kind of problems.

Another issue with the above code snippet comes from the use of recursion. In each recursion step, the state of the stack needs to be preserved because the multiplication operation is deferred, and that leads to performance problems or a stack overflow. In an imperative programming language, we would initialize a variable to 1 and then iteratively multiply it by numbers in the range (1, x). Something like:

unsigned int factorial(unsigned int x)
{
    unsigned int prod = 1;
    for(unsigned int i = 1; i <= x; ++i)
        prod *= i;

    return prod;
}

In a functional language, we replace variables with function arguments. Therefore, we can try replacing the loop with another function to achieve the same effect:

(import (rnrs))

; The "loop" part of the factorial function
(define (fac-loop prod i loop-end)
  (if (> i loop-end)
    prod
    (fac-loop
      (* prod i)
      (+ i 1)
      loop-end)))

; Just execute the "loop" with the right parameters
(define (factorial x)
  (fac-loop 1 1 x))

(display 
  (factorial 3))

Here, fac-loop is said to be tail-recursive. Tail recursion is often explained as a type of recursion where the last call in the recursive function is the recursion itself. I prefer to think of it as a type of recursion where no state needs to be stored on return from a recursive call. A conforming Scheme implementation is required to support the tail-recursion optimization, which removes the overhead of recursion. IronScheme uses the fact that CLR supports tail calls to implement this optimization.

If you think that our Scheme version was longer, less readable, and less elegant than the C++ version, I don't blame you. However, we can do better:

(import (rnrs))

(define (factorial x)
  ; The "loop"
  (define (fac-loop prod i)
    (if (> i x)
      prod
      (fac-loop
        (* prod i)
        (+ i 1))))
  ; Call the "loop" with the right parameters
  (fac-loop 1 1))

(display 
  (factorial 3))

We used two Scheme language features not found in C++: nested functions, i.e., the ability to define fac-loop within factorial, and lexical scoping, which enables x to be visible by fac-loop even if not passed as a parameter. Such nested functions are also known as closures.

"Iron" in IronScheme

The "Iron" prefix is common to several programming languages built on top of Microsoft's Dynamic Language Runtime (DLR) which is a layer on top of CLR that provides support for dynamic languages. One exciting aspect of this fact is that we are able to use Scheme as a consumer of the .NET class library and all its functionality.

The easiest way to consume .NET functionality from IronScheme is by using the ironscheme library. Here is how we can display a list of directories in the root folder C:

(import
  (rnrs)
  (ironscheme files))

(display 
  (get-directories "C:\\"))

At this point, the ironscheme library offers only a subset of the .NET functionality, but there are some very cool pieces. For instance, the linq2.ss file contains the LINQ implementation written in native Scheme. Also, we can access .NET BCL objects directly. Let's write a function that displays the fully qualified assembly name, given the assembly file path:

(import
  (rnrs)
  (ironscheme clr))

(clr-using System.Reflection)

(define (get-fq-assembly-name file-path)
  (clr-prop-get Assembly FullName 
    (clr-static-call Assembly LoadFile file-path)))

As you can see, for interaction with CLR, we use a bunch of macros whose names start with "clr". For instance, (clr-static-call Assembly LoadFile file-path) would correspond to Assembly.LoadFile(filePath) in a language such as C#. Personally, I like the syntax used in IronScheme to access .NET libraries, but it is not clear whether most C#/VB.NET programmers would agree. Maybe, it would have been a good idea to adopt something like Java Dot Notation from JScheme.

Conclusion

IronScheme is surely an impressive piece of work. For learning Scheme on Windows, even as a first language, I can definitely recommend it. Based on my limited experience, it can also be very useful as a .NET scripting language - I don't see any reason for preferring IronPython or IronRuby over it.

The real question to which I don't have an answer yet is - can IronScheme be the implementation that pulls Scheme out of academic circles into the industry? We are seeing some renewed interest in the Lisp family of languages. Clojure, a dialect of Lisp that runs on Java VM, is getting a lot of attention these days, and IronScheme has some obvious advantages over Clojure: it is a fully standard Scheme implementation, which means there are many CS students that already know the language; Scheme tools work fine with IronScheme, and there are published Scheme books. The main obstacle is the lack of documentation - both references and tutorials. My hope is that this tutorial fills at least some gap in this area.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nemanja Trifunovic
Software Developer (Senior) SAP
United States United States
Born in Kragujevac, Serbia. Now lives in Boston area with his wife and daughters.
 
Wrote his first program at the age of 13 on a Sinclair Spectrum, became a professional software developer after he graduated.
 
Very passionate about programming and software development in general.

Comments and Discussions

 
QuestionWhat's "atom?" equivalent? PinmemberMember 443182014-Jun-09 11:07 
QuestionI wonder how this landed up in Fortran.NET? Pinmvpleppie3-Feb-09 18:31 
AnswerRe: I wonder how this landed up in Fortran.NET? PinmemberNemanja Trifunovic4-Feb-09 1:25 
GeneralQuestion Pinmembersam.hill3-Feb-09 16:15 
GeneralRe: Question Pinmvpleppie3-Feb-09 18:29 
GeneralRe: Question PinmemberNemanja Trifunovic4-Feb-09 4:42 
GeneralRe: Question Pinmembertonyt5-Feb-09 13:35 
GeneralRe: Question PinmemberNemanja Trifunovic6-Feb-09 3:38 
GeneralRe: Question Pinmvpleppie8-Feb-09 6:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 3 Feb 2009
Article Copyright 2009 by Nemanja Trifunovic
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid