News

muvee Reveal - the latest incarnation of muvee's flagship product - has just been released on 11 June 2008! The behaviours of the 8 bundled styles are specified using muSE, in addition to all the styles developed for the now discontinued muvee autoProducer 6.1.

Saturday, July 19, 2008

Experimental 'the' and 'it' primitives

Version 437 introduces a couple of experimental primitives the and it aimed at minimising the need for local variables created to hold intermediate results.


Blogged with the Flock Browser

Thursday, June 12, 2008

muvee Reveal

muvee Reveal - the latest incarnation of muvee's flagship product - has just been released on 11 June 2008! The behaviours of the 8 bundled styles are specified using muSE, in addition to all the styles developed for the now discontinued muvee autoProducer 6.1.

Saturday, December 29, 2007

muSE v372 ...

Download - for macosx, for win32.
Summary of changes between v343 and v372 -

  • Features: Simple module support, more flexible xml reader, polymorphic get and put.
  • APIs: memports, muse_read_xml_node and muse_raise_error
  • Bug fixes: GC fixes, muse_pread() and anonymous symbols.

Detailed changes -

Features

  • Support for simple modules.
  • The xml reader has been made slightly flexible in dealing with non-standard input. In xml tag attributes, attrib="value" and attrib='value' are standard but attrib=value is not. Since a lot of html code uses attrib=value form, this is also now accepted by the parser. Note that this is still not enough to accept old html files because they might contain non-xhtml tags such as <br> which should be <br/> in xhtml.
  • Added a notion of a 'prop' view for getting and setting properties of arbitrary native objects. Properties are accessed and modified using (get obj prop) and (put obj prop value). i.e. get and put are now polymorphic over all object types that support the 'prop' view. See muse_prop_view_t struct for the interface.
  • Added new "box" type in the same spirit as R5RS boxes.
  • A symbol of the form ".name" will be expanded into the expression (-> self 'name) at read time. This makes it easy to refer to object properties from within method bodies.
  • Made module names behave like "do", except that within the scope of the module expression's body, symbols are interpreted in the scope of the module - irrespective
    of whether the module expression is at the global level or within a closure creating expression.
    This feature is made possible by the introduction of a new "scope view" - id 'scop' - which lets an object provide a hook into the closure creation code to introduce local bindings that aren't visible in the s-expression itself.
    The scope view lets us define context sensitive forms. Ultimately, all binding introducing forms - define, let, case, fn, fn: and gfn - can be implemented by providing a different scope view for each so that there is a constant testing overhead during closure creation instead of depending on the number of such special forms that need to be supported.

New APIs

  • Memport objects can be created from the C api.
  • muse_read_xml_node is now exposed in the C api.
  • Added muse_raise_error() for raising muSE exceptions in native code.

Bug fixes

  • (major): The main process structure was being prematurely destroyed before the gc function call returned. As a consequence, the area around the process's stack got corrupted and muse_destroy_env() won't exit cleanly. This went undetected because the primary use was via the repl which can be exited only using (exit) which terminates the whole process.
  • (major): Major GC bug that crept in from a process related tweak to make the gc run atomically. Ever since the tweak (to wrap muse_gc_impl() within an atomic block) the gc never ran but simply grew the heap. FIXED! Whew! (I'd been wondering for a while why a gc never got triggered and the heap was grown instead during several large data set experiments, but didn't probe further. Lesson learnt - pay attention to your hunches!)
  • (minor): muse_pread() wouldn't return EOF if there were only spaces or comments before end of file (or port in general). Modified it so that if only spaces and/or comments are found before it hits EOF, it dutifully fails.
    * (minor): Anonymous symbols cannot have the sym itself as the head value any more due to process architecture. So changed the head value of anonymous symbols to () (or MUSE_NIL). Since this affects the gensym operator which converts an anonymous symbol into a permanently interned symbol, muse_intern_symbol was also modified to change the head value of an interned anonymous symbol to refer to the proper symbol location in the symbol vectors of all processes.

Other changes

  • Native objects are no longer evaluated lazily when they appear in the function position. They are treated like any other native function. This allows us to define native objects which evaluate their arguments inside a special kind of context.
    To compensate for this loss, the "lazy" operator's definition has been modified (and simplified) to cause a function application (native or user defined) to be
    postponed until forced. Using the new "lazy" operator, you can use tail recursion with native functions too.

Saturday, November 03, 2007

New v338 of muSE available

Note that v338 has been replaced by v343, which contains a minor fix to the exception handling code. Prior to the introduction of finally, an expresion such as (try (raise 'error)) probably indicated a programming error since no handlers were specified. With finally however, a try without handlers is valid since it may install finalizers using finally.

Downloads page.
Summary of changes between v285 and v338 -

Language and library features

  1. mickey processing support - turns muSE into a scheme-based macro expander.
  2. Added native implementations of reverse and reverse! functions.
  3. with-connection-to-server has been replaced with open.
  4. Added split function to split a string at given separator characters.
  5. [MAJOR!] Added (finally ...) to the exception mechanism. See ExceptionHandling.
  6. Invocation of spawn has been changed.

Bugfixes and enhancements

  1. muSE can now be built as a dll.
  2. Bug fixes in hashtable object.
  3. Fixes to lazy evaluation mechanism. Incorrect behaviour in certain obscure circumstances.
  4. Fixes to networking functions - particularly read will now return () when the socket it is reading from is closed at the other end.
  5. Some fixes to exception handling implementation.
  6. list-files now usable under unix using same (but limited) wild-card syntax like windows.

Saturday, October 20, 2007

Finally!

A while ago, I'd written about raising and handling exceptions in muSE. The key idea, borrowed from Common Lisp, is that when an exception is raised, the execution context should not be unwound to the point where the exception can be handled. This allows an exception handler to resume execution from the point the exception occurred or try another handler, starting again from the inner most context. The current try-raise-retry facility in muSE manages to capture this requirement fairly well. However, there was one glaring omission - the inability to specify scope limited cleanup operations. In v332, muSE gets a cleanup mechanism ... finally ... I mean literally a finally block .. and this post is all about it. If you're going "oh yeah I know all about finally, Java has it", I humbly urge you to read on. You'll wish you had this in Java.

In muSE, you can now place a code in a finally block that can feature anywhere within an expression protected by a try. "Anywhere" doesn't just mean the visible lexical scope, but the entire execution scope. Not only can you have finally anywhere, you can have as many of it as you need - you can even accumulate finalizing actions in a loop. The finally block gets captured into a thunk that gets evaluated at the end of the scope of the try context in which the block occurs. The thunks execute in the reverse order in which they are created. The thunks can refer to any variable in its lexical context, since a closure is created when evaluating a finally block.

Here is a simple example that reads a number from file F, multiples it by a given value and returns the result.


; Raises an exception if the file couldn't be opened.
(define (safe-open file)
(let ((f (open-file file 'for-reading)))
(if (eof? f)
(raise 'FileOpenError file)
(do (finally (print "Closing file" file) (close f))
f))))

; Read a number from a file
(define (read-number file)
(define F (safe-open file))
(let ((n (read F)))
(if (number? n) n (raise 'NotANumber n))))

Here is a way to read a number from "F.scm" and if you don't get a number, use a default value -

(try (read-number "F.scm")
(fn (ex 'NotANumber n)
42))

Here is a way to read a number from "F.scm" and if the file doesn't exist, use "Default.scm" instead -

(try (read-number "F.scm")
(fn (cont 'FileOpenError file)
(cont (safe-open "Default.scm"))))



When do exceptional conditions occur?

In pure computations, they can happen when an unsupported - "unexpected" - input value is encountered when calculating the result. Let us call these p-type exceptions. The other type is when dealing with side-effecting computations, of which IO is arguably the most dominant case. Let us call these s-type exceptions. 

p-type exceptions can be dealt with in a language which allows one to substitute another expression for one containing the exceptional case, when the case does happen. muSE does this fairly well with its try-raise-retry operators. The example function in the earlier article shows how a pure function with an exceptional condition can be tamed by the calling context, given an expression substitution mechanism.

s-type exceptions cannot be dealt with adequately with a pure-expression substitution mechanism. In the interest of composability, it is often desirable to wrap side-effecting expressions into what feels like a pure function - for example, a data structure lookup that appears to never fail might be implemented via a connection to a remote database. To do that, we need to be able to execute one or more side-effecting cleanup operation, such as closing the database connection, before the wrapper function returns to its calling context. 

With the new finally block, I believes muSE satisfies both these cases.

Monday, October 01, 2007

muSE on MacOSX Intel

Now I have an Intel MacOSX machine and can build and verify muSE on it.
The v285 build was broken on OSX Intel machines and I've fixed it (Issue 32).

Here is a build with the fixes - muSE-v316-objc-macosx.tar.gz"

Wednesday, August 15, 2007

Curry Howard isomorphism

Curry Howard isomorphism states that every logical proposition corresponds to a type. (See also Curry-Howard-Lambek correspondence.) I've been fascinated by this recently, in the context of Haskell, 'cos it yields very interesting and powerful functions. Often you can just take the type expression in Haskell and write a function that has the type.

For example, consider the trivial logic "theorem" -

If it is true that proposition A implies proposition B, then A is provable implies B is provable.
If you notate provability by the symbol #, you can write that as -
(A => B) => (#A => #B)
If you consider => to be the function type constructor (in Haskell), this gives you the type of fmap where # is any functor. It looks like you can uniformly map "is provable" to some kind of containment - like a list or a set.

Here's a very interesting function derived from another logic proposition - From Löb's Theorem to Spreadsheet Evaluation.

Here's an implementation of the loeb function in muSE -

; Limitation - fmap over lists only
(define (fmap f x)
(if x
(lcons (f (first x))
(fmap f (rest x)))
()))
(define (loeb x) (fmap (fn (a) (a (loeb x))) x))


.. and a variant on the author's example -

(define test
(list (fn (x) 17)
(fn (x) (apply + (drop 3 x)))
(fn (x) (* 2 (nth 3 x)))
(fn (x) (+ (nth 4 x) (nth 5 x)))
(fn (x) 3)
(fn (x) (nth 0 x))))

The stair climbing robot

An interesting problem called "the stair climbing robot" was recently posted on LtU.

Your stair-climbing robot has a very simple low-level API: the "step" function takes no argument and attempts to climb one step as a side effect. Unfortunately, sometimes the attempt fails and the robot clumsily falls one step instead. The "step" function detects what happens and returns a boolean flag: true on success, false on failure. Write a function "step_up" that climbs one step up (by repeating "step" attempts if necessary). Assume that the robot is not already at the top of the stairs, and neither does it ever reach the bottom of the stairs. How small can you make "step_up"? Can you avoid using variables (even immutable ones) and numbers?

I thought I'll try to solve it using muSE's lcons lazy function. Here's a solution -

; A step function which fails 50% of the time.
(define (step)
(if (= 0 (rand 2))
(do (print "DOWN") ())
(do (print "UP") T)))

; Gets the second element of a list s,
; forcing the value of the first element if
; the list happens to be lazy.

(define (forced-next s) (first s) (first (rest s)))

; (steps) generates a sequence of
; successful upward steps. To climb N steps,
; just take N elements from (steps).

(define (steps)
(lcons (if (step)
T
(forced-next (rest (steps))))
(steps)))

; Climb 1 step
(take 1 (steps))

Tuesday, July 31, 2007

spawn behaviour changed in v295

The spawn function has different semantics from v295 of muSE.

Prior to v295, spawn took a thunk and evaluated it in a separate process repeatedly until the thunk evaluated to a non-() value. This was done to make it simpler to write server processes that run the same code over and over until some condition occurs that makes it terminate.

Since v250, muSE has had support for stack optimized tail recursion and lazy evaluation. This means we can now write server processes as tail recursive functions that never terminate. Therefore we don't need the extra help provided by the spawn function any more.

From v295, (spawn thunk) will cause (thunk) to be evaluated only once and it will discard the result.

Expressing server processes as tail recursive functions, besides stating the obvious, gives you new ways to modularize parts of the server. If your server needs to be placed into modes when it receives certain input, each of these modes can be expressed as tail recursive functions that revert back to the main server function whenever an exit mode message is received.

Tuesday, July 17, 2007

XML processing

muSE now (in v285) has support for parsing and generating simple XML structures. See -

Wednesday, May 23, 2007

Getting lazy

Recently, in v250 of the muSE core library, I added support for stack optimization of tail calls - finally!

muSE has been missing the ability to do tail recursion for ever and now its finally there. The absence of tail recursion support had a rather ugly but practical consequence for the language - the while and for looping constructs had a reason to exist because you couldn't loop otherwise without blowing the stack top. I'm glad to note that they can now be safely RIP-d.

Tail call optimization as implemented does not apply to tail calls of native functions. It only applies to applications of user defined functions that appear in the tail position in a block.

As a by-product of the tail call optimization, I was surprised to find it easy to implement lazy evaluation. There's an experimental primitive lcons which can be used to construct a list lazily. This operator is inspired by Dan Friedman's TR44: CONS should not evaluate its arguments technical report. Some classic examples can be found in examples/lcons-v238.scm in the source tree.

Friday, March 30, 2007

Exceptions redux

(Ref to muSE v234)
Although I've written about it a bit earlier, I did not do muSE's version of exception handling and recovery enough justice then. I simply described how to use it, that too incompletely.

The bigger picture here is the question -

What interface does a function exactly provide its user?
In languages such as C, functions as computation specifications are used only via their arguments, or via side effects on the environment. A function's documentation will typically tell you what it will do when you pass it certain arguments, what its "return value" will be and what side effect, if any, it will have on the environment at the time it is called. No facility was provided in the language to deal with exceptional conditions. Programmers usually used the "return value" to flag error conditions, which are expected to be "handled" by the caller, usually propagating the condition up the stack until something can be done about it.

C++ introduced (let's assume for simplicity) into the mainstream programmer's consciousness the notion that a function might "throw" an "exception" rather than return a value. The "throw" construct indicated that the function's computation was being terminated and it was not going to return to its caller as expected to do so under normal conditions. Instead, it was going to bounce around into "catch" blocks higher and higher in the stack of calls until one block can "handle" the exception - with the hope that it will somehow be able to recover from it. The stack would be unwound until such a willing handler is found or the program is terminated.

Java dutifully formalized this mechanism and declared that a function's signature is not only about its arguments and the return value, but is also about the exceptions that it can throw to its caller.

To summarize, a function can either return to its caller normally, or throw an exception. In both Java and C++, once a function terminates either by returning or by throwing an exception, all information about the intermediate state of the computation that was being performed when the exceptional condition occurred is destroyed. C++'s stack unwinding mechanism calls object destructors along the way and Java, since it doesn't keep objects on the stack, provides a finally block where you can do your own cleanup before the exception is passed to the caller.

Hmmm ... now, what can you really do in a catch block in C++ or Java? It is a good exercise to scour the net for C++ and Java code examples that purport to teach the reader the exception mechanism in these languages. I mean do that right now. See if you come up with anything more sophisticated than -

try {
// Call function which throws exception ..
int hex = 355, spell = 113;
abracadabra( hex/(double)spell, "hocus pocus" );
} catch ( IncantationFailedException &ex ) {
// Handle the exception
}

or maybe

try {
...
} catch ( IncantationFailedException &ex ) {
std::cerr << "Incantation 'abracadabra' didn't work!\n";
}


Even in Herb Sutter's insight filled book Exceptional C++, the pages 25 to 68 on Exception Safety : Issues and Techniques fail to cite a single useful case for the catch clause, let alone recommend typical usage. Though it can be argued that the chapter is on exception safety and not exception handling, all I'm asking for is one good example. If you do find a good case or description of "recommended practice" anywhere, I'd appreciate links as comments to this post.

Is it then a consequence of instructional failure that decent bodies of C++ code hardly ever use the catch mechanism and only use the destructor calls that happen when the stack unwinds? I dare a conjecture here -
The reason catch blocks that do something more substantial than code like the above are hard to find is that the catch mechanism is impotent and consequently nearly useless as a design tool.

The Common Lisp folks have provided a more flexible definition of a function - that its not just about the arguments and the "return value", but can be an arbitrary protocol in which control can pass between the caller and a particular invocation multiple times before the task is done. Peter Seibel talks in detail about the Common Lisp conditions facility in Beyond Exception Handling : Conditions and Restarts and provides a concrete example where a function is useful in more than one context because it exposes such a protocol to its caller.

Having experienced deep frustration on several occasions along the lines of - "if only this function would let me customize the way it treats this condition differently, I won't have to write my own", I realized that the exception mechanism was standing in the way of a significant amount of code reusability. Library functions were deciding too much for me. It is cumbersome to keep passing protocol handlers via arguments for every function, so nobody does that either.

I use muSE as a platform for experimenting with the language features I'd like to have, so I decided to try something more flexible than C++/Java and less cumbersome to use than CL - a mechanism with which you can ignore exceptions when you call a function or go all out to customize the way a function should behave - the former case not requiring any additional work on your part, as it should be. Along the way, I figured that pattern matching dispatch works very well to achieve this goal and makes the handling of exceptions and restarts much simpler than the Common Lisp approach.

Syntactically, the muSE approach is similar to C++/Java - an expression that might not evaluate successfully is wrapped into a try expression and a set of handlers are provided to try when the expression failed to evaluate normally. In order to signal an abnormal condition, a function uses the raise function, supplying it an arbitrary set of arguments that get passed on to the handlers. Here's a simple, though contrived, example (*)-

(define H (fn (x y)
(try (/ (+ x y)
(if (= x y)
(raise 'DivideByZeroDiff x y)
(- x y)))
(fn ('ForceResult r) r))))

The above function tries to compute the value (x + y)/(x - y). It can't perform the division if x = y, so it raises a 'DivideByZeroDiff condition in that case. It also provides a code path using which you can force the result of the function to be some arbitrary value in case nothing else can be done about it. Here's a caller trying to compute something using this function (*) -

(define G (fn (x1 y1 x2 y2)
(try (/ (H x1 y1) (H x2 y2))
(fn (ex 'DivideByZeroDiff x y)
(if (= (- x1 y1) (- x2 y2))
(ex 1)
(retry 'ForceResult 2000))))))

G is trying to compute H(x1,y1)/H(x2,y2). If one of the differences (x1-y1) and (x2-y2) becomes small, H will raise the 'DivideByZeroDiff exception. Now, G knows that if the two differences are equal, the actual value of the difference doesn't matter to the end result. The H function cannot possibly know this. So in this case, G simply asks H to assume a value of 1 instead of the (raise ...) expression and continue to compute the result. If this is not the case, G decides to cap the value of H to 2000 using the 'ForceResult code path.

Note that H embodies no knowledge about the domain that G knows about. This makes it possible to use H in situations where the original author cannot possibly know how to handle the divide by zero case. The computations performed by this example are trivial, but when they are substantial, the facility to interact with the execution flow of a function definitely renders it more useful in a broader set of contexts and therefore useful as a design tool. Peter Seibel's more substantial log file parsing example can be expressed using the try-raise-retry constructs too.

We've used pattern matching throughout to catch raised conditions and to pass on control to restart points. The Common Lisp approach has equivalent functionality, but to me appears more verbose than really needs to be. In muSE, a function's interface consists of a hierarchical set of patterns -
  1. The argument pattern which must match in order for the function's computation to even start,

  2. a tree of exception patterns, handlers and restart patterns,

  3. the result value and

  4. side effects if any.


All of them are available as design tools for a function creator so that she can create code that can be used in a variety of contexts not all of which can be known at design time.

An analogy


It is interesting to compare the hierarchy of function dependencies to a company's employee hierarchy. It is often the case that at every designation, an employee has access to some information that employees at lower designations don't. This information lets them make decisions in situations where those working under them don't know enough to make the decision themselves.

Imagine a company P that works like this - a task is handed down to the top-level employee who further describes it and hands subtasks down to those working under her. The company philosophy is that whenever an employee reaches a point at which she can't take a decision, she promptly destroys all the work she's done starting from the time her task was handed to her. Then she informs her boss that such a situation has happened. Her boss is very happy with her capacity to cleanup her work without leaving a trace, destroys all the work she has done since her task was handed down, files a report in the company activity database and talks to her boss about what happened, in her own language.

I'm sure you don't work in a company like that! Now bump up the scale and imagine if Boeing worked like that!

What usually happens is that the lower level employee suspends her work and immediately talks to her boss, presenting her with a few options. Her boss, based on what she understands, instructs her to proceed in a particular direction. The result is that the task gets done. All the way up the hierarchy.

We'll not want to work in the pathological company P, but we all choose to live with and even praise programming languages that work exactly like that.

Appendix


(*) In muSE, it is slightly more efficient to write the G and H functions as -

(define H (fn (x y)
(try (/ (+ x y)
(if (= x y)
(raise 'DivideByZeroDiff x y)
(- x y)))
{fn: ('ForceResult r) r})))


(define G (fn (x1 y1 x2 y2)
(try (/ (H x1 y1) (H x2 y2))
{fn: (ex 'DivideByZeroDiff x y)
(if (= (- x1 y1) (- x2 y2))
(ex 1)
(retry 'ForceResult 2000))})))

Tuesday, March 13, 2007

An Objective Scheme

Here's a first cut at an Objective-C bridge for muSE on MacOSX. There cannot be an easier way to run native code from muSE!

Wednesday, February 14, 2007

Performance

When you know your code is correct and you don't get any of the diagnostics boxes popping up under Windows or stderr dumps on unices, you may be able to get up to twice the performance of your existing code by turning off the internal diagnostic checks.

To turn of diagnostics, edit the src/muse_config.h file, set the MUSE_DIAGNOSTICS_LEVEL to 0 and recompile muSE.

If performance with diagnostics on is acceptable, you should consider leaving the checks in place.

Friday, February 02, 2007

Subversion perversion

As I'd mentioned earlier, Google code has added a Wiki feature supposed to be useful to maintain documentation. We in the programming world know that once you document stuff in a place other than the original source code, it rapidly goes out of sync. So I tried another way to present the generated muSE documentation right where the source code is available - the Subversion repo itself.

I simply generated the HTML documentation using doxygen and placed the output into its own Subversion module/folder - here. Once all the files are tagged with the appropriate mime types, the whole site is functional .. and versioned to boot!

Here are the mime-type setting commands you need to use in the doxygen generated documentation folder -


svn propset svn:mime-type "text/html" *.html
svn propset svn:mime-type "image/png" *.png
svn propset svn:mime-type "text/css" *.css
svn propset svn:mime-type "image/gif" *.gif
svn propset svn:mime-type "text/plain" *.md5
svn propset svn:mime-type "text/plain" *.map

Friday, January 19, 2007

A simple module system using macros

In this post, I describe how to build a simple module system. For simplicity, I assume we won't be providing support for mutually recursive functions within a module.

You can think of a module as a set of symbols and the values they must be bound to. These bindings are made current whenever you "enter" a module's scope and they vanish whenever you exit the scope. Here's what we hope to achieve, with a statistics module as an example -


(define Statistics
(module
(mean
(fn args (/ (apply + args) (length args))))

(variance
(fn args (- (apply mean (map (fn (x) (* x x)) args))
(let ((mx (apply mean args)))
(* mx mx)))))
))

The above module provides definitions for mean and variance.

Notice that the body of the module definition is very similar to the bindings section of the let construct. We can therefore use the let construct in a macro system to implement module as a macro.

It is simple to use the Statistics module like this -
{Statistics (mean 1 2 3)}
. For that to be possible, Statistics must itself be a macro. Therefore, module must be a macro that generates a macro. Here is a definition for module -

(define module
(fn 'bindings
(let ((evaluated-bindings
(eval (list 'let bindings
(list 'map
(fn: ((s . _))
(list s (cons 'quote (eval s))))
(cons 'quote bindings))))))
(fn 'args
(cons 'let (cons evaluated-bindings args))))))

Now all you have to do to rope in the Statistics definitions in a block of code is to wrap it up in a {Statistics ...} block.

Suppose we wish to use the module name to resolve the value of a single symbol. It is potentially expensive to do ({Statistics mean} 1 2 3) because the brace expression places a (potentially huge) let block there which gets evaluated every time the whole expression is evaluated. We can make this case more efficient by defining a scope resolution operator like this -

(define :: (fn 'args (cons quote (eval (eval args)))))

Then we can resolve the meaning of mean like this
({:: Statistics mean} 1 2 3)
. The resolution will happen once at read time, after which the actual function will be used.

See examples/modules.scm.

Thursday, January 18, 2007

"Processes" branch is now the "trunk".

The experimental multi-processing support that was being developed in the processes branch is now merged on to the trunk (v140). That is, its official! I've been waiting for this for a long time now. Apart from various bug fixes and other process related stuff already described, here's whats new -

  1. New primitives post and process?.
  2. New s-expr syntax and functions to work with raw byte data.
  3. MAJOR code change to make muSE truly embeddable. This change breaks API compatibility. The new API calls all take the environment pointer as their first arguments so that multiple muSE environments can coexist in the same OS process or thread without interfering with each other.

Wednesday, January 10, 2007

Specifying function arguments using key-value pairs

Since v129 in the processes branch, two new primitives call/keywords and apply/keywords have been added to let function argument be specified in an order that's different from the declaration order. These primitives do not change the evaluation complexity of functions at all, but simply expose a function's declared arguments as "keywords".


(call/keywords f 'key1 val1 'key2 val2 ...)

Evaluates the given function by explicitly binding each named argument to the given value. Can also be used on macros, in which case the value positions are used without being evaluated. This is the counterpart of the normal position based evaluation.

(apply/keywords f alist)

If you store a set of argument bindings in an alist, you can apply a function to those bindings using apply/keywords. This is the counterpart of the position-based apply.


Any arguments that don't feature in the supplied key-value pairs take on their current value in the environment. This lets you specify common arguments using just a let binding or a global define. Such common arguments are therefore said to be implemented via a limited form of dynamic scoping.

Wednesday, December 27, 2006

Creating stand-alone executables

The muSE interpreter binary can now create extensible stand-alone command line executables. For details, check out the wiki page StandAloneExecutables.

Monday, December 25, 2006

Simple run-time type checking in muSE

The guarded patterns facility can be used to implement a simple type checking facility that can be turned on and off with a global setting. Some functions useful in this situation are defined in examples/rtts1.scm.

muSE has some built-in functions to check types - int?, float?, cons?, vector?, hashtable?, text? and symbol?. The common characteristic of these predicates is to evaluate to their argument if it satisfies the predicate and to () if it doesn't.

If you take a type to be defined by a predicate - the class of objects being all objects that satisfy the predicate - the above predicates together with the combinators ?or, ?and, ?not and ?list-of defined in examples/rtts1.scm can express a broad set of types.

Note: The type checks are all performed at function invocation time. This can be quite a drag on performance. So the definition of decltype in the rtts1.scm file is such that the checks can be turned off by setting the *enable-rtts* to () at the start of the file.


Here's an example (note the use of braces {} around the decltype expressions) -

(define number? (?or int? float?)) ; From rtts1.scm

(define max
(fn ({decltype n1 number?}
{decltype n2 number?})
(if (> n1 n2) n2 n1)))

(define multi-max
(fn ({decltype n1 number?} . {decltype ns (?list-of number?)})
(reduce max n1 ns)))

With type checking enabled, multi-max will only be usable with numeric arguments and you have to give it at least one argument.

If you disable type checking, then the above definitions become as though you'd typed -

(define max
(fn (n1 n2)
(if (> n1 n2) n2 n1)))

(define multi-max
(fn (n1 . ns)
(reduce max n1 ns)))