Deleting a macro

I sometimes run into a situation with Hy that I would like to delete a macro. This happens especially when I’m trying to get a feel of code and are prototyping in REPL. Functions are easy to delete and replace with macro, but other way around took a bit digging.

Continue reading

Some hylarious macro shenanigans

Just a word of warning: what I’m about to describe here makes even less sense than what I usually write. It’s probably very bad idea for various reasons and definitely shouldn’t be used in production code. However, it might come handy in some limited situations and offer short term gains that justify the bad things. Use your own judgement.

As you might know, Hy is gearing towards the next release and there will be some major, code breaking changes. Things like let and defclass will have changed syntax and some aliases will be dropped (true doesn’t work anymore, only True). After talking with various people, I started thinking “what kind of system could help me to port old code to new one?”. And of course I turned for macros. So, here I’m going to show basic implementation for a macro that takes Hy code written in old syntax and transforms it for the new syntax. One can then wrap all existing code in that macro, update Hy and things hopefully work right out of box. Then it’s just matter of manually fixing things on the new syntax, while having test suite to check that there has not been any errors in process. Sounds easy enough, right?

Continue reading

Multiple dispatch in Hy

While tinkering with some AI routines, adderall and Hy in general, I came to conclusion that I needed something akin to multimethods in common lisp or clojure. Luckily I wasn’t the only one who had been wondering about them and quick googling found various articles detailing ways to implement them.

Adam Bard wrote a nice post with fully working code example. That I borrowed and more or less directly translated into Hy (just changed some names in the end and hacked in doc-string):

(defn multi-decorator [dispatch-fn]
  (setv inner (fn [&rest args &kwargs kwargs]
                (setv dispatch-key (apply dispatch-fn args kwargs))
                (if (in dispatch-key inner.--multi--)
                  (apply (get inner.--multi-- dispatch-key) args kwargs)
                  (apply inner.--multi-default-- args kwargs))))
  (setv inner.--multi-- {})
  (setv inner.--doc-- dispatch-fn.--doc--)
  (setv inner.--multi-default-- (fn [&rest args &kwargs kwargs] nil))
  inner)

(defn method-decorator [dispatch-fn &optional [dispatch-key nil]]
  (fn [func]
    (if (is dispatch-key nil)
      (setv dispatch-fn.--multi-default-- func)
      (assoc dispatch-fn.--multi-- dispatch-key func))
    dispatch-fn))

That’s really not much code at all and still it does exactly what I was after (dispatching by certain key in a dictionary). One could maybe add some extra guard clauses to raise an exception if multiple methods with same dispatch-key were registered, but that wouldn’t add much more code.

But, writing functions with decorators gets bothersome eventually, so I created three small macros to automate that part for me:

(defmacro defmulti [name params &rest body]
  `(with-decorator multi-decorator
    (defn ~name ~params ~@body)))

(defmacro defmethod [name multi-key params &rest body]
  `(with-decorator (method-decorator ~name ~multi-key)
    (defn ~name ~params ~@body)))

(defmacro default-method [name params &rest body]
  `(with-decorator (method-decorator ~name)
    (defn ~name ~params ~@body)))

With these Bard’s example can be written in Hy as:

=> (defmulti area [shape]
...  (:type shape))

=> (defmethod area "square" [square]
...  (* (:width square)
...     (:height square)))

=> (defmethod area "circle" [circle]
...  (* (** (:radius circle) 2)
...     3.14))

=> (default-method area [shape]
...  (raise (Exception "Can't calculate area of this shape")))

=> (area {:type "circle" :radius 0.5})
0.785

=> (area {:type "square" :width 1 :height 1})
1

=> (area {:type "rhombus"})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 6, in inner
  File "<input>", line 2, in area
Exception: Can't calculate area of this shape

Quite succinct, without being obtuse. And this system allows extensions (adding new implementations) for new cases, without the need for old ones being modified.

Hy and anaphoric macros

There’s a nice contrib module in Hy called anaphoric, that (surprise, surprise) contains bunch of anaphoric macros. Wikipedia has nice definition for these in general:

An anaphoric macro is a type of programming macro that deliberately captures some form supplied to the macro which may be referred to by an anaphor (an expression referring to another). Anaphoric macros first appeared in Paul Graham‘s On Lisp and their name is a reference to linguistic anaphora—the use of words as a substitute for preceding words.

Hy documentation lists several of them: ap-if, ap-each, ap-each-while, ap-map, ap-map-when, ap-filter, ap-reject, ap-dotimes, ap-first, ap-last and ap-reduce. I’m not going through all of them in detail, but instead picking couple of them to highlight what the concept is about (I use them all the time while coding).

Continue reading

Finite-state machines in Herculeum – part 3

Now that I have fully functional finite-state machine system, I can get started writing AI routines for the game, right? Apparently not yet. When inspecting the code, I realized that the finite-state machine system lacked a very important feature: ability to have values entered with –init– method. So that needed to be taken care of before starting to work with the AI.

Current implementation of rat AI is as follows:

(defclass RatAI []
  [[__doc__ "AI routine for rats"]
   [character None]
   [mode ["transit" None]]
   [--init-- (fn [self character]
           "default constructor"
           (.--init-- (super RatAI self))
           (setv self.character character) None)]
   [act (fn [self model action-factory rng]
      "check the situation and act accordingly"
      (rat-act self model action-factory))]])

–init– is used to give AI reference to the character it’s in charge of operating. act is called every time it’s characters turn to act. I could have changed the system to pass character reference as a parameter of act, but I wanted to keep interface unchanged for now, so chose to add –init– method for finite-state machine system instead.

–init– form of defstatemachine mirrors closely what –init– does for a regular class (in fact, it’s injected to be part of –init– method of final class). It takes a list of parameters and body of code. With this it was simple to define correct parameter list and rest of the game was happily ignorant that it was interacting with finite-state machine instead of plain old class (although, that finite-state machine is still a plain old class in the end of course).

Next design challenge I encountered was related with splitting common functionality into smaller pieces. I spent couple evening prototyping this with state monad from Hymn, but couldn’t come up with a system that I liked. With Hymn passing the state back and forth would have been taken care of automatically and behind the scenes, which was one of the attractive reasons for trying to use it. In the end (or should I say, for now?), I abandoned the idea and went with regular functions. This of course meant that I had to pass the state around manually and modifying it took a bit more coding. The end goal is to have some sort of library for AI routines that can be combined easily for new creatures. Getting a simple rat AI done was lots more work than what I anticipated, but I’m hopeful that next ones will be easier.

Final wart needing solving was the need for deactivation code. When a rat notices an enemy, it switches to a different state and a little exclamation mark is shown in UI to signal player that the rat is now in alert state. When rat loses the sight of the enemy, old AI used to raise a question mark to signal that the rat wasn’t sure about location of its target (they have lousy memory). I couldn’t raise the question mark in finding home state, since then it would be shown every time the rat decides to head home. Instead of that, there was need for codeblock that gets execute when state deactivates, but before next state is activated. Adding the state was almost identical on how on-activated is handled.

Now that I had all the major parts of the code ready, I could assemble whole thing and ended up with the following code:

(defstatemachine RatAI [model action-factory rng]
  "AI routine for rats"
  (--init-- [character] (state character character))

  "find a place to call a home"
  (finding-home initial-state
                (on-activate (when (not (home-location character))
                               (select-home character wallside?)))
                (active (travel-home (a-star (whole-level)) character))
                (transitions [(arrived-destination? character) patrolling]
                             [(detected-enemies character) fighting]))
  
  "patrol alongside the walls"
  (patrolling (on-activate (when (not (home-area character))
                             (map-home-area character
                                            (fill-along-walls (. character level)))) 
                           (clear-current-destination character))
              (active (one-of (patrol-home-area (a-star (along-walls)) character)
                              (wait character)))
              (transitions [(detected-enemies character) fighting]))
  
  "fight enemy"
  (fighting (on-activate (clear-current-destination character)
                         (select-current-enemy character closest-enemy)
                         (show-alert-icon character (current-enemy character)))
            (active (if (in-area area-4-around (. character location) 
                                 (. (current-enemy character) location))
                      (melee character (current-enemy character))
                      (close-in (a-star (whole-level)) 
                                character 
                                (. (current-enemy character) location))))
            (on-deactivate (show-confusion-icon character))
            (transitions [(not (detected-enemies character)) finding-home])))

It’s pretty clear I think and should be easier to fine tune and adjust in the future. It certainly is much more reusable than the previous patrol AI code.

Here and there I used functions as parameters facilitate fine tuning algorithms. For example, when the rat is finding its way to home area, whole level is considered as valid ground for traveling. A* algorithm will try and find the shortest possible route. However, as soon as the rat arrives to home area, it switches to different mode and completely ignores anything but edges of rooms (and areas in front of doorframes, so it can pass those too). It’s still the same A* routine, but slightly differently configured.

Another example is shown below. It’s a function that maps character’s home area and assigns it to AI data:

(defn map-home-area [character neighbours]
  "build map of home area for character"
  (let [[state (ai-state character)]
        [to-check [(home-location character)]]
        [result []]]
    (while to-check
      (let [[it (.pop to-check)]]
        (when (not (in it result))
          (.append result it)
          (.extend to-check (neighbours it)))))
    (assoc state :home-area result)))

It basically does a flood fill from home location of character. Function that provides coordinates for neighbouring cells is provided from outside and its implementation or internal behaviour isn’t any concern of this function. As a consequence, this function (couple with the neighbours function) can be used to flood fill whole level, edges of room, center of room or any other continuous area. It could even be completely random random area that gets filled.

Most of the AI functions manipulate shared state in a way or another. I’m basing this on a convention, rather than contract. Any function can read, write and otherwise manipulate the state. This allows them to be combined in a way that I didn’t anticipate at the time of writing them. Problem of course is that if I’m not careful, unexpected interactions between those functions might arise and manifest as bugs.

This was also pretty tricky code to test. While coding, I kept thinkig that I should have some sort of automated system for testing the code, but it would be pain to write and maintain. I would have to set up whole level with creatures, items and worst of all, specific internal state of the AI. These tests would break all the time if even the slightest change was made in the internal representation of AI. In the end I decided that it wouldn’t be worth the gain to go through all that pain and just resolved testing the AI by playing the game. It’s slow process that will miss errors, but it’s still better than nothing (just not by that much).

Testing Hy macros

I like my code tested and working. I like it even better when it’s done automatically. But recently I was faced with a new kind of code that proved to be rather different beast to tackle: macros. Problem with testing them is that macros usually capture some common, general pattern and give it a name. If one were to use the same approach as with testing function, they would feed specific parameters to macro and assert that the generated code looks like what it should look like. This could soon lead into brittle, hard to maintain tests, since tests would specify very precisely what the generated code should look like. Even minor changes to implementation would break most of the tests, even if the functionality of the generated code wouldn’t change.

Better option (depending on the case again) would be to write code that uses that macro and assert that the code has correct functionality. Depending on the complexity of the macro this might require more than single test in order to cover all possibilities. This is the way I usually test my macros, when I bother testing them at all. Most of the time I just assume that if there are any problems with the macro implementation, tests covering code where it is used will catch problems.

Continue reading