Architecture for a clojure project

14 views
Skip to first unread message

Andrew Wagner

unread,
May 25, 2009, 9:17:27 PM5/25/09
to clo...@googlegroups.com
I'm trying to wrap my head around how to architect this project. I've got some functional programming experience (with Haskell), but am pretty new to Lisps, and feel a bit lost without the type system.

So. The project is a chess AI. Now the nice thing is, there's a protocol for interacting with a chess AI, and a UI built on top of that protocol. So one thing I know I want to do is write an implementation of that protocol, so that my AI can be interacted with using the UI. Great.

This is where it gets tricky for me. I very much want the ability to be able to try things out with different engines. So I want to be able to write something like:

(ns my.namespace (:use [winboard]))

(def engine ...)

(def main (run-winboard engine))

Seems straightforward enough. My difficulty though comes in trying to figure out how to write the winboard bit. I know how to do the IO stuff, that's pretty trivial. But, let's say I'm ready to ask the engine what move to make in a particular position. The engine itself should provide a function that takes a position and returns a move. But...and this is where my old OO mindset is probably kicking in...there's no way to do something like engine.getMove(position), and it does have "its own" functions.

There is only one way I can think of: engine is itself a function. When run, it returns a map. One key in the map is, e.g., :get-move. The value at that key is the desired function. But...this seems rather hackish. I'm sure there's some obvious clojure-ish/lisp-ish way of doing this, and it's just not coming to me. Any suggestions?

Konrad Hinsen

unread,
May 26, 2009, 12:24:31 AM5/26/09
to clo...@googlegroups.com
On 26.05.2009, at 03:17, Andrew Wagner wrote:

> Seems straightforward enough. My difficulty though comes in trying
> to figure out how to write the winboard bit. I know how to do the
> IO stuff, that's pretty trivial. But, let's say I'm ready to ask
> the engine what move to make in a particular position. The engine
> itself should provide a function that takes a position and returns
> a move. But...and this is where my old OO mindset is probably
> kicking in...there's no way to do something like engine.getMove
> (position), and it does have "its own" functions.
>
> There is only one way I can think of: engine is itself a function.
> When run, it returns a map. One key in the map is, e.g., :get-move.
> The value at that key is the desired function. But...this seems
> rather hackish. I'm sure there's some obvious clojure-ish/lisp-ish
> way of doing this, and it's just not coming to me. Any suggestions?

What else do you want your engine to do? If its only task is to
return a move for a given position, then just make it a function of
position:

(defn engine [position] ...)

Konrad.

Daniel Lyons

unread,
May 26, 2009, 1:23:45 AM5/26/09
to clo...@googlegroups.com

On May 25, 2009, at 7:17 PM, Andrew Wagner wrote:

Seems straightforward enough. My difficulty though comes in trying to figure out how to write the winboard bit. I know how to do the IO stuff, that's pretty trivial. But, let's say I'm ready to ask the engine what move to make in a particular position. The engine itself should provide a function that takes a position and returns a move. But...and this is where my old OO mindset is probably kicking in...there's no way to do something like engine.getMove(position), and it does have "its own" functions.

If that's the only function, just pass it that function. But I don't think anyone would fault you for just making a class if you have more than one. Certainly if it's what you're most comfortable with.

If you really wanted to do something functional you could close over your state and return a dispatch function. For a trivial counter it might look something like this:

(defn make-counter
  [init]
  (let [counter (ref init)]
    (fn [method & args]
      (condp = method
'count (dosync (alter counter inc))
'reset (dosync (ref-set  counter 0))
'set-to (dosync (ref-set counter (first args)))
'peek  @counter))))

Then use it like this:

user> (def c (make-counter 0))
#'user/c
user> (c 'count)
1
user> (c 'count)
2
user> (c 'peek)
2
user> (c 'reset)
0
user> (c 'set-to 5)
5
user> (c 'count)
6

I don't think I'd really do that in practice though. If you need a dispatch function like that you might as well make a class. In Clojure, it would look something like this:

(ns Counter
  (:gen-class
   :constructors {[] []}
   :init init
   :state cnt
   :methods [[count [] Integer]
    [reset [] Integer]
    [setTo [Integer] Integer]
    [peek  [] Integer]]))

(defn -init []
  [[] (ref 0)])

(defn -count [this]
  (dosync (ref-set (.cnt this) (inc @(.cnt this)))))

(defn -reset [this]
  (dosync (ref-set (.cnt this) 0)))

(defn -setTo [this val]
  (dosync (ref-set (.cnt this) val)))

(defn -peek [this]
  @(.cnt this))


Using it would look like this:

user> (compile 'Counter) 
Counter
user> (def c (Counter.))
#'user/c
user> (.count c)
1
user> (.count c)
2
user> (.count c)
3
user> (.setTo c 10)
10
user> (.count c)
11
user> (.reset c)
0
user> (.peek c)
0
user> (.peek c)
0
user> 

It's more code, but it's also much more readable, accessible from Java (and other JVM languages) and might be closer to what you're having to do.

— 
Daniel Lyons

max3000

unread,
May 26, 2009, 6:48:57 AM5/26/09
to Clojure
I really like the above class solution (but maybe its my OO
background! ;).

Howerver, I thought gen-class wasn't doing anything when not AOT-ing.
In other words, unless you are doing AOT compilation, gen-class
shoudn't be used. Did I miss something?

Thanks,

Max

Rich Hickey

unread,
May 26, 2009, 8:15:07 AM5/26/09
to Clojure
Yes - keep your functions out of your data. If you are used to
engine.getMove(position) it becomes:

(get-move engine position)

If you want polymorphism you can make get-move a multimethod. If the
'engine' concept is only about code (e.g. has no data of its own, just
specifies a protocol), you can simply use names for engines in your
multimethods:

(get-move :minmax-engine position)
(get-move :greedy-engine position)

etc.

Rich

Meikel Brandmeyer

unread,
May 26, 2009, 1:17:41 PM5/26/09
to clo...@googlegroups.com
Hi,

Am 26.05.2009 um 14:15 schrieb Rich Hickey:

> Yes - keep your functions out of your data. If you are used to
> engine.getMove(position) it becomes:
>
> (get-move engine position)
>
> If you want polymorphism you can make get-move a multimethod. If the
> 'engine' concept is only about code (e.g. has no data of its own, just
> specifies a protocol), you can simply use names for engines in your
> multimethods:
>
> (get-move :minmax-engine position)
> (get-move :greedy-engine position)

as Rich said: multimethods to the rescue.

You could define a namespace giving the interface of an engine. This
consists of the method definitions and maybe some default
implementations.

(ns car.engine)

(defmulti start
"Start the engine."
type)

(defmulti refuel
"Refuel the tank of the engine."
(fn refuel-dispatch [e _] (type e)))

(defmethod refuel :default
[e amount]
(swap! (:tank e) + amount))

(defmulti explode
"Explode the engine."
type)

(defmethod explode :default
[e]
"BOOOM!")

This basically looks similar to an abstract class in Java. Now we have
to provide some concrete implementation.

(ns car.engine.otto
(:require
[car.engine :as engine]))

(let [type-info {:type ::Otto}]
(defn make-engine
[initial-fuel]
(with-meta {:tank (atom initial-fuel)} type-info)))

(defmethod engine/start ::Otto
[e]
"VROOOOM!")

And another one:

(ns car.engine.diesel
(:require
[car.engine :as engine]))

(let [type-info {:type ::Diesel}]
(defn make-engine
[initial-fuel]
(with-meta {:tank (atom initial-fuel)} type-info)))

(defmethod engine/start ::Diesel
[e]
"TACKTACKTACKTACK!")

(defmethod engine/explode ::Diesel
[e]
"POOOOOOOF!")

Now we use our different implementations.

(ns car.sportscar
(:require
[car.engine :as engine]
[car.engine.otto :as otto]))

(defn make-sportscar
[]
{:engine (otto/make-engine)})

(defn do-drive
[car]
(engine/start (:engine car))
(accelerate car)
(brake car)
(engine/refuel (:engine car) 100)
...)

Or the diesel one....

(ns car.truck
(:require
[car.engine :as engine]
[car.engine.diesel :as diesel]))

(defn make-truck
[]
{:engine (diesel/make-engine)})

....

You get the idea...

Although this went a bit away from the initial chess example,
it should give an idea how the original problem could be solved.
A more real life example is my experimental monad library
based on multimethods (http://bitbucket.org/kotarak/monad).

Hope this helps.

Sincerely
Meikel

Reply all
Reply to author
Forward
0 new messages