Arc Forumnew | comments | leaders | submitlogin
help? bit of code that should be prettier
1 point by bOR_ 6040 days ago | 9 comments
Hi.

Happily writing in arc, and trying to make my functions more functional, rather than imperative. The code below are the functions to move a creature through a square grid, and to keep him/her in this grid. The general mode of moving of the creature is (1) to go forward, (2) occasionally change direction, (3) not to move outside the grid, and (4) if it is at the edge to change its direction so that it moves away from the edge.

Three of these functions are nice pretty one-liners, but the last one I couldn't think of a way to make it pretty, and that is where I request your help (but feel free to meddle with the other three as well, if you want to point out something interesting).

  (def newdir ()
     `(,(- (rand 3) 1) ,(- (rand 3) 1)))

  (def forward (creature)
     (map + (creature 'pos) (creature 'dir)))
   
  (def placewithinbounds (creature)
     ; sort of wrongly assuming the world has the same height as it has width
     (map [min _ (- (len world) 1)] (map [max _ 0] (creature 'pos))))

  (def directawayfrombounds (creature)
     ; don't know how to do this more elegant hmpf
     (let newdir '(nil nil)
        (if (and (is (car (creature 'pos)) 0) (is   (car (creature 'dir)) -1)) (= (car newdir) 1))
        (if (and (is (car (creature 'pos)) (- (len (world 0)) 1)) (is (car (creature 'dir)) 1)) (= (car newdir) -1))
        (if (and (is (cadr (creature 'pos)) 0) (is (cadr (creature 'dir)) -1)) (= (cadr newdir) 1))
        (if (and (is (cadr (creature 'pos)) (- (len (world 0)) 1)) (is (cadr (creature 'dir)) 1)) (= (cadr newdir) -1))
        (if (no (car newdir)) (= (car newdir) (car (creature 'pos))))
        (if (no (cadr newdir)) (= (cadr newdir) (cadr (creature 'pos))))
      newdir))


1 point by almkglor 6040 days ago | link

First tip: Avoid using '( ... ) . Use (list ...) instead. When you learn about mutating objects, then use '( ... ) , and only for optimization or if you really, really want to.

  (def directawayfrombounds (creature)
    (with ((oldxdir oldydir) creature!dir
           (oldxpos oldypos) creature!pos
           delimit
           (fn (minpos oldpos maxpos olddir)
             (if
               (is oldpos minpos)
                 (if (is olddir -1)
                     1
                     olddir)
               (is oldpos maxpos)
                 (if (is olddir 1)
                     -1
                     olddir)
               ; else
                 olddir)))
       `(,(delimit 0 oldxpos (- (len (world 0)) 1)
                   oldxdir)
         ,(delimit 0 oldypos (- (len (world 0)) 1)
                   oldydir))))

-----

1 point by bOR_ 6040 days ago | link

So.. (let newdir '(nil nil) should become (let newdir (list nil nil) and creature!pos is equivalent to (creature 'pos) and you make a temporary anonymous function within a function but still give it a name (delimit, through the with command) that does the comparing. Three new things learned :).

I'll see if I can improve it some more though, might be able to do something with testing whether position + car / cadr direction would put a creature out of bounds in the world array, and then multiply the direction by -1. Not at home now, so will have to wait.

-----

1 point by Darmani 6040 days ago | link

  (= range cons)

  (def within (range num)
    (and (>= num (car range)) (<= num (cdr range))))

  (def directawayfrombounds (creature)
    (let awaydir (fn (pos dim dir)
       (if (within (range 0 (- dim 1)) (+ pos dir))
         dir
         (- dir)))
      (list 
        (awaydir (car creature!pos) (len (car world)) (car creature!dir))
        (awaydir (cadr creature!pos) (len (car world)) (cadr creature!dir)))))

-----

1 point by almkglor 6040 days ago | link

'range is an arc.arc function in canonical Arc, so you should be leery of redefining it - if you decide to use some random library, and it uses 'range in the canonical sense, you can expect it to break.

Instead, define it in subfunction form:

  (def directawayfrombound (creature)
    (withs (range
              cons
            within
              (fn (range num)
                (let (min . max) range
                  (<= min num max)))
            awaydir
              (fn (pos dim dir)
                (if (within (range 0 (- dim 1)) (+ pos dir))
                    dir
                    (- dir)))
            (xpos ypos) creature!pos
            (xdir ydir) creature!dir)
      (list
        (awaydir xpos (len world.0) xdir)
        (awaydir ypos (len world.0) ydir))))

-----

1 point by Darmani 6039 days ago | link

Thanks for the correction. Although I did know about the canonical range function, it slipped my mind when I wrote that (though I did have the feeling that there was a "real" range function). I should have mentioned that I wasn't near any computer with Arc when I wrote, though I still should have known better.

-----

1 point by bOR_ 6039 days ago | link

Both of you, thanks!, more learning arc. (1) "withs" is "with sequentially"', so that "range" makes sense in the function "awaydir". (2) cons isn't just appending a number to a list.

I like the table!key syntax, not sure what I'm thinking about world.0 as a syntax, especially because world.0.0 doesn't give what I'd expect (first element of the first element of world), but just returns the same as world.0

  (def makeworld (x)
     (= world (n-of x (n-of x nil))) ; world of nil
     nil)

-----

1 point by Darmani 6039 days ago | link

Actually, almkglor's code would (I'm pretty sure) have worked with just a normal with; function definitions are unevaluated at definition, so that (fn (x) (afergergghersgergferg x)) would not give "Error: afergergghersgergferg is not a function" until it's called. If a defined a function called afergergghersgergferg in the meantime, as with a normal with statement, it should have worked.

Anyway, you're not alone in being surprised that x.y.z expands to (x y z) instead of (x (y z)).

-----

2 points by absz 6039 days ago | link

Not quite. It works in the toplevel because doing (def name args ...) is effectively to (= name (fn args ...)); this establishes a global, not a lexical, binding. Using let/with/withs adds a lexical binding, and since functions (or "closures") close over their scopes, they can't get at functions defined at the same time. Thus, you need to use withs. Observe:

  arc> (with (foo (fn (x) (* 2 x)) bar (fn (y) (foo y)))
         (prn (foo 10))
         (prn (bar 10))
         t)
  20
  Error: "reference to undefined identifier: __foo"
  arc> (withs (foo (fn (x) (* 2 x)) bar (fn (y) (foo y)))
         (prn (foo 10))
         (prn (bar 10))
         t)
  20
  20
  t

-----

1 point by bOR_ 6038 days ago | link

That implies that at the top level, the order in which I define my functions (which use each other) doesn't matter..

nice! Then I can group them in a way that is more sensible for humans.

Anyway, the program is now far enough that I've a critter running around the field and filling its memory with associations between the things it encounters. Next stop is to let it learn what is edible and to let it steer itself based on its observations :).

-----