Functions with side-effects?

Hi all, I'm a Haskell newbie and I don't really understand how Haskell deals with functions that really must have side-effects. Like a rand() function or getLine(). I know this has something to do with monads, but I don't really understand monads yet. Is there someone who might explain this in newbie terms? I don't need to understand the whole thing, I don't need a rand() function right this minute. I just want to understand how Haskell separates purely functional code from non-functional code (I understand that a rand() function is inevitably not functional code, right?) Cheers, Daniel. -- /\/`) http://oooauthors.org /\/_/ http://opendocumentfellowship.org /\/_/ \/_/ I am not over-weight, I am under-tall. /

On Wed, 21 Dec 2005, Daniel Carrera wrote:
Hi all,
I'm a Haskell newbie and I don't really understand how Haskell deals with functions that really must have side-effects. Like a rand() function or getLine().
I know this has something to do with monads, but I don't really understand monads yet. Is there someone who might explain this in newbie terms? I don't need to understand the whole thing, I don't need a rand() function right this minute. I just want to understand how Haskell separates purely functional code from non-functional code (I understand that a rand() function is inevitably not functional code, right?)
Well, it's *all* functional fundamentally but when people say "non-functional" in relation to Haskell code what they mean is in a monad. Monads, I believe, can be just thought of as containers for state. So like I said in an e-mail on the tutorial thread the only distinction you need to make is between "=" which is truly a statement of definition and not subject to change, and "<-" which says that the left hand side is the result of the action of the right hand side. In terms of types, "<-" strips away the monadic type. Note for example that if you want your main program to take in a string and then print it to the screen, it is *NOT* main = putStrLn . getLine (which was I first thought) or main = putStrLn (getLine) (which you might guess from the type sig) but rather main = do x <- getLine putStrLn x Because you want putStrLn to act on the string that *resulted* from getLine, and not on getLine itself. I'd check out this link from HaWiki on monads, http://haskell.org/hawiki/MonadsAsContainers

Am Mittwoch, 21. Dezember 2005 13:15 schrieb Creighton Hogg:
[...]
Monads, I believe, can be just thought of as containers for state.
I would say that you are talking especially about the I/O monad here. A monad as such is a rather general concept like a group is in algebra. The important point of the integration of imperative programming in Haskell is not that it's done using monads. The point is that you have a specific type (IO) whose values are descriptions of I/O actions, and some primitive operations on IO values. The IO type together with two of these primitive operations forms a monad but this is secondary in my opinion.
[...]
Best wishes, Wolfgang

Wolfgang Jeltsch writes: ----- Original Message -----
Am Mittwoch, 21. Dezember 2005 13:15 schrieb Creighton Hogg:
[...]
Monads, I believe, can be just thought of as containers for state.
I would say that you are talking especially about the I/O monad here. A monad as such is a rather general concept like a group is in algebra.
While this is correct, I'm afraid that for most of us it is a flavorless answer. I wish I had the mathematical mind that made the word "group" in this context instantly intuitively recognizable, but I don't. I think Phil Wadler said it best when he said that a monad is a *computation*. If a function is a mapping between input and output values, a computation is both an invocation of a function and the provision of values --- which can include state, ordering, and many other things. Of course, I'm a Phil Wadler fan anyway.
The important point of the integration of imperative programming in Haskell is not that it's done using monads. The point is that you have a specific type (IO) whose values are descriptions of I/O actions, and some primitive operations on IO values. The IO type together with two of these primitive operations forms a monad but this is secondary in my opinion.
Yes and no. It is important for me, at least, to continue to grasp that IO is just not a functional thing --- it is not captured intuitively in a function. Rather, it is a computation --- IO doesn't make sense until it executes in an environment which it can effect. This is why we capture IO (as well as other computational concepts) in monads, and why (again IMHO) mondadic IO is so much more effective and intuitive than continuation style IO or stream based IO ever was. Dave Barton

Am Mittwoch, 21. Dezember 2005 16:28 schrieb David Barton:
Wolfgang Jeltsch writes: ----- Original Message -----
Am Mittwoch, 21. Dezember 2005 13:15 schrieb Creighton Hogg:
[...]
Monads, I believe, can be just thought of as containers for state.
I would say that you are talking especially about the I/O monad here. A monad as such is a rather general concept like a group is in algebra.
While this is correct, I'm afraid that for most of us it is a flavorless answer. I wish I had the mathematical mind that made the word "group" in this context instantly intuitively recognizable, but I don't.
The point is that the concept of a monad is a rather abstract one. Maybe we should take vector spaces instead of groups. A vector space is just a set together with two operations, namely vector addition and scalar multiplication whereby the operations have to fulfill a couple of laws. One law, for example, would be that vector addition is commutative. The point is that this vector space concept is very general. There are a lot of concrete vector spaces which all fit that general idea. For example, R^2 together with the commonly defined vector addition and scalar multiplication is a vector space. But there are many others, lots of which seem to have nothing to do with R^2 and its operations from the first sight. So we have a general concept "vector space" and a lot of concrete vector spaces. Now, "monad" is a general concept like "vector space". In Haskell terms a monad is a type of kind * -> * (i.e., a type which needs to be applied to one type parameter in order to be able to be used as a type of expressions) together with operations (>>=) and return whereby (>>=) and return have to have specific types and have to fulfill certain laws. IO together with its (>>=) and return is an example of a concrete monad. [] together with concatMap and \x -> [x] is another one. This may seem very surprising at first ("What do lists have to do with I/O computations?") but it's really so.
I think Phil Wadler said it best when he said that a monad is a *computation*.
No, a value of type IO <something> is a computation. The type IO plus (>>=) plus return is a monad. So is [] plus concatMap plus \x -> [x].
[...]
Dave Barton
Best wishes, Wolfgang

--- Wolfgang Jeltsch
I think Phil Wadler said it best when he said that a monad is a *computation*.
To be honest, I'm still struggling with the monad concept myself. Oh sure, I can read the definition and it makes sense. But I'm still missing that "aha!" moment when it all comes together.
No, a value of type IO <something> is a computation. The type IO plus (>>=) plus return is a monad. So is [] plus concatMap plus \x -> [x].
This may be totally off-base, but when I read this, it occured to me that all I/O is basically a computation made to appear as if it is something your program "does". You (or, rather the processor) don't execute instructions to write "Hello" in same way as, say, adding 2 and 2. Rather, you add writing this string to a "to do" list and wait for a driver to respond to an interrupt, pick up the request(s), and carry it (them) out when control passes back the kernel.
===
Gregory Woodhouse

Am Mittwoch, 21. Dezember 2005 19:02 schrieben Sie:
[...]
You (or, rather the processor) don't execute instructions to write "Hello" in same way as, say, adding 2 and 2.
Exactly!
Rather, you add writing this string to a "to do" list and wait for a driver to respond to an interrupt, pick up the request(s), and carry it (them) out when control passes back the kernel.
I don't completely understand what you mean but I think it goes into the right direction. We can illustrate the process of executing a Haskell program as follows. We have a "evaluation machine" and a "execution machine". The former evaluates expressions, the latter executes I/O actions. When the program is started, the execution machine wants to execute the actions from the "main to do list". In order to be able to do so, it asks the evaluation machine to start evaluating main. For each entry on the to do list, main is evaluated as far as it's necessary to provide the execution machine with the entry. Of course, evaluating main may result in evaluating other expressions. The point is that the evaluation machine does never execute any I/O actions while the execution machine is unable to do any evaluation but needs to be feeded by the evaluation machine. Best wishes, Wolfgang

On 21/12/05, Daniel Carrera
Hi all,
I'm a Haskell newbie and I don't really understand how Haskell deals with functions that really must have side-effects. Like a rand() function or getLine().
I know this has something to do with monads, but I don't really understand monads yet. Is there someone who might explain this in newbie terms? I don't need to understand the whole thing, I don't need a rand() function right this minute. I just want to understand how Haskell separates purely functional code from non-functional code (I understand that a rand() function is inevitably not functional code, right?)
Cheers, Daniel.
Haskell separates pure functions from computations where side effects must be considered by encoding those side effects as values of a particular type. Specifically, a value of type (IO a) is an action, which if executed would produce a value of type 'a'. Some examples: getLine :: IO String putStrLn :: String -> IO () -- note that the result value is an empty tuple. randomRIO :: (Random a) => (a,a) -> IO a Ordinary Haskell evaluation doesn't cause this execution to occur. A value of type (IO a) is almost completely inert. In fact, the only IO action which can really be said to run in a compiled Haskell program is main. Now, so far, all this is great, but without a way to chain actions together end-to-end, we can't do a whole lot. So Haskell provides us with a few primitives for composing and chaining together IO actions. A simple one of these is: (>>) :: IO a -> IO b -> IO b where if x and y are IO actions, then (x >> y) is the action that performs x, dropping the result, then performs y and returns its result. Great, we can now write programs which do multiple things: main = putStrLn "Hello" >> putStrLn "World" will print "Hello" and "World" on separate lines. However, we don't yet have a way to chain actions in which we are allowed to use the result of the first in order to affect what the second action will do. This is accomplished by the following operation, called 'bind': (>>=) :: IO a -> (a -> IO b) -> IO b Now, if x is an IO action with a result of type 'a', and f is a function from values of type 'a' to actions with a result of type 'b', then x >>= f is the action that performs x, and captures its result, passing it to f, which then computes a second action to be performed. That action is then carried out, and its result is the result of the overall computation. That's a mouthful, but once you see it in use, perhaps the idea will become clearer: main = putStrLn "Hello, what is your name?" >> getLine >>= \name -> putStrLn ("Hello, " ++ name ++ "!") This is most of what we need. In fact, this bind function is really successful, and we can define (>>) in terms of it: x >> y = x >>= const y In practice, it turns out to also be quite important to turn a value into an IO action which does nothing, and simply returns that value. This is quite handy near the end of a chain of actions, where we want to decide what to return ourselves, rather than leaving it up to the last action in the chain. So there's one more primitive, return :: a -> IO a which does just that. Note that there is no function: unsafe :: IO a -> a as this would defeat the referential transparency of Haskell -- applying 'unsafe' to the same IO action might return different things every time, and Haskell functions aren't allowed to behave that way. Now, I haven't really told you anything about monads in general yet. Most monads are actually rather unlike IO, but they do share the similar concepts of bind and return. For monads in general, see my tutorial MonadsAsContainers on the wiki :) Hope this helps - Cale

On 21/12/05, Cale Gibbard
On 21/12/05, Daniel Carrera
wrote: Hi all,
I'm a Haskell newbie and I don't really understand how Haskell deals with functions that really must have side-effects. Like a rand() function or getLine().
I know this has something to do with monads, but I don't really understand monads yet. Is there someone who might explain this in newbie terms? I don't need to understand the whole thing, I don't need a rand() function right this minute. I just want to understand how Haskell separates purely functional code from non-functional code (I understand that a rand() function is inevitably not functional code, right?)
Cheers, Daniel.
Haskell separates pure functions from computations where side effects must be considered by encoding those side effects as values of a particular type. Specifically, a value of type (IO a) is an action, which if executed would produce a value of type 'a'.
Some examples: getLine :: IO String putStrLn :: String -> IO () -- note that the result value is an empty tuple. randomRIO :: (Random a) => (a,a) -> IO a
Ordinary Haskell evaluation doesn't cause this execution to occur. A value of type (IO a) is almost completely inert. In fact, the only IO action which can really be said to run in a compiled Haskell program is main.
Now, so far, all this is great, but without a way to chain actions together end-to-end, we can't do a whole lot. So Haskell provides us with a few primitives for composing and chaining together IO actions. A simple one of these is: (>>) :: IO a -> IO b -> IO b where if x and y are IO actions, then (x >> y) is the action that performs x, dropping the result, then performs y and returns its result. Great, we can now write programs which do multiple things: main = putStrLn "Hello" >> putStrLn "World" will print "Hello" and "World" on separate lines.
However, we don't yet have a way to chain actions in which we are allowed to use the result of the first in order to affect what the second action will do. This is accomplished by the following operation, called 'bind': (>>=) :: IO a -> (a -> IO b) -> IO b Now, if x is an IO action with a result of type 'a', and f is a function from values of type 'a' to actions with a result of type 'b', then x >>= f is the action that performs x, and captures its result, passing it to f, which then computes a second action to be performed. That action is then carried out, and its result is the result of the overall computation.
That's a mouthful, but once you see it in use, perhaps the idea will become clearer: main = putStrLn "Hello, what is your name?" >> getLine >>= \name -> putStrLn ("Hello, " ++ name ++ "!")
This is most of what we need. In fact, this bind function is really successful, and we can define (>>) in terms of it: x >> y = x >>= const y
In practice, it turns out to also be quite important to turn a value into an IO action which does nothing, and simply returns that value. This is quite handy near the end of a chain of actions, where we want to decide what to return ourselves, rather than leaving it up to the last action in the chain. So there's one more primitive, return :: a -> IO a which does just that.
Note that there is no function: unsafe :: IO a -> a as this would defeat the referential transparency of Haskell -- applying 'unsafe' to the same IO action might return different things every time, and Haskell functions aren't allowed to behave that way.
Now, I haven't really told you anything about monads in general yet. Most monads are actually rather unlike IO, but they do share the similar concepts of bind and return. For monads in general, see my tutorial MonadsAsContainers on the wiki :)
Hope this helps - Cale
Oh, just sent that, but there was one more point I wanted to make. You might see do-notation all over the place in real Haskell programs. In do-notation, our example program might look like: main = do putStrLn "Hello, what is your name?" name <- getLine putStrLn ("Hello, " ++ name ++ "!") This is in fact entirely equivalent to the above form, and is translated into it by the Haskell compiler. So whenever you see a do block, you can just imagine a chain of applications of (>>) and (>>=), and some lambdas when appropriate to capture the results of actions. - Cale

Thanks for all the help. I think things are much clearer now. And this bit:
main = do putStrLn "Hello, what is your name?" name <- getLine putStrLn ("Hello, " ++ name ++ "!")
Looks quite straight forward. I just wrote my very first IO program with Haskell: --//-- main = do x <- getLine putStrLn( show(length x) ) --//-- That's not so scary. The next thing I need to figure out is how to act on the input data itself (not just its length). For example, if I wanted a program to output the nth element of the fibonacci sequence: --//-- $ ./fib 12 144 --//-- My first inclination would be to write it as: --//-- main = do x <- getLine putStrLn( show(fib x) ) --//-- Of course that won't work because x is an IO String and 'fib' wants an Int. To it looks like I need to do a conversion "IO a -> b" but from what Cale said, that's not possible because it would defeat the referential transparency of Haskell. Hmm... there must be a way to solve this problem though... In any event, thanks for the help. I'm learning :) Cheers, Daniel. -- /\/`) http://oooauthors.org /\/_/ http://opendocumentfellowship.org /\/_/ \/_/ I am not over-weight, I am under-tall. /

On 21/12/05, Daniel Carrera
Thanks for all the help. I think things are much clearer now. And this bit:
main = do putStrLn "Hello, what is your name?" name <- getLine putStrLn ("Hello, " ++ name ++ "!")
Looks quite straight forward.
I just wrote my very first IO program with Haskell: --//-- main = do x <- getLine putStrLn( show(length x) ) --//--
That's not so scary. The next thing I need to figure out is how to act on the input data itself (not just its length). For example, if I wanted a program to output the nth element of the fibonacci sequence: --//-- $ ./fib 12 144 --//--
My first inclination would be to write it as: --//-- main = do x <- getLine putStrLn( show(fib x) ) --//--
Of course that won't work because x is an IO String and 'fib' wants an Int. To it looks like I need to do a conversion "IO a -> b" but from what Cale said, that's not possible because it would defeat the referential transparency of Haskell.
Actually, you're closer than you think here. x is actually of type String! The problem is just that fib wants a number, so you need to read the string: main = do x <- getLine putStrLn (show (fib (read x))) There's a handy name for the composite (putStrLn . show) called 'print', so you can write: main = do x <- getLine print (fib (read x))) Cheers! - Cale

On Wed, 21 Dec 2005, Daniel Carrera wrote:
Thanks for all the help. I think things are much clearer now. And this bit:
main = do putStrLn "Hello, what is your name?" name <- getLine putStrLn ("Hello, " ++ name ++ "!")
Looks quite straight forward.
I just wrote my very first IO program with Haskell: --//-- main = do x <- getLine putStrLn( show(length x) ) --//--
That's not so scary. The next thing I need to figure out is how to act on the input data itself (not just its length). For example, if I wanted a program to output the nth element of the fibonacci sequence: --//-- $ ./fib 12 144 --//--
My first inclination would be to write it as: --//-- main = do x <- getLine putStrLn( show(fib x) ) --//--
Of course that won't work because x is an IO String and 'fib' wants an Int. To it looks like I need to do a conversion "IO a -> b" but from what Cale said, that's not possible because it would defeat the referential transparency of Haskell.
x is a String, getLine has type IO String. That's what I was getting at in one of my last e-mails. So you just need something that can read in a string and convert it to an int. Something like let y = (read x) putStrLn $ show $ fib y should work, yes?

Creighton Hogg wrote:
x is a String, getLine has type IO String. That's what I was getting at in one of my last e-mails.
Hmm... let's see if I understand: * getLine() has type IO String. * The <- will "convert" an IO String to a plain String * So if I do x <- getLine() then x has the type String. So, the <- effectively ammounts to an IO a -> a conversion. In another email John Hughes said that one could think of "IO a" as a set of instructins for obtaining a. I guess that means that IO is a sort of imperative layer that helps the purely functional code interact with the outside world. So I can have an IO bit (e.g. a do-block) that calls functions (which are purely functional code) but I can't have a function that executes any IO. For example, it is not possible to write a function "my_read_file" that could work like this: my_data = my_read_file("my_file.txt") Correct? Otherwise this would be a function that is not referentially transparent. Cheers, Daniel. -- /\/`) http://oooauthors.org /\/_/ http://opendocumentfellowship.org /\/_/ \/_/ I am not over-weight, I am under-tall. /

On Wed, 21 Dec 2005, Daniel Carrera wrote:
Creighton Hogg wrote:
x is a String, getLine has type IO String. That's what I was getting at in one of my last e-mails.
Hmm... let's see if I understand:
* getLine() has type IO String. * The <- will "convert" an IO String to a plain String * So if I do x <- getLine() then x has the type String.
So, the <- effectively ammounts to an IO a -> a conversion.
Yeah, pretty much. You're saying that "x is the result of action getLine", and in terms of types it means you're getting the a out of m a.
In another email John Hughes said that one could think of "IO a" as a set of instructins for obtaining a. I guess that means that IO is a sort of imperative layer that helps the purely functional code interact with the outside world.
So I can have an IO bit (e.g. a do-block) that calls functions (which are purely functional code) but I can't have a function that executes any IO.
For example, it is not possible to write a function "my_read_file" that could work like this:
my_data = my_read_file("my_file.txt")
Correct? Otherwise this would be a function that is not referentially transparent.
Assuming I understand correctly, then you are right. "=" implies definition. my_data is the symbol assigned to the result of a computation, not a defined constant or function. That's at least how I think of referential transparency.

Hello Daniel, Wednesday, December 21, 2005, 6:24:29 PM, you wrote: DC> So I can have an IO bit (e.g. a do-block) that calls functions (which DC> are purely functional code) but I can't have a function that executes DC> any IO. it's true DC> For example, it is not possible to write a function "my_read_file" that DC> could work like this: DC> my_data = my_read_file("my_file.txt") DC> Correct? Otherwise this would be a function that is not referentially DC> transparent. you are right. type "IO a", after all, stands for "RealWorld -> (a,RealWorld)", i.e. it gets RealWorld as parameter and returns, besides value of type "a", a new RealWorld state. the function which type don't ended with "IO a", just can't receive or return value of type RealWorld, so there is just no way to check something outside or return new state of the outer world -- Best regards, Bulat mailto:bulatz@HotPOP.com

On Wed, Dec 21, 2005 at 11:43:42AM +0000, Daniel Carrera wrote:
Hi all,
I'm a Haskell newbie and I don't really understand how Haskell deals with functions that really must have side-effects. Like a rand() function or getLine().
I know this has something to do with monads, but I don't really understand monads yet. Is there someone who might explain this in newbie terms? I don't need to understand the whole thing, I don't need a rand() function right this minute. I just want to understand how Haskell separates purely functional code from non-functional code (I understand that a rand() function is inevitably not functional code, right?)
What everyone said about monads is great, but I'd just like to add that for rand() you don't strictly need a monad. With a bit of care, you could write rand() to be of type rand :: Int -> [Int] where it accepts a seed value, and returns an infinite list of pseudorandom numbers. In one sense we haven't gone non-monadic here, since there is actually an instance of monad for [a], but you needn't actually write monadic code. The catch with this rand is that when you go to actually use the output, you need to thread this list of random numbers through your code, and need to be sure never to use the same random number twice. Which is why you'd really prefer to use a monad that could force you to obey the constraints of only using each random number once. For some uses, though, the above rand will be great, as long as you're using it for list manipulations, such as xoring a list of numbers with random numbers as a primitive form of encryption (or a strong form of encryption, if you use a strong random number generator). -- David Roundy http://www.darcs.net

Daniel Carrera wrote:
I'm a Haskell newbie and I don't really understand how Haskell deals with functions that really must have side-effects. Like a rand() function or getLine().
Those aren't functions.
A function is a single-valued relation, i.e. a (possibly infinite) set
of ordered pairs
I know this has something to do with monads, but I don't really understand monads yet. Is there someone who might explain this in newbie terms? I don't need to understand the whole thing, I don't need a rand() function right this minute. I just want to understand how Haskell separates purely functional code from non-functional code (I understand that a rand() function is inevitably not functional code, right?)
All Haskell code is functional (discounting certain low-level details
such as unsafePerformIO).
Side effects are implemented by making the prior state an argument and
the new state a component of the result, i.e. a C procedure of type:
res_t foo(arg_t);
becomes a Haskell function with type:
ArgType -> State -> (State, ResType)
To simplify coding (particularly, making sure that you use the correct
iteration of the state at any given point), all of this is usually
wrapped up in an instance of the Monad class. But there isn't anything
special about Monad instances. The class itself and many of its
instances are written in standard Haskell.
To provide a concrete example, here's a monadic random number
generator:
type Seed = Int
data Rand a = R { app :: Seed -> (Seed, a) }
myRand :: Rand Int
myRand = R $ \seed -> let
result = (seed' `div` 65536) `mod` 32768
seed' = seed * 1103515245 + 12345
in (seed', result)
instance Monad Rand where
f >>= g = R $ \seed -> let (seed', x) = app f seed
in app (g x) seed'
return x = R $ \seed -> (seed, x)
runR :: Seed -> Rand a -> a
runR seed f = snd $ app f seed
Example usage:
randomPair :: Rand (Int, Int)
randomPair = do
myRand >>= \x ->
myRand >>= \y ->
return (x, y)
or, using "do" notation (which is simply syntactic sugar):
randomPair :: Rand (Int, Int)
randomPair = do
x <- myRand
y <- myRand
return (x, y)
main = print $ runR 99 randomPair
The main difference between the built-in IO monad and the Rand monad
above is that where the Rand monad has a Seed for its state, the IO
monad has the (conceptual) World type.
As the World type has to represent the entire observable state of the
universe, you can't actually obtain instances of it within a Haskell
program, and thus there is no equivalent to runR.
Instead, you provide an IO instance (main) to the runtime, which
(conceptually) applies it to the World value representing the state of
the universe at program start, and updates the universe to match the
World value returned from main at program end.
--
Glynn Clements

On Wed, Dec 21, 2005 at 11:43:42AM +0000, Daniel Carrera wrote:
Hi all,
I'm a Haskell newbie and I don't really understand how Haskell deals with functions that really must have side-effects. Like a rand() function or getLine().
I know this has something to do with monads, but I don't really understand monads yet. Is there someone who might explain this in newbie terms? I don't need to understand the whole thing, I don't need a rand() function right this minute. I just want to understand how Haskell separates purely functional code from non-functional code (I understand that a rand() function is inevitably not functional code, right?)
You said you were a mathematician, so let me give you the mathematical version in case you like category theory: A monad is an monoid object in the category of endofunctors. That is, a monad is an endofunctor 'F' (i.e., a data type depending on a single parameter, which is furthermore an instance of Functor), with a natural transformations from 'F (F a)' to 'F a' and from 'a' to 'F a', satisfying the usual rules for identity and associativity. Exercise: List is a monad. So is Maybe. You can think about a monad 'F a' as a computation of an object of type 'a' with side effects; for instance, the List monad allows the side-effect of multiple answers (like non-determinism), and the Maybe monad allows the side-effect of failure. One particularly useful and complicated monad is the IO monad, which allows interactions with the outside world. Peace, Dylan
participants (10)
-
Bulat Ziganshin
-
Cale Gibbard
-
Creighton Hogg
-
Daniel Carrera
-
David Barton
-
David Roundy
-
Dylan Thurston
-
Glynn Clements
-
Greg Woodhouse
-
Wolfgang Jeltsch