Code review request: Dijikstra's algorithm
For a school assignment, I'm working on a visualization of Dijikstra's algorithm and several variants of A* search.
I tried to mimic the imperative-style implementation of Dijiksta's found in most textbooks, so I wrote the algorithm in the ST monad and used mutable hashtables and heaps.
Unfortunately, I'm not happy with how the code reads. Unlike in other imperative languages, I feel like the only way to make this algorithm concise is by turning my code into operator soup, with lots of >>=, <*>, and <$>s.
(For example, the following code:
loopCondition = do
heapNotNull <- not . PQueue.null <$> ST.readSTRef heap
tNotVisited <- isNothing <$> HT.lookup visited t
return (heapNotNull && tNotVisited)
whileM_ loopCondition $ -- do stuff
can only be written in one line if we write it as
whileM_ ((&&) <$> (not . PQueue.null <$> ST.readSTRef heap) <*> (isNothing <$> HT.lookup visited t)) $ -- do stuff
which is very ugly, in my opinion.)
What can I do to make my code more readable, especially for imperative programmers? I'm afraid that the world's finest imperative language might not be the world's most readable imperative language.
Code is here. (By the way, if you are interested in running it, graph.hs has a shebang and the stack runghc options, so chmod +x graph.hs; ./graph.hs should work if you have stack installed! The code creates a random graph, runs three variants of A*-search on it, and outputs the visualization to graph.svg.)
Posted Mar 5, 2016, 7:31 PM UTC · edited
What's wrong with your first block? That looks perfectly fine to me. Being concise is good only if it improves readability.
The code overall looks good! Now do it in a purely functional manner and see how it compares.
It's a bit hard to convince friends that Haskell is a good language for writing imperative code when it takes four lines to write the equivalent of
.
Especially to non-Haskellers, the above one-line statement is straightforward and concise; the equivalent Haskell expression uses both
do-notation andfmap, and is harder to parse/comprehend.It seems to me like functional code can be very readable in Haskell, but imperative code sometimes cannot.
(BTW, I know it's possible to write this using efficient pure data structures. However, I wanted to use mutable hashtables to prove to my non-Haskeller friends that my Haskell implementation is as readable and as efficient as their Java/Python implementations.)
You can define your own functions and operators to mimic the imperative code pretty closely.
Untested, but you get the idea. If you want, you can probably make the Haskell look almost exactly like the imperative code, but that takes you too close to BASIC in Haskell I think... ;)
This. Abstracting away details through function extraction into well named functions (e.g. isEmpty), is something I wish people would do more in every language.
The main benefits of imperative Haskell are the type safety, the ability to write your own control structures (
whileMis just a function etc), that you can specify your effects and how they interact (egStateandExceptioncan either rollback state or not depending on which is on top) ,and that you can refactor much more easily than in eg C or Java. It's not going to be more concise, usually.That you can factor out the
loopConditioninto a regular ol' Haskell value effortlessly is a huge selling point IMO. That's not trivial/easy to do in C, and is much more difficult to reason about in eg Java or Python.is going to require that all variables captured in the closure are
final. Python's methods would support dynamic lookup IIRC but still not quite as easily.Why is the burden of proof on Haskell? I've never seen a convincing argument from the other side that their way is better than Haskell. In fact, the strongest argument they can come up with for not learning Haskell is that they don't understand Haskell. facepalm.
Pretend you've never learned a C-like language and tell me that doesn't look like gibberish. What the hell does "&&" mean? Why do some words start with "!"? What's with the empty parenthesis? Etc.
Why not a self recursive function, though? It will look much better than the monadic version you got.