
Show HN: I wrote a maze traversal program in Clojure - netb258
https://github.com/netb258/clj-maze/blob/master/src/maze/core.clj
======
_virtu
If you're learning about mazes you'll have plenty of fun reading "Mazes for
programmers". I've been reading through it and it's a nice change of pace.
I've thrown a few of the algorithms on my pen plotter site
[https://plott.id/mazes](https://plott.id/mazes). I definitely encourage you
to give the book a look.

Here's the source for the site:
[https://github.com/blakedietz/plott.id](https://github.com/blakedietz/plott.id)

------
hk__2
PSA: please use clojure.edn/read-string instead of read-string. The latter can
execute code when it parses the string (try running `(read-string "#=(println
\"oops\")")`.

~~~
elamje
While you are PSAing, can you mention why this is useful, and how much it can
enhance performance?

~~~
todd8
Performance differences are likely not that critical between reading small
amounts of data using string I/O vs reading small amounts of data using a
library routine that reads and evaluates the data. Runtime evaluation of a
string as a language expression is often available in interpreted languages
like Python, Ruby, or the Lisp family, and these languages are not usually
used in the most performance sensitive programs.

Three factors _may_ make a difference between using the two aforementioned
functions. First, performance can matter if the program is doing enough I/O.
Secondly, the machinery needed to evaluate language expressions will have to
be available at runtime, meaning that the executable will have to be
interpreted by an interpreter capable of doing the evaluation (which Lisp,
Python, etc. are—they have REPLs after all) or the executable in languages
without a runtime interpreter will have to be linked statically or dynamically
with code that does the evaluation, making it resulting executable larger.
Third, there is the really important reason that programs should avoid reading
and evaluating their inputs as expressions: security.

A process runs code within a certain security context, generally with the same
privileges as the user running the program. If the data input to the process
can run expressions not found in the code, the program’s author can make few
security guarantees about the results of running the program. Programs may
receive input from the network and may even run at elevated security levels
(e.g. setuid); these programs should not evaluate arbitrary input. For
example, many security vulnerabilities come from database programs reading
strings that are passed directly to the SQL interpreter. See the XKCD
“Exploits of a Mom” [1].

[1] [https://xkcd.com/327/](https://xkcd.com/327/)

~~~
lispm
> interpreted languages like Python, Ruby, or the Lisp family

Lisp isn't an 'interpreted language'.

> have to be interpreted by an interpreter capable of doing the evaluation
> (which Lisp, Python, etc. are—they have REPLs after all)

SBCL, a Common Lisp implementation...

    
    
      * (mapcar #'disassemble (list (lambda (a) (print a))))
      ; disassembly for (LAMBDA (A))
      ; Size: 28 bytes. Origin: #x226B7580
      ; 80:       498B4510         MOV RAX, [R13+16]                ; no-arg-parsing entry point
                                                                    ; thread.binding-stack-pointer
      ; 84:       488945F8         MOV [RBP-8], RAX
      ; 88:       488BD3           MOV RDX, RBX
      ; 8B:       B902000000       MOV ECX, 2
      ; 90:       FF7508           PUSH QWORD PTR [RBP+8]
      ; 93:       B8D8C35222       MOV EAX, #x2252C3D8              ; #<FDEFN PRINT>
      ; 98:       FFE0             JMP RAX
      ; 9A:       CC0F             BREAK 15                         ; Invalid argument count trap
      (NIL)
    

To me that looks like machine code...

A bunch of Lisp implementations use compilers for evaluation in the Read-Eval-
Print-Loop.

~~~
todd8
I wasn’t trying to insult Lisp.

Compilers for Lisp have been around in some form for many years—I can remember
hearing Patrick Winston talking about them when I first learned Lisp in his
class in 1973. There are both interpreters and compilers for many languages
and translators that are in between that use JIT compilation at run time.

Lisp and it’s offshoots (Scheme, Dylan, Common Lisp, Clojure, etc.) have a
wider range of compilation and interpretation strategies used in their
implementations than other programming languages. Perhaps this is due to the
fact that Lisp has been around longer than almost any other important language
or perhaps its due to its homoiconicity.

However my point was really about the availability of eval at run time,
however it is implemented, and particularly eval that runs in the program’s
address space and security context.

It’s true that the SBCL implementation of Common Lisp, by default, compiles
eval’s arguments at runtime, but it may also use interpretation at runtime
depending on the value of the a global variable. Other implementations of
Common Lisp always use interpretation, like CLISP. Also, Common Lisp has
_eval-when_ which affects the compile vs interpret decision [1].

Readers interested in interpretation vs compilation can find introductory
coverage of the topic in Wikipedia, see [2]. Lisp has an interesting and
important history to computer science as the first interpreted high level
language, see it’s Wikipedia entry at [3].

[1]
[http://www.lispworks.com/documentation/lw50/CLHS/Body/s_eval...](http://www.lispworks.com/documentation/lw50/CLHS/Body/s_eval_w.htm)

[2]
[https://en.m.wikipedia.org/wiki/Interpreter_(computing)](https://en.m.wikipedia.org/wiki/Interpreter_\(computing\))

[3]
[https://en.m.wikipedia.org/wiki/Lisp_(programming_language)](https://en.m.wikipedia.org/wiki/Lisp_\(programming_language\))

~~~
lispm
> I wasn’t trying to insult Lisp.

Sure, just categorizing Lisp as an interpreted languages isn't correct.

> However my point was really about the availability of eval at run time,
> however it is implemented, and particularly eval that runs in the program’s
> address space and security context.

Right, but technically Common Lisp doesn't requite eval to use a compiler and
it can also load compiled code from the file system, at runtime.

> Other implementations of Common Lisp always use interpretation, like CLISP.

CLISP also provides a virtual machine with a byte code compiler (also
optionally a JIT native code compiler), though the compiler needs to be called
explicitly.

> Also, Common Lisp has eval-when which affects the compile vs interpret
> decision [1].

Depends. In an implementation like SBCL or CCL, which use compilation for
EVAL, it has no effect on such a decision.

------
onetom
First of all, thanks for sharing; it's a very educational piece of code!

I have some generic coding advice though. With slightly better names you can
get rid of a lot of the comments. There is a lot of repetition in the code. If
you factor that out, you will have even less need for comments. I highly
recommend watching some Kevlin Henney talks on such topics, for example
[https://youtu.be/ZsHMHukIlJY?t=487](https://youtu.be/ZsHMHukIlJY?t=487) (but
he has even better talks recently)

The input of the program should be supplied in a more format more natural to
humans and let the computer do the transformation into whatever data structure
it's comfortable with. So I would propose a simple, multi-line text as an
input:

    
    
        xxxxxx
        0x000x
        x*0x0x
        xxxx00
        00000x
        xxxx0x
    

Then the code would look like this:

    
    
        (ns maze.solver
            (:require [clojure.string :as str]))
    
        (def maze-filename "maze.txt")
    
        (defn parse [maze-str]
         (->> maze-str
              str/trim 
              str/split-lines
              (mapv (partial into []))))
    
        (def maze (-> maze-filename slurp parse))
    

You can also just hardwire a maze during development into the source file,
since Clojure supports multiline strings:

    
    
        (def maze (parse "
        xxxxxx
        0x000x
        x*0x0x
        xxxx00
        00000x
        xxxx0x
        "))
    

The result of `parse` function will contain character data types instead of 1
character long strings. I think in this case it's actually more readable and
more concise too:

    
    
        [[\x \x \x \x \x \x]
         [\0 \x \0 \0 \0 \x]
         [\x \* \0 \x \0 \x]
         [\x \x \x \x \0 \0]
         [\0 \0 \0 \0 \0 \x]
         [\x \x \x \x \0 \x]]
    

Next `println` actually accepts multiple arguments and concatenates them with
a space, after stringifying them, so you can drop the extra `(str ...)`
wrapping around them.

Then again, the comment is superfluous. It's obvious that you are printing
stuff. If you really want to tell what is it, just wrap it in a well-named
function. Eg:

    
    
        (defn report [paths]
          (println "The maze has" (count paths) "paths.")
          (println "The shortest path in the maze is:" (count (first paths)) "steps long.")
          (println "The path is" (first paths))
          (println "The longest path in the maze is:" (count (last paths)) "steps long.")
          (println "The path is" (last paths)))
    

So you can just put `(report sorted-paths)` in your `-main`.

~~~
netb258
I am shamelessly stealing your code.

------
fuzz4lyfe
Looks similar to A* to my non-Clojure using eye. How has your performance
been? If I use a 1000 x 1000 grid in my A* Python implementation I wrote last
night it takes forever.

[0][https://en.m.wikipedia.org/wiki/A*_search_algorithm](https://en.m.wikipedia.org/wiki/A*_search_algorithm)

~~~
thethirdone
I wouldn't expect performance to take forever (> 1 hr) for 1000x1000 in
python. I know I have made not optimized path-finding that can solve 100x100
instantly (< 1 sec), and because the time complexity scales with # of vertices
for planar networks that should mean that it would only take ~100 sec to solve
1000x1000.

~~~
fuzz4lyfe
I was being hyperbolic, run time was more in the two minute range. Thank you
for the numbers it seems my idea of thousands of units pathfinding over long
distances isn't feasible in this way.

My next idea is to "prerender" a series of interconnected waypoints across the
map at the start and then pathfind to the nearest one before moving long
distances using small bursts of pathfinding to move around obstacles and stay
on track.

------
torvaney
Neat! I happened to finish a similar project in Clojure just this week:

[https://github.com/Torvaney/flow-solver](https://github.com/Torvaney/flow-
solver)

Although I used a much lazier strategy for doing the solving (reduction to
SAT).

------
lispm
Common Lisp:
[https://gist.github.com/lispm/145fc3e0967f42ff44a11e0670be1a...](https://gist.github.com/lispm/145fc3e0967f42ff44a11e0670be1aef)

------
danielcorin
Related (not my repo):
[https://github.com/joewing/maze](https://github.com/joewing/maze)

------
Heliosmaster
Thanks for teaching me about the fn trampoline, which I had never seen/used
before!

Really cool project!

~~~
thom
It's very useful. I once had a codebase where we wrote two (parallel) variants
of trampoline called tramampoline and trambopoline, and I'm not even a little
bit sorry.

------
tincholio
Just a nit-picky comment... your 'can-go-left?' and 'move-left', etc.
functions would probably be more idiomatic by either passing the direction as
an argument, (and maybe using multi-methods, though it might be overkill for
this).

------
2000andlate
I don’t have a whole lot to say other than this looks awesome! Well done!

