<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-978780246348321177</id><updated>2011-04-21T23:32:05.288+02:00</updated><category term='photo'/><category term='travel'/><category term='microcontrollers'/><category term='nirvana'/><category term='haskell'/><category term='programming'/><category term='optimization'/><category term='ion3'/><category term='japan'/><category term='forth'/><category term='lua'/><category term='brainfuck'/><category term='electronics'/><title type='text'>my sabbatical year diary</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>12</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-1148539247007845015</id><published>2008-05-05T23:11:00.002+02:00</published><updated>2008-05-05T23:15:16.928+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='travel'/><category scheme='http://www.blogger.com/atom/ns#' term='japan'/><title type='text'>Back from Japan</title><content type='html'>By the way - about three weeks ago I came back from a long planned trip to Japan. Great country, nice people, beautiful women, and beautiful cherry blossom. One bad thing - too many gaijins around, but I still enjoyed it.&lt;br /&gt;&lt;br /&gt;If someone is interested in some photos here they go: &lt;a href="http://photo.glum.website.pl/thumbnails.php?album=1"&gt;pics&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-1148539247007845015?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/1148539247007845015/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=1148539247007845015' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/1148539247007845015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/1148539247007845015'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/05/back-from-japan.html' title='Back from Japan'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-4245643930629184053</id><published>2008-03-02T15:26:00.007+01:00</published><updated>2008-03-02T18:26:04.077+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='photo'/><title type='text'>A few more drops</title><content type='html'>&lt;div style="text-align:justify;"&gt;&lt;br /&gt;I have spent most of the day today playing with the drop detector and refining this ultra-sophisticated technology. Below you can see the effects.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_HK7Rq_EG-j8/R8q55WyxyjI/AAAAAAAAAMI/SgvNM0TTdbs/s1600-h/img_4329.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://3.bp.blogspot.com/_HK7Rq_EG-j8/R8q55WyxyjI/AAAAAAAAAMI/SgvNM0TTdbs/s320/img_4329.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5173151517010676274" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_HK7Rq_EG-j8/R8q552yxykI/AAAAAAAAAMQ/RRfxq2xkem0/s1600-h/img_4339.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_HK7Rq_EG-j8/R8q552yxykI/AAAAAAAAAMQ/RRfxq2xkem0/s320/img_4339.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5173151525600610882" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_HK7Rq_EG-j8/R8q56GyxylI/AAAAAAAAAMY/BlC5SP1-Rq4/s1600-h/img_4353.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_HK7Rq_EG-j8/R8q56GyxylI/AAAAAAAAAMY/BlC5SP1-Rq4/s320/img_4353.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5173151529895578194" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_HK7Rq_EG-j8/R8q56WyxymI/AAAAAAAAAMg/zfJdgDX48LQ/s1600-h/img_4357.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://3.bp.blogspot.com/_HK7Rq_EG-j8/R8q56WyxymI/AAAAAAAAAMg/zfJdgDX48LQ/s320/img_4357.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5173151534190545506" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_HK7Rq_EG-j8/R8riX2yxynI/AAAAAAAAAMo/toy_ZM5l39M/s1600-h/img_4366.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_HK7Rq_EG-j8/R8riX2yxynI/AAAAAAAAAMo/toy_ZM5l39M/s320/img_4366.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5173196021461797490" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-4245643930629184053?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/4245643930629184053/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=4245643930629184053' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/4245643930629184053'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/4245643930629184053'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/03/few-more-drops.html' title='A few more drops'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_HK7Rq_EG-j8/R8q55WyxyjI/AAAAAAAAAMI/SgvNM0TTdbs/s72-c/img_4329.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-688627891255702292</id><published>2008-03-01T20:35:00.008+01:00</published><updated>2008-03-02T07:31:18.440+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='photo'/><category scheme='http://www.blogger.com/atom/ns#' term='electronics'/><category scheme='http://www.blogger.com/atom/ns#' term='microcontrollers'/><title type='text'>Mega High-Tech Computerized Laser Drop-Detector</title><content type='html'>&lt;div style="text-align:justify;"&gt;&lt;br /&gt;It's alive! My high-tech drop detector is alive and kickin'!&lt;br /&gt;&lt;br /&gt;I have been thinking about making a simple device detecting falling drops (or other objects) and triggering my camera (Canon EOS300D) for some time now, but I never had time to actually do it. Until now.&lt;br /&gt;&lt;br /&gt;Short description:&lt;ul&gt;&lt;li&gt;based on AVR ATTiny2313&lt;/li&gt;&lt;li&gt;using laser module + phototransistor as detector&lt;/li&gt;&lt;li&gt;communicating with PC via RS232 to allow fine-tuning of delay between detection and shutter&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Here you can see one of the very first photos taken with it. It is low quality, but it was taken in very low light conditions etc. And with hands shaking in excitement :)&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_HK7Rq_EG-j8/R8mzKmyxyiI/AAAAAAAAAMA/vYRJox6SVnE/s1600-h/kropla-small.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://3.bp.blogspot.com/_HK7Rq_EG-j8/R8mzKmyxyiI/AAAAAAAAAMA/vYRJox6SVnE/s320/kropla-small.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5172862641805314594" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-688627891255702292?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/688627891255702292/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=688627891255702292' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/688627891255702292'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/688627891255702292'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/03/mega-high-tech-computerized-laser-drop.html' title='Mega High-Tech Computerized Laser Drop-Detector'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_HK7Rq_EG-j8/R8mzKmyxyiI/AAAAAAAAAMA/vYRJox6SVnE/s72-c/kropla-small.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-5096264913104820273</id><published>2008-02-19T17:48:00.002+01:00</published><updated>2008-02-19T17:53:01.070+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='forth'/><title type='text'>Playing with monad transformers</title><content type='html'>Yesterday someone posted a link to a great &lt;a href="http://uebb.cs.tu-berlin.de/~magr/pub/Transformers.en.html"&gt;monad transformers tutorial&lt;/a&gt;. It's incredible. I think I finally start to understand (or at least being able to use) it.&lt;br /&gt;&lt;br /&gt;As part of testing my fresh knowledge I've written a simple virtual machine for a Forth-like language. Code (as usual - Literate Haskell, so you can just put it into .lhs file and run it) below.&lt;br /&gt;Enjoy!&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; module Main where&lt;br /&gt;&gt; &lt;br /&gt;&gt; import Control.Monad.State&lt;br /&gt;&gt; import Control.Monad.Reader&lt;br /&gt;&gt; import Data.Char&lt;br /&gt;&gt; import Data.Maybe&lt;br /&gt;&gt; import System.IO&lt;br /&gt;&gt; &lt;br /&gt;&gt; import qualified Data.Map as Map&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Data type &lt;code&gt;Op&lt;/code&gt; defines all primitive operations available in our language.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data Op =&lt;br /&gt;&gt;         Number Int    -- push number on stack&lt;br /&gt;&gt;     |   Plus          -- add two elements on top of stack&lt;br /&gt;&gt;     |   Minus         -- subtract&lt;br /&gt;&gt;     |   Mul           -- multiplicate&lt;br /&gt;&gt;     |   Div           -- divide&lt;br /&gt;&gt;     |   Out           -- write single number from stack to stdout&lt;br /&gt;&gt;     |   Emit          -- emit character with code from stack to stdout&lt;br /&gt;&gt;     |   Dup           -- duplicate item on top of stack&lt;br /&gt;&gt;     |   Drop          -- drop top-most item&lt;br /&gt;&gt;     |   Call String   -- call function with given name&lt;br /&gt;&gt;     |   GetI          -- get current loop counter and push on stack&lt;br /&gt;&gt;     |   Loop [Op]     -- execute loop body; max and min must be on stack&lt;br /&gt;&gt;     |   If [Op] [Op]  -- if statement with "then" and "else" block&lt;br /&gt;&gt;     |   Equal         -- push 1 if top-most items are equal, 0 otherwise&lt;br /&gt;&gt;     |   Less          -- push 1 if top-most item is greater than item below it&lt;br /&gt;&gt;     |   Greater       -- push 1 if top-most item is smaller than item below it&lt;br /&gt;&gt;     deriving Show&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;code&gt;Words&lt;/code&gt; is a dictionary mapping function names to their code.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; type Words = Map.Map String [Op]&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;code&gt;ForthState&lt;/code&gt; contains current interpreter state. It consists of data stack and of loop counter. Currently no nested loops are supported.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data ForthState = ForthState {&lt;br /&gt;&gt;     stack   :: [Int],&lt;br /&gt;&gt;     counter :: Int&lt;br /&gt;&gt; } deriving Show&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Interpreter state is defined using two monad transformers - &lt;code&gt;StateT&lt;/code&gt; responsible for state management (stack, counter), and &lt;code&gt;ReaderT&lt;/code&gt; providing read-only environment, in this case dictionary of user-defined words. &lt;code&gt;IO&lt;/code&gt; is used as internal monad since program is supposed to perform I/O operations.&lt;br /&gt;Computation does not return any useful results, hence return type of whole computation is &lt;code&gt;()&lt;/code&gt;.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; type Forth = ReaderT Words (StateT ForthState IO) ()&lt;br /&gt;&gt; &lt;br /&gt;&gt; initState = ForthState [] 0&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;code&gt;runForth&lt;/code&gt; function performs execution of given program in given environment (defined words) starting from given state.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; runForth :: Words -&gt; ForthState -&gt; [Op] -&gt; IO ((), ForthState)&lt;br /&gt;&gt; runForth env st program = runStateT (runReaderT (execSequence program) env) st&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Execution of sequence of operations is realized by simple (monadic) mapping of &lt;code&gt;exec&lt;/code&gt; function which interprets single operation over whole program. Mapping variant used ignores return values of subsequent &lt;code&gt;exec&lt;/code&gt; invocations since it does not return any useful data anyway.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; execSequence program = mapM_ exec program&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Utility functions for stack operations.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; push n = do state &lt;- get&lt;br /&gt;&gt;             put $ state { stack = n:(stack state) }&lt;br /&gt;&gt; &lt;br /&gt;&gt; pop = do state &lt;- get&lt;br /&gt;&gt;          case stack state of&lt;br /&gt;&gt;             (x:xs) -&gt; do put $ state { stack = xs }&lt;br /&gt;&gt;                          return x&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;And &lt;code&gt;exec&lt;/code&gt; implementation for different operations.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; -- (  -- n )&lt;br /&gt;&gt; exec (Number n) = push n&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( n --  )&lt;br /&gt;&gt; exec  Drop      = pop &gt;&gt; return ()    -- pop and ignore returned value&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( n -- n n )&lt;br /&gt;&gt; exec  Dup       = do x &lt;- pop&lt;br /&gt;&gt;                      push x&lt;br /&gt;&gt;                      push x&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( y x -- y+x )&lt;br /&gt;&gt; exec  Plus      = do x &lt;- pop&lt;br /&gt;&gt;                      y &lt;- pop&lt;br /&gt;&gt;                      push (y + x)&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( y x -- y-x )&lt;br /&gt;&gt; exec  Minus     = do x &lt;- pop&lt;br /&gt;&gt;                      y &lt;- pop&lt;br /&gt;&gt;                      push (y - x)&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( y x -- y*x )&lt;br /&gt;&gt; exec  Mul       = do x &lt;- pop&lt;br /&gt;&gt;                      y &lt;- pop&lt;br /&gt;&gt;                      push (y * x)&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( y x -- y/x )&lt;br /&gt;&gt; exec  Div       = do x &lt;- pop&lt;br /&gt;&gt;                      y &lt;- pop&lt;br /&gt;&gt;                      push (y `div` x)&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( y x -- y==x )&lt;br /&gt;&gt; exec  Equal     = do x &lt;- pop&lt;br /&gt;&gt;                      y &lt;- pop&lt;br /&gt;&gt;                      if y == x then push 1 else push 0&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( y x -- y&lt;x )&lt;br /&gt;&gt; exec  Less      = do x &lt;- pop&lt;br /&gt;&gt;                      y &lt;- pop&lt;br /&gt;&gt;                      if y &lt; x then push 1 else push 0&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( y x -- y&gt;x )&lt;br /&gt;&gt; exec  Greater   = do x &lt;- pop&lt;br /&gt;&gt;                      y &lt;- pop&lt;br /&gt;&gt;                      if y &gt; x then push 1 else push 0&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( x --  )&lt;br /&gt;&gt; exec  Out       = do x &lt;- pop&lt;br /&gt;&gt;                      liftIO $ (putStr (show x) &gt;&gt; putStr " " &gt;&gt; hFlush stdout)&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( x --  )&lt;br /&gt;&gt; exec  Emit      = do x &lt;- pop&lt;br /&gt;&gt;                      liftIO $ (putChar (chr x) &gt;&gt; hFlush stdout)&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- stack effect depends on invoked function&lt;br /&gt;&gt; exec (Call fn)  = do body &lt;- asks (fromJust . Map.lookup fn)&lt;br /&gt;&gt;                      execSequence body&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- (  -- I )&lt;br /&gt;&gt; exec  GetI      = do state &lt;- get&lt;br /&gt;&gt;                      push $ counter state&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( n m -- .. ) - n - high loop bound, m - low loop bound&lt;br /&gt;&gt; exec (Loop b)   = do low &lt;- pop&lt;br /&gt;&gt;                      high &lt;- pop&lt;br /&gt;&gt;                      if low &lt; high then mapM_ (doOnce b) [low .. high-1] else return () where&lt;br /&gt;&gt;                         doOnce program c = do state &lt;- get&lt;br /&gt;&gt;                                               put $ state { counter = c }&lt;br /&gt;&gt;                                               execSequence program&lt;br /&gt;&gt; &lt;br /&gt;&gt; -- ( x -- .. ) - if x != 0 then t else e&lt;br /&gt;&gt; exec (If t e)   = do x &lt;- pop&lt;br /&gt;&gt;                      if x /= 0 then execSequence t else execSequence e&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Time for a sample program to be executed. Program will calculate factorials of numbers from 1 to 7 and output them as lines with pairs number - factorial.&lt;br /&gt;&lt;br /&gt;Environment contains definition of a &lt;code&gt;factorial&lt;/code&gt; function that calculates factorial of an item on top of stack, that is used by the main program to perform calculations.&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; env = Map.fromList [ ("factorial",&lt;br /&gt;&gt;                           [ Dup&lt;br /&gt;&gt;                           , Number 1&lt;br /&gt;&gt;                           , Greater               -- if n &gt; 0&lt;br /&gt;&gt;                           , If [ Dup&lt;br /&gt;&gt;                                , Number 1&lt;br /&gt;&gt;                                , Minus&lt;br /&gt;&gt;                                , Call "factorial" -- factorial (n-1)&lt;br /&gt;&gt;                                , Mul              -- *&lt;br /&gt;&gt;                                ]&lt;br /&gt;&gt;                                [ Drop&lt;br /&gt;&gt;                                , Number 1&lt;br /&gt;&gt;                                ]                  -- else 1&lt;br /&gt;&gt;                           ])&lt;br /&gt;&gt;                    ]&lt;br /&gt;&gt; sample = [ Number 8&lt;br /&gt;&gt;          , Number 1&lt;br /&gt;&gt;          , Loop [ GetI&lt;br /&gt;&gt;                 , Dup&lt;br /&gt;&gt;                 , Out&lt;br /&gt;&gt;                 , Call "factorial"&lt;br /&gt;&gt;                 , Out&lt;br /&gt;&gt;                 , Number 10&lt;br /&gt;&gt;                 , Emit&lt;br /&gt;&gt;                 ]&lt;br /&gt;&gt;          ]&lt;br /&gt;&lt;br /&gt;&gt; main = do a &lt;- runForth env initState sample&lt;br /&gt;&gt;           return ()&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-5096264913104820273?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/5096264913104820273/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=5096264913104820273' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/5096264913104820273'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/5096264913104820273'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/02/playing-with-monad-transformers.html' title='Playing with monad transformers'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-4765810113989547637</id><published>2008-01-31T10:31:00.000+01:00</published><updated>2008-01-31T10:43:01.853+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ion3'/><category scheme='http://www.blogger.com/atom/ns#' term='lua'/><title type='text'>Applications menu in ion3</title><content type='html'>&lt;div style="text-align:justify"&gt;After some time spent using KDE I recently switched back to &lt;a href="http://modeemi.fi/~tuomov/ion/"&gt;ion3&lt;/a&gt;. One of the first things I realized after the switch was lack of KDE menu with all installed apps. Actually lack of any menu with all installed apps. I am too old to remember all the exec names I have installed, and way too lazy to add those manually to the ion menu, so I've written a script that parses all .desktop files from /usr/share/applications and adds (more or less) all the apps to the menu which can be invoked by Meta1-Esc (for me: alt-esc).&lt;br /&gt;&lt;br /&gt;You can download the script from &lt;a href="http://glum.website.pl/files/cfg_apps.lua"&gt;here&lt;/a&gt;. All you have to do is to save it in your ~/.ion3 directory and add &lt;code&gt;dopath("cfg_apps")&lt;/code&gt; to cfg_ion.lua.&lt;br /&gt;Menu is created during ion start, so if you install new apps you either have to logout/login, or restart ion for changes to be visible.&lt;br /&gt;&lt;br /&gt;Note: the script is quite lame, uses hardcoded list of categories, does not handle some Exec entries properly etc. I will be cleaning those up soon.&lt;br /&gt;&lt;br /&gt;Enjoy.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-4765810113989547637?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/4765810113989547637/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=4765810113989547637' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/4765810113989547637'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/4765810113989547637'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/01/applications-menu-in-ion3.html' title='Applications menu in ion3'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-3956258161632345342</id><published>2008-01-15T22:37:00.000+01:00</published><updated>2008-01-17T16:51:27.697+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='optimization'/><category scheme='http://www.blogger.com/atom/ns#' term='brainfuck'/><title type='text'>Brainfuck - it's the last one, I promise</title><content type='html'>&lt;div style="text-align: justify;"&gt;&lt;br /&gt;OK, I promise - this is the last version. What changed since last time:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;program is now stored in a list of [Op Word16] instead of the zipper - zipper is good for memory, where pointer moves in both directions, but in case of program that only goes in one direction it has an unnecessary overhead&lt;/li&gt;&lt;br /&gt;&lt;li&gt;parser clean-up + optimization of sequence of similar operation ("++++", "---", "&gt;&gt;&gt;", "&lt;&lt;&lt;") built-in into parser&lt;/li&gt;&lt;br /&gt;&lt;li&gt;precompiled code optimization based on common loop patterns such as setting current cell to 0, adding current cell to another etc&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Result - ca 5x speed-up on prime.bf. Other benchmarks also get some speed.&lt;br /&gt;&lt;br /&gt;Again, post is a literate Haskell source. Enjoy.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; module Main where&lt;br /&gt;&gt;&lt;br /&gt;&gt; import qualified Control.Exception as CE&lt;br /&gt;&gt; import Control.Monad&lt;br /&gt;&gt; import Data.Char&lt;br /&gt;&gt; import Data.Word&lt;br /&gt;&gt; import System.Environment&lt;br /&gt;&gt; import System.IO&lt;br /&gt;&gt; import Text.ParserCombinators.Parsec&lt;br /&gt;&lt;br /&gt;&gt; import Debug.Trace&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This version of interpreter makes use of "precompiled" and optimized Brainfuck code. Source file gets compiled to following set of operations:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data Op a =&lt;br /&gt;&gt;     Add a         -- add a to current memory cell (a can be negative)&lt;br /&gt;&gt;   | Move a        -- move pointer a positions to the right (to the left if a negative)&lt;br /&gt;&gt;   | Input         -- input one character and store it in memory&lt;br /&gt;&gt;   | Output        -- output current memory cell to stdout&lt;br /&gt;&gt;   | Loop [Op a]   -- loop with body consisting of given operations list&lt;br /&gt;&gt;   | Set a         -- OPTIMIZATION: set cell to value&lt;br /&gt;&gt;   | FarAdd a a    -- OPTIMIZATION: add k*value of current cell to cell 'a' away&lt;br /&gt;&gt;   | Scan a        -- OPTIMIZATION: search for 0 jumping x cells at a time&lt;br /&gt;&gt;       deriving (Show, Eq)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Source code parsing is done using simple grammar written with Parsec.&lt;br /&gt;&lt;br /&gt;The BNF form of grammar looks something like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;program     ::= instruction*&lt;br /&gt;instruction ::= loop | simple&lt;br /&gt;loop        ::= '[' instruction* ']'&lt;br /&gt;simple      ::= '+' | '-' | '&lt;' | '&gt;' | '.' | ','&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;which can be written in Parsec:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; program :: Parser [Op Int]&lt;br /&gt;&gt; program = many instruction&lt;br /&gt;&gt;&lt;br /&gt;&gt; instruction :: Parser (Op Int)&lt;br /&gt;&gt; instruction = simple &lt;|&gt; loop&lt;br /&gt;&gt;&lt;br /&gt;&gt; loop :: Parser (Op Int)&lt;br /&gt;&gt; loop = between (char '[') (char ']') program &gt;&gt;= \p -&gt; return $ Loop p&lt;br /&gt;&gt;&lt;br /&gt;&gt; simple :: Parser (Op Int)&lt;br /&gt;&gt; simple = (many1 (char '+') &gt;&gt;= \p -&gt; return $ Add (length p))&lt;br /&gt;&gt;      &lt;|&gt; (many1 (char '-') &gt;&gt;= \p -&gt; return $ Add (negate $ length p))&lt;br /&gt;&gt;      &lt;|&gt; (many1 (char '&gt;') &gt;&gt;= \p -&gt; return $ Move (length p))&lt;br /&gt;&gt;      &lt;|&gt; (many1 (char '&lt;') &gt;&gt;= \p -&gt; return $ Move (negate $ length p))&lt;br /&gt;&gt;      &lt;|&gt; (      (char '.') &gt;&gt;= \_ -&gt; return $ Output)&lt;br /&gt;&gt;      &lt;|&gt; (      (char ',') &gt;&gt;= \_ -&gt; return $ Input)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Data memory is handled using a zipper:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data ListZipper a = ListZipper {&lt;br /&gt;&gt;   left    :: ![a], -- elements left from focus&lt;br /&gt;&gt;   focus   :: ! a , -- current element&lt;br /&gt;&gt;   right   :: ![a]  -- elements right from focus&lt;br /&gt;&gt; } deriving Show&lt;br /&gt;&gt;&lt;br /&gt;&gt; move :: Int -&gt; ListZipper a -&gt; ListZipper a&lt;br /&gt;&gt; move (-1) (ListZipper (x:xs) y zz)  = ListZipper xs x (y:zz)&lt;br /&gt;&gt; move   1  (ListZipper xx y (z:zs))  = ListZipper (y:xx) z zs&lt;br /&gt;&gt; move   0  lz                        = lz&lt;br /&gt;&gt; move   n  lz = if n &gt; 0 then move (n-1) (move 1 lz)&lt;br /&gt;&gt;                         else move (n+1) (move (-1) lz)&lt;br /&gt;&gt;&lt;br /&gt;&gt; mkZipper :: [a] -&gt; ListZipper a&lt;br /&gt;&gt; mkZipper x = ListZipper [] (head x) (tail x)&lt;br /&gt;&gt;&lt;br /&gt;&gt; getValue :: ListZipper a -&gt; a&lt;br /&gt;&gt; getValue (ListZipper _ y _) = y&lt;br /&gt;&gt;&lt;br /&gt;&gt; setValue :: ListZipper a -&gt; a -&gt; ListZipper a&lt;br /&gt;&gt; setValue (ListZipper xx _ yy) v = ListZipper xx v yy&lt;br /&gt;&gt;&lt;br /&gt;&gt; {-# INLINE move     #-}&lt;br /&gt;&gt; {-# INLINE getValue #-}&lt;br /&gt;&gt; {-# INLINE setValue #-}&lt;br /&gt;&lt;br /&gt;&gt; scan :: Int -&gt; ListZipper Word16 -&gt; ListZipper Word16&lt;br /&gt;&gt; scan n lz@(ListZipper _ 0 _) = lz&lt;br /&gt;&gt; scan n lz = scan n (move n lz)&lt;br /&gt;&gt; &lt;br /&gt;&gt; addAt :: Int -&gt; Int -&gt; ListZipper Word16 -&gt; ListZipper Word16&lt;br /&gt;&gt; addAt n k lz@(ListZipper l v r) =&lt;br /&gt;&gt;       if v == 0 then lz&lt;br /&gt;&gt;       else let doAddAt 0 _ _ = error "Invalid use of doAddAt - 0 offset"&lt;br /&gt;&gt;                doAddAt 1 (x:xs) v = (v+x):xs&lt;br /&gt;&gt;                doAddAt n (x:xs) v = x:(doAddAt (n-1) xs v)&lt;br /&gt;&gt;                value  = fromIntegral k*v in&lt;br /&gt;&gt;            if n &gt; 0 then ListZipper l v (doAddAt n r value)&lt;br /&gt;&gt;            else ListZipper (doAddAt (-n) l value) v r&lt;br /&gt;&gt;&lt;br /&gt;&gt; {-# INLINE addAt #-}&lt;br /&gt;&gt; {-# INLINE scan  #-}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Time for main interpreter.&lt;br /&gt;&lt;br /&gt;Executing program transforms memory state (ListZipper Word16) into a new state with possible side-effects in I/O.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; runSequence :: ListZipper Word16 -&gt; [Op Int] -&gt; IO (ListZipper Word16)&lt;br /&gt;&gt; runSequence memory = foldM step memory&lt;br /&gt;&gt;&lt;br /&gt;&gt; step :: ListZipper Word16 -&gt; Op Int -&gt; IO (ListZipper Word16)&lt;br /&gt;&gt; step mem op = --trace (show op) $!&lt;br /&gt;&gt;   case op of&lt;br /&gt;&gt;       Move n      -&gt; return $! move n mem&lt;br /&gt;&gt;       Add n       -&gt; return $! setValue mem ((getValue mem) + (fromIntegral n))&lt;br /&gt;&gt;       Loop p      -&gt; doLoop p mem&lt;br /&gt;&gt;       Set n       -&gt; return $! setValue mem (fromIntegral n)&lt;br /&gt;&gt;       FarAdd n k  -&gt; return $! addAt n k mem&lt;br /&gt;&gt;       Scan n      -&gt; return $! scan n mem&lt;br /&gt;&gt;       Input       -&gt; CE.try getChar &gt;&gt;= \c -&gt;&lt;br /&gt;&gt;                         case c of&lt;br /&gt;&gt;                           Left err    -&gt; return $! setValue mem 0&lt;br /&gt;&gt;                           Right x     -&gt; return $! setValue mem (fromIntegral $ ord x)&lt;br /&gt;&gt;       Output      -&gt; hPutChar stdout (chr . fromEnum $ getValue mem) &gt;&gt; return mem&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I have enclosed loop body execution into a separate function to make it look more clear (for me at least). Algorithm is simple - if on entry to the loop current memory cell has non-zero value, the loop body is executed as if it was a separate program, and the resulting memory state is passed again to doLoop to check if another iteration should be performed.&lt;br /&gt;If value of current cell is zero program is skipped and function simply returns the same memory state that it got as parameter.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; doLoop :: [Op Int] -&gt; ListZipper Word16 -&gt; IO (ListZipper Word16)&lt;br /&gt;&gt; doLoop block memory = if (getValue memory) == 0 then return memory&lt;br /&gt;&gt;                                                 else runSequence memory block &gt;&gt;= doLoop block&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Optimization step - precompiled program is transformed according to following rules:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt; sequence of Add operations is replaced with single Add with argument being sum of arguments of input, for example [Add 1, Add1] will be replaced with [Add 2], [Add 1, Add 1, Add (-1)] with [Add 1] etc. Technically sequences of '+' or '-' are handled by Brainfuck parser, but it does not optimize intermixed '+' and '-' correctly, for example in "+-+-+-". This is actually programer's mistake, but can happen anyway.&lt;/li&gt;&lt;br /&gt;&lt;li&gt; sequence of Move operations is replaced with single Move in similar fashion - [Move (-1), Move (-1)] is replaced with [Move (-2)] etc. Situation here is similar to described above - parser does not optimize "&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;" properly.&lt;/li&gt;&lt;br /&gt;&lt;li&gt; empty operations are removed, for example Add 0, Move 0 or empty loop.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Optimizer also replaces common loop patterns with single opcodes:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt; Loop [Add (-1)] (filling current cell with 0) is replaced with Set 0.&lt;/li&gt;&lt;br /&gt;&lt;li&gt; Loop [Add (-1), Move n, Add k, Move (-n)] (addition of k*current to cell n away) replaced with [FarAdd n k, Set 0] (Set 0 is necessary, since side-effect of the original loop is setting current cell to 0). Also variant with Add (-1) at the end of the loop is recognized.&lt;/li&gt;&lt;br /&gt;&lt;li&gt; Loop [Add (-1), Move n1, Add k1, Move n2, Add k2, Move n3] if n1+n2+n3 == 0 (two additions  - k1*current to cell n1 away, k2*current to cell n1+n2) replaced with sequence of [FarAdd n1 k1, FarAdd (n1+n2) k2, Set 0]. Again, also variant with Add (-1) at the end is recognized.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; optimize :: [Op Int] -&gt; [Op Int]&lt;br /&gt;&gt; optimize []                       = []&lt;br /&gt;&gt; optimize ((Add 0)          :xs)   = optimize xs&lt;br /&gt;&gt; optimize ((Move 0)         :xs)   = optimize xs&lt;br /&gt;&gt; optimize ((Loop [])        :xs)   = optimize xs&lt;br /&gt;&gt; optimize ((Add x) :(Add y) :xs)   = optimize (Add (x+y) :xs)&lt;br /&gt;&gt; optimize ((Move x):(Move y):xs)   = optimize (Move (x+y):xs)&lt;br /&gt;&gt; optimize ((Set x) :(Add y) :xs)   = optimize (Set (x+y) :xs)&lt;br /&gt;&gt; optimize ((Set x) :(Set y) :xs)   = optimize (Set y     :xs)&lt;br /&gt;&gt; optimize ((Loop [Add (-1)]):xs)   = optimize (Set 0     :xs)&lt;br /&gt;&gt; optimize ((Loop [Move x])  :xs)   = optimize (Scan x    :xs)&lt;br /&gt;&gt; optimize ((Loop p)         :xs)   = let p' = optimize p in loopOptimize (Loop p') ++ (optimize xs)&lt;br /&gt;&gt; optimize (x                :xs)   = x:(optimize xs)&lt;br /&gt;&gt; &lt;br /&gt;&gt; loopOptimize x@(Loop [Add (-1), Move n1, Add k, Move n2]) =&lt;br /&gt;&gt;       if n1 == -n2 then [FarAdd n1 k, Set 0]&lt;br /&gt;&gt;                    else [x]&lt;br /&gt;&gt; loopOptimize x@(Loop [Move n1, Add k, Move n2, Add (-1)]) =&lt;br /&gt;&gt;       if n1 == -n2 then [FarAdd n1 k, Set 0]&lt;br /&gt;&gt;                    else [x]&lt;br /&gt;&gt; loopOptimize x@(Loop [Add (-1), Move n1, Add k1, Move n2, Add k2, Move n3]) =&lt;br /&gt;&gt;       if (n1+n2) == -n3 then [FarAdd n1 k1, FarAdd (n1+n2) k2, Set 0]&lt;br /&gt;&gt;                         else [x]&lt;br /&gt;&gt; loopOptimize x@(Loop [Move n1, Add k1, Move n2, Add k2, Move n3, Add (-1)]) =&lt;br /&gt;&gt;       if (n1+n2) == -n3 then [FarAdd n1 k1, FarAdd (n1+n2) k2, Set 0]&lt;br /&gt;&gt;                         else [x]&lt;br /&gt;&gt; loopOptimize x = [x]&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;doOptimize does optimization on its input as long as subsequent optimize calls result in any changes to the program.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; doOptimize p = let p' = optimize p in&lt;br /&gt;&gt;                   if p == p' then p else doOptimize p'&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Program "normalization", i.e. removal of all characters other than valid Brainfuck instructions.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; normalize :: String -&gt; String&lt;br /&gt;&gt; normalize program = filter (`elem` "+-&lt;&gt;[].,") program&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And finally main function.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; main :: IO ()&lt;br /&gt;&gt; main = do&lt;br /&gt;&gt;       args &lt;- getArgs&lt;br /&gt;&gt;       if length args &lt; 1&lt;br /&gt;&gt;           then fail "Please provide name of the program to run"&lt;br /&gt;&gt;           else do&lt;br /&gt;&gt;               prog &lt;- readFile (head args)&lt;br /&gt;&gt;               case (parse program "" . normalize) prog of&lt;br /&gt;&gt;                   Left err    -&gt; do putStr "Parse error at "&lt;br /&gt;&gt;                                     print err&lt;br /&gt;&gt;                   Right res   -&gt; do&lt;br /&gt;&gt;                                   runSequence (mkZipper (replicate 30000 0)) (doOptimize res)&lt;br /&gt;&gt;                                   return ()&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-3956258161632345342?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/3956258161632345342/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=3956258161632345342' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/3956258161632345342'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/3956258161632345342'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/01/brainfuck-its-last-one-i-promise.html' title='Brainfuck - it&apos;s the last one, I promise'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-8418488387287701685</id><published>2008-01-11T16:24:00.000+01:00</published><updated>2008-01-13T00:27:40.572+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='nirvana'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='brainfuck'/><title type='text'>The purest is "the bestest" - final attempt to Brainfuck in Haskell</title><content type='html'>&lt;div style="text-align: justify;"&gt;&lt;br /&gt;Ladies and gentelmen, time for the final attempt to writing a perfect implementation of Brainfuck interpreter in Haskel.&lt;br /&gt;&lt;br /&gt;First of all I must make a statement: all previous attempts were lame, wrong and the result sucked big time. And I shouldn't have published them to save myself embarrassment.&lt;br /&gt;The code was big, ugly and was imperative rather than functional. Luckily people suggested proper ways of doing things, and I must admit that I like the result. Thanks guys!&lt;br /&gt;&lt;br /&gt;This post is again a valid literate Haskell code, so you can just paste it into .lhs file and compile it.&lt;br /&gt;Let's do it.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; module Main where&lt;br /&gt;&gt;&lt;br /&gt;&gt; import qualified Control.Exception as CE&lt;br /&gt;&gt; import Data.Char&lt;br /&gt;&gt; import Data.Word&lt;br /&gt;&gt; import System.Environment&lt;br /&gt;&gt; import System.IO&lt;br /&gt;&gt; import Text.ParserCombinators.Parsec&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This version of interpreter makes use of "precompiled" and optimized Brainfuck code. Source file gets compiled to following set of operations:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data Op a =&lt;br /&gt;&gt;     Add a         -- add a to current memory cell (a can be negative)&lt;br /&gt;&gt;   | Move a        -- move pointer a positions to the right (to the left if a negative)&lt;br /&gt;&gt;   | Input         -- input one character and store it in memory&lt;br /&gt;&gt;   | Output        -- output current memory cell to stdout&lt;br /&gt;&gt;   | Loop [Op a]   -- loop with body consisting of given operations list&lt;br /&gt;&gt;   | End           -- guard used to detect end of program or loop body&lt;br /&gt;&gt;       deriving (Show, Eq)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;"End" is used as guard and is being placed at the end of each loop body and of the whole program. It is perfectly possible to get rid of this guard, but I somehow found it more pleasant to use than handling empty lists in zipper.&lt;br /&gt;&lt;br /&gt;Source code parsing is done using simple grammar written with Parsec.&lt;br /&gt;&lt;br /&gt;The BNF form of grammar looks something like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;program     ::= instruction*&lt;br /&gt;instruction ::= loop | simple&lt;br /&gt;loop        ::= '[' instruction* ']'&lt;br /&gt;simple      ::= '+' | '-' | '&lt;' | '&gt;' | '.' | ','&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;which can be written in Parsec:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; program :: Parser [Op Int]&lt;br /&gt;&gt; program = do { ins &lt;- instruction     -- instruction&lt;br /&gt;&gt;              ; do { rest &lt;- program   -- followed by more instructions&lt;br /&gt;&gt;                   ; return (ins:rest)&lt;br /&gt;&gt;                   }&lt;br /&gt;&gt;                &lt;|&gt;  return [ins]      -- or not - just single&lt;br /&gt;&gt;              }&lt;br /&gt;&gt;           &lt;|&gt; return [End]            -- empty program / end of list&lt;br /&gt;&gt;&lt;br /&gt;&gt; instruction :: Parser (Op Int)&lt;br /&gt;&gt; instruction = simple &lt;|&gt; loop&lt;br /&gt;&gt;&lt;br /&gt;&gt; loop :: Parser (Op Int)&lt;br /&gt;&gt; loop = do { char '['&lt;br /&gt;&gt;           ; body &lt;- program&lt;br /&gt;&gt;           ; char ']'&lt;br /&gt;&gt;           ; return (Loop body)&lt;br /&gt;&gt;           }&lt;br /&gt;&gt;&lt;br /&gt;&gt; simple :: Parser (Op Int)&lt;br /&gt;&gt; simple = do { ins &lt;- oneOf "+-&lt;&gt;.,"&lt;br /&gt;&gt;             ; let op = case ins of&lt;br /&gt;&gt;                           '+' -&gt; Add 1&lt;br /&gt;&gt;                           '-' -&gt; Add (-1)&lt;br /&gt;&gt;                           '&lt;' -&gt; Move (-1)&lt;br /&gt;&gt;                           '&gt;' -&gt; Move 1&lt;br /&gt;&gt;                           '.' -&gt; Output&lt;br /&gt;&gt;                           ',' -&gt; Input&lt;br /&gt;&gt;             ; return op&lt;br /&gt;&gt;             }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To make things as pure and as functional as possible both program and memory handling will be implemented using zipper. Really cool technique!&lt;br /&gt;Note: it could also be implemented in the way suggested by lasts &lt;a href="http://hpaste.org/4874"&gt;here&lt;/a&gt;, i.e. without "focus" field. I don't know it that would be faster or not, but I like the way with explicitly declaring focus field.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data ListZipper a = ListZipper {&lt;br /&gt;&gt;   left    :: ![a], -- elements left from focus&lt;br /&gt;&gt;   focus   :: ! a , -- current element&lt;br /&gt;&gt;   right   :: ![a]  -- elements right from focus&lt;br /&gt;&gt; } deriving Show&lt;br /&gt;&gt;&lt;br /&gt;&gt; move :: Int -&gt; ListZipper a -&gt; ListZipper a&lt;br /&gt;&gt; move (-1) (ListZipper (x:xs) y zz)  = ListZipper xs x (y:zz)&lt;br /&gt;&gt; move   1  (ListZipper xx y (z:zs))  = ListZipper (y:xx) z zs&lt;br /&gt;&gt; move   0  lz                        = lz&lt;br /&gt;&gt; -- note: move for abs(n)&gt;1 is done in such way, because it is faster than&lt;br /&gt;&gt; -- interpretation of a series of '&gt;'/'&lt;' opcodes and than trying to implement&lt;br /&gt;&gt; -- such operation using take/drop/splitAt etc.&lt;br /&gt;&gt; move   n  lz = if n &gt; 0 then move (n-1) (move 1 lz)&lt;br /&gt;&gt;                         else move (n+1) (move (-1) lz)&lt;br /&gt;&gt;&lt;br /&gt;&gt; mkZipper :: [a] -&gt; ListZipper a&lt;br /&gt;&gt; mkZipper x = ListZipper [] (head x) (tail x)&lt;br /&gt;&gt;&lt;br /&gt;&gt; getValue :: ListZipper a -&gt; a&lt;br /&gt;&gt; getValue (ListZipper _ y _) = y&lt;br /&gt;&gt;&lt;br /&gt;&gt; setValue :: ListZipper a -&gt; a -&gt; ListZipper a&lt;br /&gt;&gt; setValue (ListZipper xx _ yy) v = ListZipper xx v yy&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Time for main interpreter.&lt;br /&gt;&lt;br /&gt;"runSequence" executes program inside ListZipper until it reaches "End" marker. Executing program transforms memory state (ListZipper Word16) into a new state with possible side-effects in I/O.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; runSequence :: ListZipper (Op Int) -&gt; ListZipper Word16 -&gt; IO (ListZipper Word16)&lt;br /&gt;&gt; runSequence program memory =&lt;br /&gt;&gt;       if (focus program) == End&lt;br /&gt;&gt;               then return memory&lt;br /&gt;&gt;               else do mem' &lt;- step (focus program) memory&lt;br /&gt;&gt;                       runSequence (move 1 program) mem'&lt;br /&gt;&gt;&lt;br /&gt;&gt; step :: Op Int -&gt; ListZipper Word16 -&gt; IO (ListZipper Word16)&lt;br /&gt;&gt; step op mem =&lt;br /&gt;&gt;   case op of&lt;br /&gt;&gt;       Move n  -&gt; return $ move n mem&lt;br /&gt;&gt;       Add n   -&gt; return $ setValue mem ((getValue mem) + (fromIntegral n))&lt;br /&gt;&gt;       Loop p  -&gt; doLoop (mkZipper p) mem&lt;br /&gt;&gt;       Input   -&gt; do c &lt;- CE.try getChar&lt;br /&gt;&gt;                     case c of&lt;br /&gt;&gt;                       Left err    -&gt; return $ setValue mem 0&lt;br /&gt;&gt;                       Right x     -&gt; return $ setValue mem (fromIntegral $ fromEnum x)&lt;br /&gt;&gt;       Output  -&gt; do hPutChar stdout (chr $ fromEnum $ getValue mem)&lt;br /&gt;&gt;                     hFlush stdout&lt;br /&gt;&gt;                     return mem&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I have enclosed loop body execution into a separate function to make it look more clear (for me at least). Algorithm is simple - if on entry to the loop current memory cell has non-zero value, the loop body is executed as if it was a separate program, and the resulting memory state is passed again to doLoop to check if another iteration should be performed.&lt;br /&gt;If value of current cell is zero program is skipped and function simply returns the same memory state that it got as parameter.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; doLoop :: ListZipper (Op Int) -&gt; ListZipper Word16 -&gt; IO (ListZipper Word16)&lt;br /&gt;&gt; doLoop block memory = if (getValue memory) == 0 then return memory&lt;br /&gt;&gt;                                                 else do mem' &lt;- runSequence block memory&lt;br /&gt;&gt;                                                         doLoop block mem'&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Optimization step - precompiled program is transformed according to following rules:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt; sequence of Add operations is replaced with single Add with argument being sum of arguments of input, for example [Add 1, Add1] will be replaced with [Add 2], [Add 1, Add 1, Add (-1)] with [Add 1] etc&lt;/li&gt;&lt;br /&gt;&lt;li&gt; sequence of Move operations is replaced with single Move in similar fashion - [Move (-1), Move (-1)] is replaced with [Move (-2)] etc&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; optimize :: [Op Int] -&gt; [Op Int]&lt;br /&gt;&gt; optimize [] = []&lt;br /&gt;&gt; optimize (x:[]) = [x]&lt;br /&gt;&gt; optimize ((Add x):(Add y):xs) = optimize ((Add (x+y)):xs)&lt;br /&gt;&gt; optimize ((Move x):(Move y):xs) = optimize ((Move (x+y)):xs)&lt;br /&gt;&gt; optimize ((Loop p):xs) = (Loop (optimize p)):(optimize xs)&lt;br /&gt;&gt; optimize (x:xs) = x:(optimize xs)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Program "normalization", i.e. removal of all characters other than valid Brainfuck instructions.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; normalize :: String -&gt; String&lt;br /&gt;&gt; normalize program = filter (\c -&gt; elem c "+-&lt;&gt;[].,") program&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And finally main function.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; main :: IO ()&lt;br /&gt;&gt; main = do&lt;br /&gt;&gt;       args &lt;- getArgs&lt;br /&gt;&gt;       if length args &lt; 1&lt;br /&gt;&gt;           then fail "Please provide name of the program to run"&lt;br /&gt;&gt;           else do&lt;br /&gt;&gt;               prog &lt;- readFile (head args)&lt;br /&gt;&gt;               case (parse program "" (normalize prog)) of&lt;br /&gt;&gt;                   Left err    -&gt; do putStr "Parse error at "&lt;br /&gt;&gt;                                     print err&lt;br /&gt;&gt;                   Right res   -&gt; do runSequence (mkZipper (optimize res)) (mkZipper (take 30000 $ repeat 0))&lt;br /&gt;&gt;                                     return ()&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Result:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;time (echo 30 | ./Main  ../tests/prime.bf) &lt;br /&gt;Primes up to: 2 3 5 7 11 13 17 19 23 29 &lt;br /&gt;&lt;br /&gt;real    0m0.267s&lt;br /&gt;user    0m0.239s&lt;br /&gt;sys     0m0.012s&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Yep, that's right! Cleaner, better code being ca 30% faster than semi-imperative code using unboxed types!&lt;br /&gt;Note, that it can be sped up a little bit more - marking ListZipper fields as strict and passing -funbox-strict-fields to ghc drops time to 0.2s.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;EDIT: original post missed one quite important line - optimize ((Loop p):xs) ... . Without it optimizations are not propagated inside loops, hence in any non-trivial program they have basically no effect.&lt;br /&gt;&lt;br /&gt;This is corrected now.&lt;/i&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-8418488387287701685?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/8418488387287701685/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=8418488387287701685' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/8418488387287701685'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/8418488387287701685'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/01/purest-is-bestest-final-attempt-to.html' title='The purest is &quot;the bestest&quot; - final attempt to Brainfuck in Haskell'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-2235668535143062964</id><published>2008-01-10T07:19:00.000+01:00</published><updated>2008-01-10T08:28:07.650+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='brainfuck'/><title type='text'>Brainfuck in Haskell - speed gives me what I need</title><content type='html'>&lt;div style="text-align: justify;"&gt;&lt;br /&gt;Today it's time for final two optimizations to our interpreter. Initially I planned to use mutable unboxed array (access time of O(1), in-place updates, unboxed elements), but I was given a nicer, more functional solution - list zipper.&lt;br /&gt;I must admit I didn't know this technique, but it looks nice.&lt;br /&gt;&lt;br /&gt;First of all I will change benchmarking procedure a little bit - calculation of primes up to 10 has gotten too fast (ca 0.15s) to be measured accurately, so from now on benchmark will be finding primes up to 30, which gives us a nice timing of ca 3.5s:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt; time (echo 30 | ./Main +RTS -p -RTS prime.bf)&lt;br /&gt;Primes up to: 2 3 5 7 11 13 17 19 23 29 &lt;br /&gt;&lt;br /&gt;real    0m3.626s&lt;br /&gt;user    0m3.480s&lt;br /&gt;sys     0m0.099s&lt;br /&gt;&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;with profile:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;COST CENTRE                    MODULE               %time %alloc&lt;br /&gt;&lt;br /&gt;run                            Main                  41.2   32.8&lt;br /&gt;step                           Main                  24.2   37.8&lt;br /&gt;setMem                         Main                  21.6   21.3&lt;br /&gt;getMem                         Main                   5.9    0.0&lt;br /&gt;!!!                            Main                   3.3    2.5&lt;br /&gt;nextPC                         Main                   2.6    5.5&lt;br /&gt;isEnd                          Main                   1.3    0.0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Zipper is a structure that "simulates" movement over list elements. It holds three things - current element (focus), list of elements on the left of focus and list of elements on the right.&lt;br /&gt;Moving left or right means simply changing focus and adding/removing single element from left/right lists.&lt;br /&gt;Here it is wrapped in code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data ListZipper a = ListZipper {&lt;br /&gt;&gt;   left    :: [a], -- elements on the left from focus&lt;br /&gt;&gt;   focus   :: a,   -- current element&lt;br /&gt;&gt;   right   :: [a]  -- elements on the right&lt;br /&gt;&gt; }&lt;br /&gt;&gt;&lt;br /&gt;&gt; moveLeft :: ListZipper a -&gt; ListZipper a&lt;br /&gt;&gt; moveLeft (ListZipper (x:xs) y zs) = ListZipper xs x (y:zs)&lt;br /&gt;&gt;&lt;br /&gt;&gt; moveRight :: ListZipper a -&gt; ListZipper a&lt;br /&gt;&gt; moveRight (ListZipper xs y (z:zs)) = ListZipper (y:xs) z zs&lt;br /&gt;&gt;&lt;br /&gt;&gt; setValue :: ListZipper a -&gt; a -&gt; ListZipper a&lt;br /&gt;&gt; setValue (ListZipper xs y zs) v = ListZipper xs v zs&lt;br /&gt;&gt;&lt;br /&gt;&gt; getValue :: ListZipper a -&gt; a&lt;br /&gt;&gt; getValue (ListZipper _ y _) = y&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We also need to change definition of BFState and functions operating on memory, namely setMem and getMem:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data BFState = BFState {&lt;br /&gt;&gt;   program     :: BS.ByteString,       -- program being interpreted&lt;br /&gt;&gt;   memory      :: ListZipper Word16,   -- memory&lt;br /&gt;&gt;   pc          :: Int,                 -- current program counter&lt;br /&gt;&gt;&lt;br /&gt;&gt;   prog_len    :: Int,                 -- cached program length&lt;br /&gt;&gt;   loop_ends   :: UArray Int Int       -- cached loop ends&lt;br /&gt;&gt; }&lt;br /&gt;&lt;br /&gt;&gt; initState :: BS.ByteString -&gt; Int -&gt; BFState&lt;br /&gt;&gt; initState program memSize = BFState program mem 0 (BS.length program) loops where&lt;br /&gt;&gt;               loops = (listArray (0, (BS.length program - 1)) $ loopEnds program 0)&lt;br /&gt;&gt;               mem = ListZipper [] 0 (take (memSize - 1) $ repeat 0)&lt;br /&gt;&lt;br /&gt;&gt; getMem :: BFState -&gt; Word16&lt;br /&gt;&gt; getMem st = getValue (memory st)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&gt; setMem :: BFState -&gt; Word16 -&gt; ListZipper Word16&lt;br /&gt;&gt; setMem st value = setValue (memory st) value&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The benchmark:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt; ghc --make -prof -auto-all -O2 Main.lhs &lt;br /&gt;[1 of 1] Compiling Main             ( Main.lhs, Main.o )&lt;br /&gt;Linking Main ...&lt;br /&gt;&gt; time (echo 30 | ./Main +RTS -p -RTS prime.bf)&lt;br /&gt;Primes up to: 2 3 5 7 11 13 17 19 23 29 &lt;br /&gt;&lt;br /&gt;real    0m2.673s&lt;br /&gt;user    0m2.511s&lt;br /&gt;sys     0m0.108s&lt;br /&gt;&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Over 25% faster - nice! (&lt;i&gt;side note: IOUArray is a little bit faster - you can gain another 10% - but it is not that much, so let's stick with purer solution&lt;/i&gt;)&lt;br /&gt;&lt;br /&gt;The profile does not give us much clue of what to do to make it faster&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;COST CENTRE                    MODULE               %time %alloc&lt;br /&gt;&lt;br /&gt;run                            Main                  52.1   40.6&lt;br /&gt;step                           Main                  28.2   43.7&lt;br /&gt;nextPC                         Main                   5.1    6.8&lt;br /&gt;isEnd                          Main                   3.4    0.0&lt;br /&gt;getValue                       Main                   3.4    0.0&lt;br /&gt;getMem                         Main                   2.6    0.0&lt;br /&gt;moveLeft                       Main                   1.7    2.4&lt;br /&gt;!!!                            Main                   1.7    3.1&lt;br /&gt;moveRight                      Main                   0.0    2.4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;but there is one more thing to look at - while interpreting Brainfuck program all characters that are not valid BF instructions are being ignored, i.e. their execution is limited to advancing program counter. This happens each time such character is encountered, possibly multiple times during program run.&lt;br /&gt;If those characters do nothing, we can simply filter them away before we start interpreting program. It should give us some speed gain (especially if you look inside prime.bf - a LOT of whitespace and some comments).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; normalize :: BS.ByteString -&gt; BS.ByteString&lt;br /&gt;&gt; normalize program = BS.filter (\c -&gt; elem (chr $ fromEnum c) "+-[]&lt;&gt;.,") program&lt;br /&gt;&lt;br /&gt;&gt; main :: IO ()&lt;br /&gt;&gt; main  = do&lt;br /&gt;&gt;       args &lt;- getArgs&lt;br /&gt;&gt;       if length args == 0 then fail "Please provide name of the program to execute"&lt;br /&gt;&gt;                           else do program &lt;- BS.readFile (head args)&lt;br /&gt;&gt;                                   run (initState (normalize program) 30000)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Results:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt; ghc --make -prof -auto-all -O2 Main.lhs &lt;br /&gt;[1 of 1] Compiling Main             ( Main.lhs, Main.o )&lt;br /&gt;Linking Main ...&lt;br /&gt;&gt; time (echo 30 | ./Main +RTS -p -RTS prime.bf)&lt;br /&gt;Primes up to: 2 3 5 7 11 13 17 19 23 29 &lt;br /&gt;&lt;br /&gt;real    0m1.734s&lt;br /&gt;user    0m1.658s&lt;br /&gt;sys     0m0.054s&lt;br /&gt;&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Another 1s shaved off.&lt;br /&gt;&lt;br /&gt;Some additional notes:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt; times above have profiling overhead. If compiled without profiling ("-prof -auto-all") and run without "+RTS -p -RTS" time drops to ca 0.38s&lt;/li&gt;&lt;br /&gt;&lt;li&gt; interpreter can be about twice faster with some more hacking - using IOUArray instead of zipper for memory, making use of strict markers on fields (pc, prog_len) + passing -funbox-strict-fields to compiler etc&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-2235668535143062964?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/2235668535143062964/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=2235668535143062964' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/2235668535143062964'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/2235668535143062964'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/01/brainfuck-in-haskell-speed-gives-me.html' title='Brainfuck in Haskell - speed gives me what I need'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-1467752745719333843</id><published>2008-01-06T08:22:00.000+01:00</published><updated>2008-01-06T09:00:12.770+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='brainfuck'/><title type='text'>Brainfuck in Haskell - optimizations part 2</title><content type='html'>&lt;div style="text-align: justify;"&gt;&lt;br /&gt;In previous post we went down with execution time of prime.bf benchmark from 14s down to 4s. The profile result was:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;COST CENTRE                    MODULE               %time %alloc&lt;br /&gt;&lt;br /&gt;step                           Main                  85.6   42.1&lt;br /&gt;findMatchingForward            Main                   5.0    0.5&lt;br /&gt;findMatchingBackwards          Main                   5.0    0.5&lt;br /&gt;loopEnds                       Main                   3.0    0.6&lt;br /&gt;run                            Main                   1.5   30.2&lt;br /&gt;setMem                         Main                   0.0   18.3&lt;br /&gt;nextPC                         Main                   0.0    7.4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It does not show us directly what operation consumes most CPU ("step" is quite complex function), but we can do some guessing. First of all program is operating a lot on strings, mostly accessing random element of the string (!! operator). Strings in Haskell are a list of char, which makes this operation O(n).&lt;br /&gt;Haskell provides a nice data type that could be used as replacement - ByteString.&lt;br /&gt;&lt;br /&gt;First we need to add an import (it has to be qualified since there are name conflicts with Prelude):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; import qualified Data.ByteString as BS&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now we can replace all occurrences of String (since its used only for program) with BS.ByteString, all "length program" with "BS.length program" and "readFile" with "BS.readFile" (which reads file contents as ByteString). The only thing left is !! operator, which is not defined for ByteString.&lt;br /&gt;Function for accessing random elements is called "index", but unfortunately it returns Word8 instead of Char, so we would have to change character constants in code with its numerical equivalents. To prevent this we'll write our own operator that returns ByteString element as Char:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; (!!!) :: BS.ByteString -&gt; Int -&gt; Char&lt;br /&gt;&gt; (!!!) bs ind = chr $ fromEnum $ BS.index bs ind&lt;br /&gt;&gt;&lt;br /&gt;&gt; infix 9 !!!&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The last line makes !!! an infix operator with the same priority as string's !!.&lt;br /&gt;&lt;br /&gt;Now we can safely replace !! with !!! in all places where we are selecting program character ("program !! pos" with "program !!! pos", "program st !! pc st" with "program st !!! pc st") and do some benchmarking.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt; ghc --make -prof -auto-all -O2 Main.lhs &lt;br /&gt;[1 of 1] Compiling Main             ( Main.lhs, Main.o )&lt;br /&gt;Linking Main ...&lt;br /&gt;&gt; time (echo 10 | ./Main +RTS -p -RTS prime.bf)&lt;br /&gt;Primes up to: 2 3 5 7 &lt;br /&gt;&lt;br /&gt;real    0m0.694s&lt;br /&gt;user    0m0.517s&lt;br /&gt;sys     0m0.020s&lt;br /&gt;&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Not bad - almost 6 times faster.&lt;br /&gt;&lt;br /&gt;There is a similar situation with loop_ends and with memory - we often access random elements of a list. Plus list elements are boxed in Haskell objects, which generates additional cost when operating on them.&lt;br /&gt;loop_ends is the easiest to improve, since the array is immutable, so we can replace it with Haskell's type for immutable arrays of unboxed objects (UArray), which additionally has O(1) access time for its elements.&lt;br /&gt;&lt;br /&gt;Necessary changes:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; import Data.Array.Unboxed&lt;br /&gt;&lt;br /&gt;&gt; data BFState = BFState {&lt;br /&gt;&gt;   program     :: BS.ByteString,   -- program being interpreted&lt;br /&gt;&gt;   memory      :: [Word16],        -- memory&lt;br /&gt;&gt;   pc          :: Int,             -- current program counter&lt;br /&gt;&gt;   pos         :: Int,             -- current pointer position&lt;br /&gt;&gt;&lt;br /&gt;&gt;   prog_len    :: Int,             -- cached program length&lt;br /&gt;&gt;   loop_ends   :: UArray Int Int   -- cached loop ends&lt;br /&gt;&gt; }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&gt; initState :: BS.ByteString -&gt; Int -&gt; BFState&lt;br /&gt;&gt; initState program memSize = BFState program (take memSize $ repeat 0) 0 0 (BS.length program)&lt;br /&gt;&gt;               (listArray (0, (BS.length program - 1)) $ loopEnds program 0)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Plus change of all "(loop_ends st) !! (pc st)" with "(loop_ends st) ! (pc st)", since UArray uses ! to access elements.&lt;br /&gt;&lt;br /&gt;As simple as that.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt; ghc --make -prof -auto-all -O2 Main.lhs &lt;br /&gt;[1 of 1] Compiling Main             ( Main.lhs, Main.o )&lt;br /&gt;Linking Main ...&lt;br /&gt;&gt; time (echo 10 | ./Main +RTS -p -RTS prime.bf)&lt;br /&gt;Primes up to: 2 3 5 7 &lt;br /&gt;&lt;br /&gt;real    0m0.137s&lt;br /&gt;user    0m0.101s&lt;br /&gt;sys     0m0.012s&lt;br /&gt;&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Wow! Another 5 times faster! Profile:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;COST CENTRE                    MODULE               %time %alloc&lt;br /&gt;&lt;br /&gt;step                           Main                  40.0   41.1&lt;br /&gt;setMem                         Main                  40.0   15.3&lt;br /&gt;getMem                         Main                  20.0    0.0&lt;br /&gt;run                            Main                   0.0   32.4&lt;br /&gt;nextPC                         Main                   0.0    6.2&lt;br /&gt;!!!                            Main                   0.0    3.3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As you can see getMem and setMem operations are now taking ca 60% of program run time, and we know exactly why - accessing random elements of a list.&lt;br /&gt;Unfortunately memory is mutable, so we cannot replace it as easily as we did with loop_ends. Haskell does have a proper type, namely mutable arrays of unboxed elements (IOUArray) and that's what we will use next time.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-1467752745719333843?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/1467752745719333843/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=1467752745719333843' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/1467752745719333843'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/1467752745719333843'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/01/brainfuck-in-haskell-optimizations-part.html' title='Brainfuck in Haskell - optimizations part 2'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-7868849116500938014</id><published>2008-01-02T08:13:00.000+01:00</published><updated>2008-01-07T07:46:15.983+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='brainfuck'/><title type='text'>Brainfuck in Haskell - time for some optimization</title><content type='html'>&lt;div style="text-align: justify;"&gt;&lt;br /&gt;In the &lt;a href="http://sabbatical-year.blogspot.com/2007/12/brainfuck-interpreter-in-haskell.html"&gt;previous post&lt;/a&gt; we wrote an extremely slow, but working, Brainfuck interpreter in Haskell. Now it's time to make it a little bit faster. (Side note: this post will not be a valid literate Haskell program. It describes changes to the origital + command line operations.)&lt;br /&gt;&lt;br /&gt;GHC gives us a nice profiler that makes it easy to spot most of the performance issues, and that's what we will use.&lt;br /&gt;For benchmarking I'll use &lt;a href="http://esoteric.sange.fi/brainfuck/bf-source/prog/PRIME.BF"&gt;prime.bf&lt;/a&gt; finding prime numbers up to, let's say, 10.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt; ghc --make -prof -auto-all -O2 Main.lhs &lt;br /&gt;[1 of 1] Compiling Main             ( Main.lhs, Main.o )&lt;br /&gt;Linking Main ...&lt;br /&gt;&gt; time (echo 10 | ./Main +RTS -p -RTS prime.bf)&lt;br /&gt;Primes up to: 2 3 5 7 &lt;br /&gt;&lt;br /&gt;real    0m14.244s&lt;br /&gt;user    0m14.010s&lt;br /&gt;sys     0m0.092s&lt;br /&gt;&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Profiling results are in Main.prof. Here's a snippet that shows our hot spots:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;COST CENTRE                    MODULE               %time %alloc&lt;br /&gt;&lt;br /&gt;findMatchingBackwards          Main                  32.3   13.0&lt;br /&gt;isEnd                          Main                  30.9    0.0&lt;br /&gt;step                           Main                  21.3   33.8&lt;br /&gt;findMatchingForward            Main                  14.9    5.6&lt;br /&gt;setMem                         Main                   0.4   19.5&lt;br /&gt;run                            Main                   0.1   19.7&lt;br /&gt;nextPC                         Main                   0.0    8.0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;It seems that the easiest thing to get rid of is isEnd function - each time it is called it calculates length of the program (string =&gt; list of char =&gt; length's complexity is O(n)). Program does not change over time, so we can simply cache program length in BFState and compare pc against this cached value.&lt;br /&gt;Changes are needed in definition of BFState:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data BFState = BFState {&lt;br /&gt;&gt;   program     :: String,      -- program being interpreted&lt;br /&gt;&gt;   memory      :: [Word16],    -- memory&lt;br /&gt;&gt;   pc          :: Int,         -- current program counter&lt;br /&gt;&gt;   pos         :: Int,         -- current pointer position&lt;br /&gt;&gt;&lt;br /&gt;&gt;   prog_len    :: Int          -- cached program length&lt;br /&gt;&gt; }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;in initState function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; initState :: String -&gt; Int -&gt; BFState&lt;br /&gt;&gt; initState program memSize = BFState program (take memSize $ repeat 0) 0 0 (length program)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and in isEnd itself:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; isEnd :: BFState -&gt; Bool&lt;br /&gt;&gt; isEnd st  = (pc st) &gt;= (prog_len st)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Result:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt; ghc --make -prof -auto-all -O2 Main.lhs &lt;br /&gt;[1 of 1] Compiling Main             ( Main.lhs, Main.o )&lt;br /&gt;Linking Main ...&lt;br /&gt;&gt; time (echo 10 | ./Main +RTS -p -RTS prime.bf)&lt;br /&gt;Primes up to: 2 3 5 7 &lt;br /&gt;&lt;br /&gt;real    0m9.387s&lt;br /&gt;user    0m9.234s&lt;br /&gt;sys     0m0.058s&lt;br /&gt;&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So it worked as expected - ca 30% performance gain. Current performance bottlenecks:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;COST CENTRE                    MODULE               %time %alloc&lt;br /&gt;&lt;br /&gt;findMatchingBackwards          Main                  47.5   11.4&lt;br /&gt;step                           Main                  26.8   34.3&lt;br /&gt;findMatchingForward            Main                  24.4    4.9&lt;br /&gt;setMem                         Main                   0.9   17.0&lt;br /&gt;run                            Main                   0.4   25.1&lt;br /&gt;nextPC                         Main                   0.0    7.0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;70% of the time is spent looking for matching '[' and ']' in loops.&lt;br /&gt;Situation here is similar to isEnd - program does not change over time, so it should not be a problem to cache somehow findMatching results.&lt;br /&gt;&lt;br /&gt;The simplest way is to create a table of the same length as program, where on a position corresponding to '[' we will have index of matching ']', and on position corresponding to ']' we will have index of matching '[', for example table for program &lt;tt&gt;"++[&gt;+&lt;-]"&lt;/tt&gt; will look like &lt;tt&gt;[0, 0, 7, 0, 0, 0, 0, 2]&lt;/tt&gt; (values for characters other than '[' and ']' do not matter, so I put 0 there).&lt;br /&gt;&lt;br /&gt;Function generating such table could look like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; loopEnds :: String -&gt; Int -&gt; [Int]&lt;br /&gt;&gt; loopEnds program pos =&lt;br /&gt;&gt;   if (pos &gt;= length program) then []&lt;br /&gt;&gt;   else end:(loopEnds program (pos+1)) where&lt;br /&gt;&gt;       end = case (program !! pos) of&lt;br /&gt;&gt;               '[' -&gt; findMatchingForward program pos&lt;br /&gt;&gt;               ']' -&gt; findMatchingBackwards program pos&lt;br /&gt;&gt;               otherwise -&gt; 0&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Of course we need also changes in BFState:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data BFState = BFState {&lt;br /&gt;&gt;   program     :: String,      -- program being interpreted&lt;br /&gt;&gt;   memory      :: [Word16],    -- memory&lt;br /&gt;&gt;   pc          :: Int,         -- current program counter&lt;br /&gt;&gt;   pos         :: Int,         -- current pointer position&lt;br /&gt;&gt;&lt;br /&gt;&gt;   prog_len    :: Int,         -- cached program length&lt;br /&gt;&gt;   loop_ends   :: [Int]        -- cached loop ends&lt;br /&gt;&gt; }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;initState:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; initState :: String -&gt; Int -&gt; BFState&lt;br /&gt;&gt; initState program memSize = BFState program (take memSize $ repeat 0) 0 0 (length program) (loopEnds program 0)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and in implementation of '[' and ']' in step:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;       '[' -&gt; return st { pc = pc' } where&lt;br /&gt;&gt;               pc' = if getMem st == 0 then ((loop_ends st) !! (pc st)) + 1&lt;br /&gt;&gt;                                       else nextPC st&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;       ']' -&gt; return st { pc = pc' } where&lt;br /&gt;&gt;               pc' = (loop_ends st) !! (pc st)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Results:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt; ghc --make -prof -auto-all -O2 Main.lhs &lt;br /&gt;[1 of 1] Compiling Main             ( Main.lhs, Main.o )&lt;br /&gt;Linking Main ...&lt;br /&gt;&gt; time (echo 10 | ./Main +RTS -p -RTS prime.bf)&lt;br /&gt;Primes up to: 2 3 5 7 &lt;br /&gt;&lt;br /&gt;real    0m4.090s&lt;br /&gt;user    0m4.003s&lt;br /&gt;sys     0m0.032s&lt;br /&gt;&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With current profile:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;COST CENTRE                    MODULE               %time %alloc&lt;br /&gt;&lt;br /&gt;step                           Main                  85.6   42.1&lt;br /&gt;findMatchingForward            Main                   5.0    0.5&lt;br /&gt;findMatchingBackwards          Main                   5.0    0.5&lt;br /&gt;loopEnds                       Main                   3.0    0.6&lt;br /&gt;run                            Main                   1.5   30.2&lt;br /&gt;setMem                         Main                   0.0   18.3&lt;br /&gt;nextPC                         Main                   0.0    7.4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Not bad. 14s down to 4s. Still way slower than C version, but getting better.&lt;br /&gt;&lt;br /&gt;In the next part - some more optimizations, this time mostly on data types used.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-7868849116500938014?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/7868849116500938014/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=7868849116500938014' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/7868849116500938014'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/7868849116500938014'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2008/01/brainfuck-in-haskell-time-for-some.html' title='Brainfuck in Haskell - time for some optimization'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-2659372621696326944</id><published>2007-12-30T11:44:00.000+01:00</published><updated>2007-12-30T12:09:31.348+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='brainfuck'/><title type='text'>Brainfuck interpreter in Haskell</title><content type='html'>&lt;div style="text-align: justify;"&gt;One of the biggest problems I have learning Haskell is working with state and with I/O. Playing with simple tasks that involve simple transformations of one value to another or generating some sets of values (factorial, sorting, fibonacci, simple grep) is fun, but in reality almost every program needs some internal state to be stored and modified as the program goes.&lt;br /&gt;To exercise state handling I came up with an idea - why not implement an interpreter of some simple language, like &lt;a href="http://en.wikipedia.org/wiki/Brainfuck"&gt;Brainfuck&lt;/a&gt;? It has only a few instructions, state is simple to describe, and it's fun.&lt;br /&gt;&lt;br /&gt;This post (and a few following) have a cleaned-up and commented version of my experiments.&lt;br /&gt;&lt;br /&gt;The post is actually a valid literate Haskell source, so you can just save it as Main.lhs and either run using runghc or compile with ghc, and then run it with "runghc Main.lhs program.bf" or "Main program.bf".&lt;br /&gt;&lt;br /&gt;For a collection of Brainfuck programs to execute I recommend this place: &lt;a href="http://esoteric.sange.fi/brainfuck/bf-source/prog/"&gt;http://esoteric.sange.fi/brainfuck/bf-source/prog/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Let's start with module declaration and a few imports:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; module Main where&lt;br /&gt;&gt;&lt;br /&gt;&gt; import Control.Exception&lt;br /&gt;&gt; import Data.Word&lt;br /&gt;&gt; import Data.Char&lt;br /&gt;&gt; import System.Environment&lt;br /&gt;&gt; import System.IO&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Current state of the Brainfuck interpreter consists of four things:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt; Brainfuck program being executed&lt;/li&gt;&lt;br /&gt;&lt;li&gt; current program counter position&lt;/li&gt;&lt;br /&gt;&lt;li&gt; contents of the data memory (usually 30000 8-bit wide cells, but some programs require 16 bits to run)&lt;/li&gt;&lt;br /&gt;&lt;li&gt; current memory pointer position&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Such state can be wrapped in Haskell structure:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; data BFState = BFState {&lt;br /&gt;&gt;   program     :: String,      -- program being interpreted&lt;br /&gt;&gt;   memory      :: [Word16],    -- memory&lt;br /&gt;&gt;   pc          :: Int,         -- current program counter&lt;br /&gt;&gt;   pos         :: Int          -- current pointer position&lt;br /&gt;&gt; }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I am using Word16 as memory cell since I want to run pi16.bf as a benchmark, and it requires 16-bit words.&lt;br /&gt;&lt;br /&gt;Initialization of BFState structure for given program and given size of memory (typically 30000):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; initState :: String -&gt; Int -&gt; BFState&lt;br /&gt;&gt; initState program memSize = BFState program (take memSize $ repeat 0) 0 0&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Brainfuck program ends when program counter (pc) reaches end of program.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; isEnd :: BFState -&gt; Bool&lt;br /&gt;&gt; isEnd st  = (pc st) &gt;= length (program st)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Time to write the function that will interpret single program instruction and execute it.&lt;br /&gt;All instructions transform current state into a new state with some of its data updated, for example pc moved to the next instruction, updated memory state etc. Normally type of such function could be BFState -&gt; BFState (simple transformation), but unfortunately some instructions (namely . and ,) have a side-effect - they perform I/O operations. Because of that our function needs to have type BFState -&gt; IO BFState, meaning that it transforms BFState into a new BFState, but with possible side-effects.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; step :: BFState -&gt; IO BFState&lt;br /&gt;&gt; step st   =&lt;br /&gt;&gt;   case (program st !! pc st) of&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;First four instructions are trivial.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;       '+' -&gt; return st { memory = setMem st (getMem st + 1), pc = nextPC st }&lt;br /&gt;&gt;       '-' -&gt; return st { memory = setMem st (getMem st - 1), pc = nextPC st }&lt;br /&gt;&gt;       '&lt;' -&gt; return st { pos = (pos st) - 1, pc = nextPC st }&lt;br /&gt;&gt;       '&gt;' -&gt; return st { pos = (pos st) + 1, pc = nextPC st }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;'[' checks if current memory cell is 0, and if it is program jumps to the instruction after matching ']'. If cell is non-zero program simply enters loop body.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;       '[' -&gt; return st { pc = pc' } where&lt;br /&gt;&gt;               pc' = if getMem st == 0 then findMatchingForward (program st) (pc st) + 1&lt;br /&gt;&gt;                                       else nextPC st&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;']' simply jumps to its corresponding '['.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;       ']' -&gt; return st { pc = pc' } where&lt;br /&gt;&gt;               pc' = findMatchingBackwards (program st) (pc st)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;'.' outputs current memory cell as character to standard output. Memory cell type (Word16) has to be converted to chr - it is done using chr (fromEnum x).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;       '.' -&gt; do hPutChar stdout (chr (fromEnum $ getMem st))&lt;br /&gt;&gt;                 hFlush stdout&lt;br /&gt;&gt;                 return st { pc = nextPC st}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;',' reads one character from standard input and puts it to current memory cell. If EOF is encountered 0 is written.&lt;br /&gt;Normally getChar would throw exception on EOF, but System.IO provides a nice function called try, that translates result of getChar to "Right char" on success or "Left exception" if exception occurs.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;       ',' -&gt; do c &lt;- try getChar&lt;br /&gt;&gt;                 let val   = case c of&lt;br /&gt;&gt;                               Right a -&gt; fromIntegral (fromEnum a)&lt;br /&gt;&gt;                               Left e  -&gt; 0&lt;br /&gt;&gt;                   in return st { memory = setMem st val, pc = nextPC st }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;All characters that are not Brainfuck instructions should just be ignored.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt;       otherwise  -&gt; return $ st { pc = nextPC st }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;code above makes use of a few utility functions - nextPC, getMem and setMem.&lt;br /&gt;&lt;br /&gt;nextPC - advance PC to the next instruction&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; nextPC :: BFState -&gt; Int&lt;br /&gt;&gt; nextPC st = (pc st) + 1&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;getMem - return value from memory cell pointed to by pos&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; getMem :: BFState -&gt; Word16&lt;br /&gt;&gt; getMem st = (memory st) !! (pos st)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;setMem - transform memory so that memory cell pointed to by pos gets a new value.&lt;br /&gt;Lists are immutable, so a copy of memory is generated.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; setMem :: BFState -&gt; Word16 -&gt; [Word16]&lt;br /&gt;&gt; setMem st value = take (pos st) (memory st) ++ [value] ++ drop (pos st + 1) (memory st)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;'[' and ']' implementation requires searching for matching parenthesis to provide&lt;br /&gt;loop behaviour. Functions findMatchingForward and findMatchingBackwards do that.&lt;br /&gt;&lt;br /&gt;Both functions take program and starting position in program (index of character that opens the loop) as arguments and return position in program of the corresponding loop end.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; findMatchingForward :: String -&gt; Int -&gt; Int&lt;br /&gt;&gt; findMatchingForward program pos = findMatchingForward_ program pos 0 where&lt;br /&gt;&gt;       findMatchingForward_ program pos level =&lt;br /&gt;&gt;               case program !! pos of&lt;br /&gt;&gt;                   '[' -&gt; findMatchingForward_ program (pos+1) (level+1)&lt;br /&gt;&gt;                   ']' -&gt; if level == 1 then pos&lt;br /&gt;&gt;                                        else findMatchingForward_ program (pos+1) (level-1)&lt;br /&gt;&gt;                   otherwise -&gt; findMatchingForward_ program (pos+1) level&lt;br /&gt;&gt;&lt;br /&gt;&gt; findMatchingBackwards :: String -&gt; Int -&gt; Int&lt;br /&gt;&gt; findMatchingBackwards program pos = findMatchingBackwards_ program pos 0 where&lt;br /&gt;&gt;       findMatchingBackwards_ program pos level =&lt;br /&gt;&gt;               case program !! pos of&lt;br /&gt;&gt;                   ']' -&gt; findMatchingBackwards_ program (pos-1) (level+1)&lt;br /&gt;&gt;                   '[' -&gt; if level == 1 then pos&lt;br /&gt;&gt;                                        else findMatchingBackwards_ program (pos-1) (level-1)&lt;br /&gt;&gt;                   otherwise -&gt; findMatchingBackwards_ program (pos-1) level&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Interpreter loop is very simple - if we reached end of the program just return. If not, execute one step with current interpreter state, which gets us a new updated state st', and call run again with st'.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; run st = if isEnd st then return ()&lt;br /&gt;&gt;                      else do st' &lt;- step st&lt;br /&gt;&gt;                              run st'&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Finally time for the main function - check for program arguments, if there are any treat first of them as name of the file with program to read. Then create initial BFState and pass it to run.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;&gt; main :: IO ()&lt;br /&gt;&gt; main  = do&lt;br /&gt;&gt;       args &lt;- getArgs&lt;br /&gt;&gt;       if length args == 0 then fail "Please provide name of the program to execute"&lt;br /&gt;&gt;                           else do program &lt;- readFile (head args)&lt;br /&gt;&gt;                                  run (initState program 30000)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's it. Full Brainfuck interpreter in pure Haskell.&lt;br /&gt;&lt;br /&gt;The bad news is that it's extremely slow. Even compiled with -O2 it is a few thousand times slower than interpreter written in C. Can something be done about it? It sure can, but I'll do that in the next post.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-2659372621696326944?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/2659372621696326944/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=2659372621696326944' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/2659372621696326944'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/2659372621696326944'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2007/12/brainfuck-interpreter-in-haskell.html' title='Brainfuck interpreter in Haskell'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-978780246348321177.post-6191999166997291405</id><published>2007-12-28T20:59:00.000+01:00</published><updated>2007-12-28T21:15:33.239+01:00</updated><title type='text'>Blogging attempt #4</title><content type='html'>&lt;div style="text-align: justify;"&gt;OK, so I decided to make another (#4) attempt at blogging. I hope this time I can make it beyond third post.&lt;br /&gt;&lt;br /&gt;Quick background:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Age: 32&lt;/li&gt;&lt;li&gt;Occupation: software engineer, currently on self-funded one-year sabbatical&lt;/li&gt;&lt;li&gt;Interests:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;programming languages, right now mostly focusing on functional ones (Haskell, Ocaml, Curry, Lisp), but still looking for a perfect one (C++, C and Java are definitely not perfect; Python works quite OK, but not perfect as well; Ruby sucks big time)&lt;/li&gt;&lt;li&gt;human languages (speaks more or less English, have basics of Croatian, Russian and German, currently working on Japanese)&lt;/li&gt;&lt;li&gt;travel - not many countries so far, but planning to visit Thailand and Japan next year.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Blogging purpose: indulge the world with my ramblings about programming and all the other stuff that I may think of :)&lt;/li&gt;&lt;/ul&gt;And that's about it. We'll see how it goes.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/978780246348321177-6191999166997291405?l=sabbatical-year.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://sabbatical-year.blogspot.com/feeds/6191999166997291405/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=978780246348321177&amp;postID=6191999166997291405' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/6191999166997291405'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/978780246348321177/posts/default/6191999166997291405'/><link rel='alternate' type='text/html' href='http://sabbatical-year.blogspot.com/2007/12/blogging-attempt-4.html' title='Blogging attempt #4'/><author><name>brzozan</name><uri>http://www.blogger.com/profile/13083180718801055848</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
