<?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-2187341306664604423</id><updated>2011-07-29T00:43:45.604-07:00</updated><category term='hs-ffmpeg'/><category term='scripting'/><category term='i18n'/><category term='other'/><category term='win7'/><category term='books'/><category term='haskell'/><category term='thoughts'/><category term='status'/><category term='gettext'/><category term='gtk'/><category term='ffmpeg'/><category term='cabal'/><category term='WL500gP'/><category term='databases'/><title type='text'>Programs and programming</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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>18</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2187341306664604423.post-5227319125265036623</id><published>2011-07-11T12:59:00.000-07:00</published><updated>2011-07-11T22:38:19.640-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='thoughts'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><category scheme='http://www.blogger.com/atom/ns#' term='other'/><title type='text'>The Toilet Paper Entrepreneur. Not a review</title><content type='html'>&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;Many years ago I was fascinated with ideas shared by Robert Kiyosaki and others, that everyone could be a millionaire.&lt;span&gt;  &lt;/span&gt;The only is needed is to think that you’re already a millionaire and action accordingly.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;That was easy. Only several hours a day, dreaming how would I spend my millions on yachts, helicopters, restaurants and all my dreams become true soon.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;Yes this is bullshit, but for first-year student this looked very cool along with the fact, that book authors looked rich and successful people. By the way, I haven't spent much time on practicing this, because&lt;span&gt;  &lt;/span&gt;I was busy with other things, that fascinated me more - programming, studying, family. But I still know some people who believe in this, and still sometimes invite me to "dream together".&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;Ok, most of them, who believed in the easy money grove, went for work, gathering money, playing with children and ensured that there is no easy money. Each work or business requires much of efforts, nerves etc. If you want to get more money, than prepare to greater responsibility, or greater risk or both. Anyway, you'll have less time to sleep or to play with kids.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;"But what about these lucky guys, from all these books? What about Page and Brin? Zukerberg? Other millionaires?" You'll ask. The answer is: "They're lucky, dude". Yes, they belong to the small number of population, we kindly name "lucky pants". I'm very happy these people exist and do cool stuff I'm using, but they couldn't act as examples. They're represent only 0.0001% of all businesses. Next 2-5% are ordinary businesses with ordinary income and 95% businesses are failed, or in a way of failing.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;Anyway, I like to read books and when &lt;a href="http://www.amazon.com/Toilet-Paper-Entrepreneur-tell-like/dp/0981808204"&gt;"The Toilet Paper Entrepreneur"&lt;/a&gt; comes through my eyes, I’ve decided to look on it.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;To be shortly, this book left concurrent feelings. The good are that it is not big and reads easily. Also the author tells about hard way to success, contrary with previous dreaming-based methods. He also give there some fresh tips on how to start business without initial funding, how to keep away from angels and VCs on startup. There are also many tips on how to spend less or no money on advertising, web page, conference calls etc.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;In the same time Mike presented a new population of entrepreneurs - TPE. But I don’t wannabe TPE, I just want to do what I like and get paid for it. And I don't think this book will create new move - TPE move.&lt;span&gt; &lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;Also, appealing to the lucky businesses and "The Secret" movie, left feeling that this book continues series of "Homebrew millionaire. Made easy" books, about which I have strong disagreement. Yep, believing in success should be present in all entrepreneurs, but pragmatic view should dominate.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;I don't miss I've read this book; it has many interesting advices, and tends to be real. Also it prepares you to a hard way if you'll choose to be an entrepreneur. And not only toilet paper entrepreneur :)&lt;/span&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-5227319125265036623?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/5227319125265036623/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2011/07/toilet-paper-entrepreneur-not-review.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/5227319125265036623'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/5227319125265036623'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2011/07/toilet-paper-entrepreneur-not-review.html' title='The Toilet Paper Entrepreneur. Not a review'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-6849020166254114692</id><published>2011-04-17T00:56:00.000-07:00</published><updated>2011-04-17T00:59:53.102-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='win7'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='gtk'/><category scheme='http://www.blogger.com/atom/ns#' term='cabal'/><title type='text'>Installing gtk2hs 0.12 for the latest Haskell Platform on the Windows 7</title><content type='html'>&lt;p&gt;This post only comments the original tutorial from &lt;a href="http://www.haskell.org/haskellwiki/gtk2hs"&gt;haskell.org&lt;/a&gt; and describes extra action, one should perform to successfully install the &lt;em&gt;Gtk&lt;/em&gt; bindings to &lt;em&gt;Haskell&lt;/em&gt; without &lt;em&gt;MinGW&lt;/em&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;So, steps, you should perform:&lt;/p&gt;&lt;br /&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;br /&gt;&lt;li&gt;&lt;p&gt;Read the &lt;a href="http://www.haskell.org/haskellwiki/Gtk2hs#Windows_7_32_bit"&gt;original documentation on how to install Haskell for Windows&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;p&gt;Download all required files, mentioned im this document&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;p&gt;Install the &lt;em&gt;Gtk&lt;/em&gt; to the location &lt;em&gt;without spaces in the filenames&lt;/em&gt;. So the default location (&lt;code&gt;C:\Program Files\Gtk+&lt;/code&gt;) won't work.&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;p&gt;Run command-line prompt, fill the &lt;code&gt;INCLUDE&lt;/code&gt; and &lt;code&gt;PKG_CONFIG_PATH&lt;/code&gt; with correct paths to &lt;em&gt;Gtk+&lt;/em&gt; and &lt;em&gt;libxml2&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;p&gt;run &lt;code&gt;cabal install --ghc-option=-DCABAL_VERSION_MINOR=10 gtk&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h2 id="this-is-not-all."&gt;This is not all.&lt;/h2&gt;&lt;br /&gt;&lt;p&gt;Now, some packages fails to register with error:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;Registering cairo-0.12.0...&lt;br /&gt;setup.exe: internal error: unexpected package db stack: [UserPackageDB]&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;This is because of bug (or mistake?) in &lt;code&gt;Gtk2hsSetup.hs&lt;/code&gt;. So for these packages you should unpack them (e.g. &lt;code&gt;cabal unpack cairo&lt;/code&gt;), move to the unpacked directory and change the following code in the &lt;code&gt;Gtk2hsSetup.hs&lt;/code&gt; (line 199 of the file):&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;#if CABAL_VERSION_CHECK(1,10,0)&lt;br /&gt;        installedPkgInfo pkg lbi inplace [packageDb]&lt;br /&gt;#else&lt;br /&gt;        installedPkgInfo pkg lbi inplace packageDb&lt;br /&gt;#endif&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;to&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;#if CABAL_VERSION_CHECK(1,10,0)&lt;br /&gt;        installedPkgInfo pkg lbi inplace (withPackageDB lbi)&lt;br /&gt;#else&lt;br /&gt;        installedPkgInfo pkg lbi inplace packageDb&lt;br /&gt;#endif&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Than, in the package root directory type:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;cabal install --ghc-option=-DCABAL_VERSION_MINOR=10&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2 id="other-errors-ive-faced-with-and-the-solutions"&gt;Other errors I've faced with, and the solutions&lt;/h2&gt;&lt;br /&gt;&lt;h3 id="error-in-compiling-pango-package-the-compiler-couldnt-find-glibglib.h-file."&gt;Error in compiling &lt;code&gt;pango&lt;/code&gt; package, the compiler couldn't find &lt;code&gt;glib/glib.h&lt;/code&gt; file.&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;In the file &lt;code&gt;pango-0.12.0\Graphics\Rendering\Pango\Struts.hsc&lt;/code&gt; change&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;#include &amp;lt;glib/glib.h&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;to&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;#include &amp;lt;glib.h&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3 id="error-in-compiling-gio-package-some-names-arent-defined-wmdrivestopbutton-drivestartstoptype-drivestartflags"&gt;Error in compiling &lt;code&gt;gio&lt;/code&gt; package, some names aren't defined (&lt;code&gt;wmDriveStopButton&lt;/code&gt;, &lt;code&gt;DriveStartStopType&lt;/code&gt;, &lt;code&gt;DriveStartFlags&lt;/code&gt;)&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;This is because in the module exports these names are present, but defined in a body with macro:&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;#if GLIB_CHECK_VERSION(2,22,0)&lt;br /&gt;...&lt;br /&gt;#endif&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;My solution, is to wrap the definitions of these exports in the same macro. Fortunately this will not break the compilation.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;After all these steps you should have a working installation of the Gtk2Hs. To check this, you could build and run examples from the &lt;code&gt;gtk-0.12.0\demo&lt;/code&gt; directory.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-6849020166254114692?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/6849020166254114692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2011/04/installing-gtk2hs-012-for-latest.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/6849020166254114692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/6849020166254114692'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2011/04/installing-gtk2hs-012-for-latest.html' title='Installing gtk2hs 0.12 for the latest Haskell Platform on the Windows 7'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2187341306664604423.post-3128068143686246613</id><published>2010-05-29T10:35:00.000-07:00</published><updated>2010-05-29T10:46:19.576-07:00</updated><title type='text'>Released new version of ProjectTemplate. Now on Windows.</title><content type='html'>Recently I've uploaded new, more powerful version of my &lt;a href="http://patch-tag.com/r/VasylPasternak/ProjectTemplate"&gt;ProjectTemplate&lt;/a&gt; tool. Now it could create from templates single files, and don't require the project directory to be created. More info in the &lt;a href="http://patch-tag.com/r/VasylPasternak/ProjectTemplate/snapshot/current/content/pretty/README"&gt;README&lt;/a&gt; file.&lt;br /&gt;&lt;br /&gt;Also I've added support for Windows. It is not so beauty as for Linux platforms (on Windows it works without realine functionality). The Windows binaries are stored on &lt;a href="http://code.google.com/p/haskell-on-windows/downloads/detail?name=ProjectTemplate-0.3-w32.zip&amp;can=2&amp;q="&gt;Google Code&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Ironically, but ProjectTemplate helps me more in C++ coding, not in Haskell ;)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-3128068143686246613?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/3128068143686246613/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2010/05/released-new-version-of-projecttemplate.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/3128068143686246613'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/3128068143686246613'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2010/05/released-new-version-of-projecttemplate.html' title='Released new version of ProjectTemplate. Now on Windows.'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-7795734618684683653</id><published>2010-03-09T11:20:00.000-08:00</published><updated>2010-03-09T11:36:30.998-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='cabal'/><title type='text'>On creating Haskell projects. Cabal init.</title><content type='html'>In comments to my &lt;a href="http://progandprog.blogspot.com/2010/03/projecttemplate-template-based-tool-for.html"&gt;previous article&lt;/a&gt; on &lt;a href="http://www.reddit.com/r/haskell/comments/b9kpf/projecttemplate_a_templatebased_tool_for_creating/"&gt;reddit&lt;/a&gt; I read about new feature in the &lt;a href="http://hackage.haskell.org/package/cabal-install"&gt;cabal-install&lt;/a&gt; package (version 0.8.0) - &lt;em&gt;cabal init&lt;/em&gt;. This is more convenient successor for &lt;a href="http://hackage.haskell.org/package/mkcabal"&gt;mkcabal&lt;/a&gt; and I glad it now seamlessly integrated to this powerful project management tool. &lt;br /&gt;&lt;br /&gt;This tool also supports rich command line interface, to avoid entering some common properties (such as license, version etc.) every time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-7795734618684683653?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/7795734618684683653/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2010/03/on-creating-haskell-projects-cabal-init.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/7795734618684683653'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/7795734618684683653'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2010/03/on-creating-haskell-projects-cabal-init.html' title='On creating Haskell projects. Cabal init.'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-1474687029061986605</id><published>2010-03-05T01:41:00.001-08:00</published><updated>2010-03-05T01:45:23.510-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>ProjectTemplate - a template-based tool for creating Haskell projects</title><content type='html'>&lt;p&gt;Yesterday I created 5 projects, and found many boilerplate code on preparing cabal files, Setup.hs and directory structure. So I decided to simplify project creation for haskellers and created &lt;a href="http://patch-tag.com/r/VasylPasternak/ProjectTemplate"&gt;ProjectTemplate&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;I was inspired by the Don&amp;rsquo;s &lt;a href="http://hackage.haskell.org/package/mkcabal"&gt;&lt;code&gt;mkcabal&lt;/code&gt;&lt;/a&gt; tool, and created the similar dialog-based application.&lt;/p&gt;&lt;p&gt;&lt;code&gt;mkcabal&lt;/code&gt;, created by Don, is very simple tool, and it helps, when you start using Haskell, but this tool hasn&amp;rsquo;t any configuration and personalization of Haskell project, so I every time after creating project with &lt;code&gt;mkcabal&lt;/code&gt; make some changes in the directory structure and file names.&lt;/p&gt;&lt;p&gt;&lt;code&gt;ProjectTemplate&lt;/code&gt; is the template-based project creation tool. It was developed for ease creation of Haskell projects, but now I see, it could ease creation different directory structures, based on templates (for example web sites).&lt;/p&gt;&lt;p&gt;Templates are directories with files it them, which will be copied to the newly created project. Each template could contain complex directory hierarchy. Each file/directory name could be the plain name, or template name. Template name is created from the double underscore and parameter name, and in the project directory is substituted to the parameter value.&lt;/p&gt;&lt;p&gt;For example &lt;code&gt;__ProjectName.cabal&lt;/code&gt; file with &lt;code&gt;ProjectName&lt;/code&gt; parameter set to &lt;code&gt;test&lt;/code&gt;, will be substituted to &lt;code&gt;test.cabal&lt;/code&gt;. The same is true for directories.&lt;/p&gt;&lt;p&gt;&lt;code&gt;ProjectTemplate&lt;/code&gt; also fills template files, where substitutes parameters to their values. The template file should have &lt;code&gt;.template&lt;/code&gt; extension, which will be cut of in the destination file. In the template file parameters are enclosed in &lt;code&gt;$&lt;/code&gt; character. The example cabal file, with template parameters could look like:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;name:                $ProjectName$&lt;br /&gt;version:             0.1&lt;br /&gt;synopsis:            &lt;br /&gt;description:         &lt;br /&gt;category:            $Category$&lt;br /&gt;license:             BSD3&lt;br /&gt;license-file:        LICENSE&lt;br /&gt;author:              $Author$&lt;br /&gt;maintainer:          $Maintainer$&lt;br /&gt;build-depends:       base&lt;br /&gt;build-type:          Simple&lt;br /&gt;&lt;br /&gt;executable:          $ProjectName$&lt;br /&gt;main-is:             src/Main.hs&lt;br /&gt;ghc-options:         &lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;ProjectTemplate&lt;/code&gt; defines following parameters: &lt;code&gt;ProjectName&lt;/code&gt;, &lt;code&gt;Day&lt;/code&gt;, &lt;code&gt;Month&lt;/code&gt;, &lt;code&gt;Year&lt;/code&gt;. All other parameters will be asked to user during the project creation.&lt;/p&gt;&lt;p&gt;You could look at sample templates in the &lt;a href="http://patch-tag.com/r/VasylPasternak/ProjectTemplate/snapshot/current/content/pretty/ProjectTemplates"&gt;&lt;code&gt;ProjectTemplate&lt;/code&gt; repository&lt;/a&gt;, to better understanding how this tool works.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-1474687029061986605?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/1474687029061986605/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2010/03/projecttemplate-template-based-tool-for.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/1474687029061986605'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/1474687029061986605'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2010/03/projecttemplate-template-based-tool-for.html' title='ProjectTemplate - a template-based tool for creating Haskell projects'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-1278055572892269154</id><published>2010-02-01T09:48:00.000-08:00</published><updated>2010-02-01T09:55:29.210-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Using PageRank to calculate HackageRank</title><content type='html'>&lt;blockquote&gt;&lt;p&gt;&lt;a href="http://progandprog.blogspot.com/2010/01/using-haskell-to-calculate-pagerank.html"&gt;Previous article&lt;/a&gt; was introductory to the &lt;em&gt;PageRank&lt;/em&gt; algorithm. Today I want to show one of the practical use of previous library.&lt;/p&gt;&lt;/blockquote&gt;&lt;div id="objectives"&gt;&lt;h2&gt;Objectives&lt;/h2&gt;&lt;p&gt;&lt;a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/"&gt;Hackage&lt;/a&gt; grows every day. Because of its centralized structure and the &lt;em&gt;cabal&lt;/em&gt; tool, it is easy to install packages with all dependencies with single command.&lt;/p&gt;&lt;p&gt;But I always wonder, what packages are most important, and obviously present at almost all PC&amp;rsquo;s with Haskell. Sometimes looks strange, when simple library with several functions depends on more than 5 packages. Even when I have installed most popular packages, because my Haskell environment haven&amp;rsquo;t changed last year. Also I always have troubles with choosing the right library for Web server, or for work with database.&lt;/p&gt;&lt;p&gt;So I decided to apply &lt;a href="http://en.wikipedia.org/wiki/PageRank"&gt;PageRank&lt;/a&gt; algorithm to the &lt;em&gt;Hackage&lt;/em&gt;. &lt;em&gt;PageRank&lt;/em&gt; shows the probability of the random surfer to visit single page. Similarly &lt;em&gt;HackageRank&lt;/em&gt; must show the probability for single package to be present on the random PC of the random haskeller.&lt;/p&gt;&lt;/div&gt;&lt;div id="the-code"&gt;&lt;h2&gt;The code&lt;/h2&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Keyword"&gt;module&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Main &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- use distribution package to read dependencies&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Distribution.PackageDescription&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Distribution.PackageDescription.Parse&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Distribution.PackageDescription.Configuration&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Distribution.Verbosity&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Distribution.Version&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Distribution.Package&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- file utils, to enumerate directories&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import System.Directory&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import System.&lt;/span&gt;&lt;span class="Function"&gt;FilePath&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import System.Posix.Files&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import System.Environment&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Control.&lt;/span&gt;&lt;span class="Keyword Class"&gt;Monad&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Control.Applicative&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Control.Arrow&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import qualified Data.ByteString.Char8 as B&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import qualified Data.Map as M&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Data.Map (size, keys, fromListWith, toList)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import qualified Data.Set as S&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Data.Set (empty, union, fromList, toList)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Data.&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Maybe&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Data.List (sortBy)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Text.Printf&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Rank&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="gathering-all-cabal-files"&gt;&lt;h2&gt;Gathering all cabal files&lt;/h2&gt;&lt;p&gt;All packages descriptions could be downloaded from the &lt;a href="http://hackage.haskell.org/packages/archive/00-index.tar.gz"&gt;Hackage&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The path to the unpacked archive will be send through command line argument.&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; isDir = liftA isDirectory . getFileStatus&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; notDir = liftA &lt;/span&gt;&lt;span class="Function"&gt;not&lt;/span&gt;&lt;span class="Normal NormalText"&gt; . isDir&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; directoryListing p = (&lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (p &amp;lt;/&amp;gt;) . filterUpperDirs) &amp;lt;$&amp;gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                      getDirectoryContents p&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                        &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                          filterUpperDirs = &lt;/span&gt;&lt;span class="Function"&gt;filter&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="Others InfixOperator"&gt;`notElem`&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [&lt;/span&gt;&lt;span class="String"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;..&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;])&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; cabalFiles root = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   dir &amp;lt;- directoryListing root&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   directories &amp;lt;- filterM isDir dir&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   files &amp;lt;- filterM notDir dir&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   subfiles &amp;lt;- &lt;/span&gt;&lt;span class="Function"&gt;concat&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &amp;lt;$&amp;gt; &lt;/span&gt;&lt;span class="Function"&gt;mapM&lt;/span&gt;&lt;span class="Normal NormalText"&gt; cabalFiles directories&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (files ++ subfiles)&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="read-package-names-and-dependencies"&gt;&lt;h2&gt;Read package names and dependencies&lt;/h2&gt;&lt;p&gt;I pack all strings into byte strings, to reduce memory usage (the PageRank algorithm itself heavily uses memory).&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; packName pkg = &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (PackageName pkgName) = packageName pkg&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                &lt;/span&gt;&lt;span class="Keyword"&gt;in&lt;/span&gt;&lt;span class="Normal NormalText"&gt; B.pack pkgName&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; dependencyName (Dependency (PackageName name) _) = B.pack name&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; packageDependencies pkg = &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; directDeps = buildDepends pkg&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       allBuildInfoDeps = &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         &lt;/span&gt;&lt;span class="Function"&gt;concatMap&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="Function"&gt;uncurry&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (++) . (buildTools &amp;amp;&amp;amp;&amp;amp; pkgconfigDepends))&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         (allBuildInfo pkg)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;in&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; dependencyName (directDeps ++ allBuildInfoDeps)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; parsePackageFile cabal = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   pkg &amp;lt;- flattenPackageDescription &amp;lt;$&amp;gt; readPackageDescription silent cabal&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; deps = packageDependencies pkg&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ((packName pkg, S.fromList deps) : [(x, S.empty) | x &amp;lt;- deps])&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="generate-the-list-of-all-packages-and-their-dependencies"&gt;&lt;h2&gt;Generate the list of all packages and their dependencies&lt;/h2&gt;&lt;p&gt;&lt;code&gt;enumeratePackages&lt;/code&gt; simply maps each package name to integer value. Previous versions of PageRank algorithm run out of memory, when I tried to use strings in vertex names.&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- we have a list of elements in the form of: [(name, deps set)]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- the name could be repeated in the list, so let's convert it to&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- map, and combine all sets&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; packages root = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   largeList &amp;lt;- &lt;/span&gt;&lt;span class="Function"&gt;concat&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &amp;lt;$&amp;gt; (&lt;/span&gt;&lt;span class="Function"&gt;mapM&lt;/span&gt;&lt;span class="Normal NormalText"&gt; parsePackageFile =&amp;lt;&amp;lt; cabalFiles root)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (M.fromListWith (S.union) (&lt;/span&gt;&lt;span class="Function"&gt;filter&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="Function"&gt;not&lt;/span&gt;&lt;span class="Normal NormalText"&gt; . B.&lt;/span&gt;&lt;span class="Function"&gt;null&lt;/span&gt;&lt;span class="Normal NormalText"&gt; . &lt;/span&gt;&lt;span class="Function"&gt;fst&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) largeList))&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; nameToIdx pkgsMap = M.fromList (&lt;/span&gt;&lt;span class="Function"&gt;zip&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (M.keys pkgsMap) ([&lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt;..]::[&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt;]))&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; idxToName pkgsMap = M.fromList (&lt;/span&gt;&lt;span class="Function"&gt;zip&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ([&lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt;..]::[&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt;]) (M.keys pkgsMap))&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; enumeratePackages pkgsMap = &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; indexes = nameToIdx pkgsMap&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       getIndex name = fromJust (name &lt;/span&gt;&lt;span class="Others InfixOperator"&gt;`M.lookup`&lt;/span&gt;&lt;span class="Normal NormalText"&gt; indexes)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       enumerator (n, d) = (getIndex n, &lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; getIndex d)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;in&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; enumerator (M.toList (M.&lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; S.toList pkgsMap))&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="decode-indexes-to-package-names"&gt;&lt;h2&gt;Decode indexes to package names&lt;/h2&gt;&lt;p&gt;Last function just simply decodes the integer values to the corresponding packages names.&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; decodePackages pkgsMap ranks = &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; names = idxToName pkgsMap&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       getName idx = fromJust (idx &lt;/span&gt;&lt;span class="Others InfixOperator"&gt;`M.lookup`&lt;/span&gt;&lt;span class="Normal NormalText"&gt; names)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;in&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (\(i, r) -&amp;gt; (getName i, r)) ranks&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;  &lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="main-function"&gt;&lt;h2&gt;Main function&lt;/h2&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; main ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ()&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; main = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Comment"&gt;-- take the packages directory and number of iterations&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   (dir:iters:_) &amp;lt;- getArgs&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Comment"&gt;-- read packages to the map&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   packagesMap &amp;lt;- packages dir&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; hackageRankList = pageRankList &lt;/span&gt;&lt;span class="Float"&gt;0.85&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                         (fromGraph (enumeratePackages packagesMap))&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       lastRank = decodePackages packagesMap &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                  (fromRankGraph (hackageRankList !! (&lt;/span&gt;&lt;span class="Function"&gt;read&lt;/span&gt;&lt;span class="Normal NormalText"&gt; iters)))&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       sortedRank = sortBy (\(_, r1) (_, r2) -&amp;gt; &lt;/span&gt;&lt;span class="Function"&gt;compare&lt;/span&gt;&lt;span class="Normal NormalText"&gt; r2 r1) lastRank&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Comment"&gt;-- printing rank to the screen&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; formatString (n, r) = printf &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;%10.5f - %s\n&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; r (B.unpack n)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Function"&gt;mapM_&lt;/span&gt;&lt;span class="Normal NormalText"&gt; formatString sortedRank&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="some-results"&gt;&lt;h2&gt;Some results&lt;/h2&gt;&lt;p&gt;Here the &lt;em&gt;Top20&lt;/em&gt; of packages, by HackageRank:&lt;/p&gt;&lt;pre&gt;&lt;code&gt; 470.84702 - base&lt;br /&gt;  90.73473 - syb&lt;br /&gt;  68.48688 - ghc-prim&lt;br /&gt;  57.82218 - integer&lt;br /&gt;  57.56195 - integer-gmp&lt;br /&gt;  57.55739 - rts&lt;br /&gt;  57.52356 - integer-simple&lt;br /&gt;  45.63995 - array&lt;br /&gt;  34.83558 - mtl&lt;br /&gt;  34.49726 - bytestring&lt;br /&gt;  33.62577 - containers&lt;br /&gt;  20.91788 - directory&lt;br /&gt;  17.19401 - haskell98&lt;br /&gt;  16.18344 - old-time&lt;br /&gt;  14.68120 - filepath&lt;br /&gt;  14.65891 - old-locale&lt;br /&gt;  13.04511 - unix&lt;br /&gt;  13.03026 - parsec&lt;br /&gt;  11.40593 - random&lt;br /&gt;  10.29807 - process&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hm. Doesn&amp;rsquo;t tell anything :) Let&amp;rsquo;s try to rank packages, which provides interface to &lt;em&gt;sqlite&lt;/em&gt; database:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;   0.41222 - HDBC-sqlite3&lt;br /&gt;   0.33478 - hsql-sqlite&lt;br /&gt;   0.33478 - hsql-sqlite3&lt;br /&gt;   0.33024 - bindings-sqlite3&lt;br /&gt;   0.31262 - sqlite&lt;br /&gt;   0.29853 - haskelldb-hdbc-sqlite3&lt;br /&gt;   0.29853 - haskelldb-hsql-sqlite&lt;br /&gt;   0.29853 - haskelldb-hsql-sqlite3&lt;br /&gt;   0.29853 - hsSqlite3&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Much better, now I know, that best tool to work with &lt;em&gt;sqlite&lt;/em&gt; is &lt;em&gt;HDBC&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;I think, there could be another uses of these calculations, waiting for your ideas.&lt;/p&gt;&lt;p&gt;&lt;em&gt;The complete list of HackageRank calculated for Jan 31, 2010 you can download &lt;a href="http://haskell-on-windows.googlecode.com/files/rank.txt"&gt;here&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-1278055572892269154?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/1278055572892269154/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2010/02/using-pagerank-to-calculate-hackagerank.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/1278055572892269154'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/1278055572892269154'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2010/02/using-pagerank-to-calculate-hackagerank.html' title='Using PageRank to calculate HackageRank'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-8439098948125026600</id><published>2010-01-31T08:49:00.000-08:00</published><updated>2010-01-31T08:50:40.549-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Using Haskell to calculate PageRank</title><content type='html'>&lt;blockquote&gt;&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/PageRank"&gt;PageRank&lt;/a&gt; algorithm was originally developed by the Google&amp;rsquo;s founders for ranking pages by their links. But the idea of ranking, based on references to the items could be used not only in Web, but in other different fields, which have directed graph structure. This article, and simultaneously Literate Haskell code, shows how to calculate page rank with Haskell.&lt;/p&gt;&lt;/blockquote&gt;&lt;div id="pagerank-algorithm"&gt;&lt;h1&gt;PageRank algorithm&lt;/h1&gt;&lt;p&gt;&lt;em&gt;PageRank&lt;/em&gt; algorithm uses random surfer model. It calculates the probability of visiting concrete page by some surfer, who randomly clicks on different links on the page.&lt;/p&gt;&lt;p&gt;Originally &lt;em&gt;PageRank&lt;/em&gt; for page &lt;span class="math"&gt;&lt;em&gt;A&lt;/em&gt;&lt;/span&gt; is the:&lt;/p&gt;&lt;p&gt;&lt;span class="math"&gt;&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;A&lt;/em&gt;)=&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;)/&lt;em&gt;LC&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;)+&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt;)/&lt;em&gt;LC&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt;)+...+&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;n&lt;/sub&gt;)/&lt;em&gt;LC&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;n&lt;/sub&gt;)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;where &lt;span class="math"&gt;&lt;em&gt;LC&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;)&lt;/span&gt; &amp;mdash; is number of outgoing links from &lt;span class="math"&gt;&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;&lt;/span&gt;, and &lt;span class="math"&gt;&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;&lt;/span&gt; is all pages, which links to the &lt;span class="math"&gt;&lt;em&gt;A&lt;/em&gt;&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;When page hasn&amp;rsquo;t outgoing links, that we suppose, that it connects to every other page in the Web.&lt;/p&gt;&lt;p&gt;Also &lt;em&gt;PageRank&lt;/em&gt; includes &lt;em&gt;damping factor&lt;/em&gt; &amp;mdash; a probability, that random surfer will continue clicking links on each step. Usually damping factor equals to &lt;span class="math"&gt;0.85&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;The previous PageRank formula with damping factor &lt;span class="math"&gt;&lt;em&gt;D&lt;/em&gt;&lt;/span&gt; will look:&lt;/p&gt;&lt;p&gt;&lt;span class="math"&gt;&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;A&lt;/em&gt;)=(1-&lt;em&gt;D&lt;/em&gt;)+&lt;em&gt;D&lt;/em&gt;(&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;)/&lt;em&gt;LC&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;1&lt;/sub&gt;)+&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt;)/&lt;em&gt;LC&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;2&lt;/sub&gt;)+...+&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;n&lt;/sub&gt;)/&lt;em&gt;LC&lt;/em&gt;(&lt;em&gt;P&lt;/em&gt;&lt;sub&gt;n&lt;/sub&gt;))&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Because, the link graph have cycles (i.e. &lt;span class="math"&gt;&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;A&lt;/em&gt;)&lt;/span&gt; could indirectly depend on &lt;span class="math"&gt;&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;A&lt;/em&gt;)&lt;/span&gt;), than calculating on page rank is iterative &amp;mdash; on each step we recalculate whole graph, getting it closer to real &lt;em&gt;PageRank&lt;/em&gt; value.&lt;/p&gt;&lt;p&gt;Thus the algorithm should look like:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Initialize &lt;span class="math"&gt;&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;A&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;)&lt;/span&gt; for all &lt;span class="math"&gt;&lt;em&gt;A&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;&lt;/span&gt; with arbitrary value, simply &lt;span class="math"&gt;1&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Calculate &lt;span class="math"&gt;&lt;em&gt;PR&lt;/em&gt;(&lt;em&gt;A&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;)&lt;/span&gt; for all &lt;span class="math"&gt;&lt;em&gt;A&lt;/em&gt;&lt;sub&gt;i&lt;/sub&gt;&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Repeat step 2 specified times, or until the error will be relatively small&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div id="implementation"&gt;&lt;h1&gt;Implementation&lt;/h1&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Keyword"&gt;module&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Main &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import System.Random&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Control.&lt;/span&gt;&lt;span class="Keyword Class"&gt;Monad&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Control.Applicative&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Data.&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Maybe&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import System.Environment&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import qualified Data.IntMap as M&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import qualified Data.IntSet as S&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div id="convert-input-data-to-more-convenient-structure"&gt;&lt;h2&gt;Convert input data to more convenient structure&lt;/h2&gt;&lt;p&gt;Our initial input data will have the form:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[(vertex, [vertexes to which current vertex links])]&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;vertexes, are integers, for more efficient implementation.&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Each vertex has its rank, number of outbound links and &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- set of inbound links&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Keyword"&gt;data&lt;/span&gt;&lt;span class="Normal NormalText"&gt; VertexProps = VertexProps {&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;   vertexRank ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Double&lt;/span&gt;&lt;span class="Normal NormalText"&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;   numOutboundLinks ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;   inboundLinks ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; S.IntSet&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   } &lt;/span&gt;&lt;span class="Keyword"&gt;deriving&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Keyword Class"&gt;Show&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; isDangling (VertexProps _ &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt; _) = &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;True&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; isDangling _ = &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;False&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Our graph is map between vertex index and it properties&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Keyword"&gt;type&lt;/span&gt;&lt;span class="Normal NormalText"&gt; RankGraph = M.IntMap VertexProps&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; fromGraph ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [(&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, [&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt;])] -&amp;gt; RankGraph&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; fromGraph gr = &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; depPairs (v, l) = (v, VertexProps &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;1&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="Function"&gt;length&lt;/span&gt;&lt;span class="Normal NormalText"&gt; l) S.empty) :&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                         [(x, VertexProps &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (S.singleton v)) | &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                          x &amp;lt;- l]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       depList = &lt;/span&gt;&lt;span class="Function"&gt;concatMap&lt;/span&gt;&lt;span class="Normal NormalText"&gt; depPairs gr&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       catVertexes (VertexProps r1 l1 s1) (VertexProps r2 l2 s2) =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         VertexProps (r1+r2) (l1+l2) (s1 &lt;/span&gt;&lt;span class="Others InfixOperator"&gt;`S.union`&lt;/span&gt;&lt;span class="Normal NormalText"&gt; s2)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;in&lt;/span&gt;&lt;span class="Normal NormalText"&gt; M.fromListWith catVertexes depList&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="implement-single-step-pagerank-algorithm"&gt;&lt;h2&gt;Implement single-step PageRank algorithm&lt;/h2&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; pageRank ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Double&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; RankGraph -&amp;gt; RankGraph&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; pageRank df gr = M.mapWithKey transformRank gr&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;span class="Normal NormalText"&gt; transformRank v (VertexProps r l s) = &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;           VertexProps (calcRank s) l s&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         &lt;/span&gt;&lt;span class="Comment"&gt;-- calculate rank for a single vertex&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         calcRank s = (&lt;/span&gt;&lt;span class="DecVal Decimal"&gt;1&lt;/span&gt;&lt;span class="Normal NormalText"&gt; - df) + df * (danglingScore + (S.fold incomR &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt; s))&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         &lt;/span&gt;&lt;span class="Comment"&gt;-- sum of all linkVotes for vertixe vrt&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         incomR vrt r = r + (linkVote vrt)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         linkVote v = &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; lvf (VertexProps r l _) = r / (&lt;/span&gt;&lt;span class="Function"&gt;fromIntegral&lt;/span&gt;&lt;span class="Normal NormalText"&gt; l)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                      &lt;/span&gt;&lt;span class="Keyword"&gt;in&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Function"&gt;maybe&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt; lvf (v &lt;/span&gt;&lt;span class="Others InfixOperator"&gt;`M.lookup`&lt;/span&gt;&lt;span class="Normal NormalText"&gt; gr)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         &lt;/span&gt;&lt;span class="Comment"&gt;-- calculate the dangling score&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         &lt;/span&gt;&lt;span class="Comment"&gt;-- sum all dangling page ranks and divide them to graph size&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         danglingPages = &lt;/span&gt;&lt;span class="Function"&gt;filter&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (isDangling . &lt;/span&gt;&lt;span class="Function"&gt;snd&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) (M.toList gr)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         danglingScore = (&lt;/span&gt;&lt;span class="Function"&gt;sum&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (vertexRank . &lt;/span&gt;&lt;span class="Function"&gt;snd&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) danglingPages)) / &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                         (&lt;/span&gt;&lt;span class="Function"&gt;fromIntegral&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (M.size gr))&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; pageRankList df gr = &lt;/span&gt;&lt;span class="Function"&gt;iterate&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (pageRank df) gr&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Convert from RankGraph to list [(id, rank)]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; fromRankGraph = &lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (\(v, r) -&amp;gt; (v, vertexRank r)) . M.toList&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="testing"&gt;&lt;h2&gt;Testing&lt;/h2&gt;&lt;p&gt;Let&amp;rsquo;s test our creation. I create simple main program, that will read graph size from the command-line and calculate rank with 20 iterations. To force evaluation, we&amp;rsquo;ll just print last element from the result of the last iteration. Here I only interested in algorithm&amp;rsquo;s efficiency.&lt;/p&gt;&lt;/div&gt;&lt;div id="generate-test-graph"&gt;&lt;h2&gt;Generate test graph&lt;/h2&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; createTestGraph ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [(&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, [&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Int&lt;/span&gt;&lt;span class="Normal NormalText"&gt;])]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; createTestGraph numPages maxLinks =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Function"&gt;sequence&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [(,) p &amp;lt;$&amp;gt; outboundLinks p | p &amp;lt;- [&lt;/span&gt;&lt;span class="DecVal Decimal"&gt;1&lt;/span&gt;&lt;span class="Normal NormalText"&gt;..numPages]]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     outboundLinks p = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       outLinksNum &amp;lt;- randomRIO (&lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, maxLinks)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       &lt;/span&gt;&lt;span class="Function"&gt;sequence&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [randomRangeNot p (&lt;/span&gt;&lt;span class="DecVal Decimal"&gt;1&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, numPages) | &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         x &amp;lt;- &lt;/span&gt;&lt;span class="Function"&gt;replicate&lt;/span&gt;&lt;span class="Normal NormalText"&gt; outLinksNum &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     randomRangeNot p (a, b) = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       v &amp;lt;- randomRIO (a, b - &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;1&lt;/span&gt;&lt;span class="Normal NormalText"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="Keyword"&gt;if&lt;/span&gt;&lt;span class="Normal NormalText"&gt; v == p &lt;/span&gt;&lt;span class="Keyword"&gt;then&lt;/span&gt;&lt;span class="Normal NormalText"&gt; b &lt;/span&gt;&lt;span class="Keyword"&gt;else&lt;/span&gt;&lt;span class="Normal NormalText"&gt; v)&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; main ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ()&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; main = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   (gSize:gLinks:_) &amp;lt;- getArgs&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   rank &amp;lt;- pageRankList &lt;/span&gt;&lt;span class="Float"&gt;0.85&lt;/span&gt;&lt;span class="Normal NormalText"&gt; . fromGraph &amp;lt;$&amp;gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;           createTestGraph (&lt;/span&gt;&lt;span class="Function"&gt;read&lt;/span&gt;&lt;span class="Normal NormalText"&gt; gSize) (&lt;/span&gt;&lt;span class="Function"&gt;read&lt;/span&gt;&lt;span class="Normal NormalText"&gt; gLinks)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Function"&gt;putStrLn&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Graph size: &amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ++ gSize ++ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ++ gLinks)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;   &lt;/span&gt;&lt;span class="Function"&gt;putStrLn&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="String"&gt;&amp;quot;20th iteration result: &amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ++ &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;             (&lt;/span&gt;&lt;span class="Function"&gt;show&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (M.toList (rank !! &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;20&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) !! ((&lt;/span&gt;&lt;span class="Function"&gt;read&lt;/span&gt;&lt;span class="Normal NormalText"&gt; gSize) - &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;1&lt;/span&gt;&lt;span class="Normal NormalText"&gt;))))&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;My algorithm works 7 seconds to calculate 20 iterations of PageRank for a graph with 10000 vertexes. Not bad, but I will be glad to reduce the memory usage because 150MB is too big for the tiny set of data.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-8439098948125026600?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/8439098948125026600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2010/01/using-haskell-to-calculate-pagerank.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/8439098948125026600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/8439098948125026600'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2010/01/using-haskell-to-calculate-pagerank.html' title='Using Haskell to calculate PageRank'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-3726260072017013767</id><published>2009-10-28T14:24:00.000-07:00</published><updated>2009-10-28T14:25:56.937-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='WL500gP'/><category scheme='http://www.blogger.com/atom/ns#' term='scripting'/><title type='text'>Asus WL500g Premium controller now on Windows</title><content type='html'>&lt;p&gt;Today I worked on my &lt;a href="http://hackage.haskell.org/package/WL500gPControl"&gt;WL500gPControl&lt;/a&gt; project, and already forced it to work on Windows. The most difficult parts were to set up the &lt;a href="http://www.mingw.org/wiki/HOWTO_Install_the_MinGW_GCC_Compiler_Suite"&gt;MinGW&lt;/a&gt; and build &lt;a href="http://curl.haxx.se/"&gt;curl&lt;/a&gt; under it. To be honest, I ended up with &lt;a href="http://haskell.forkio.com/Home/curl-win32/curl-7.19.4-mingw32.zip?attredirects=0&amp;amp;d=1"&gt;precompiled curl&lt;/a&gt; library from &lt;a href="http://haskell.forkio.com/Home/curl-win32"&gt;Sigbjorn Finne&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;To let others avoid these hardships, I prepared the binary version of &lt;a href="http://hackage.haskell.org/package/WL500gPControl"&gt;WL500gPControl&lt;/a&gt;. You'll only have to unpack it on some directory, put the credentials file into the default placement and use it :). The binary version is &lt;a href="http://haskell-on-windows.googlecode.com/files/WL500gPControl-0.3.4.zip"&gt;here&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Also I have added README file, where I described how to use this tool, so you shouldn't have any troubles with it.&lt;/p&gt;&lt;p&gt;&lt;em&gt;If you are updating from previous version&lt;/em&gt; note that placement of the default file has been changed. For Unix users it is&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$HOME/.WL500gPControl/credentials, &lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and for Windows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;C:\Documents And Settings\user\Application Data\WL500gPControl\credentials&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-3726260072017013767?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/3726260072017013767/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/10/asus-wl500g-premium-controller-now-on.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/3726260072017013767'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/3726260072017013767'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/10/asus-wl500g-premium-controller-now-on.html' title='Asus WL500g Premium controller now on Windows'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-6326609122965136817</id><published>2009-10-27T06:21:00.000-07:00</published><updated>2009-10-27T08:12:32.188-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hs-ffmpeg'/><category scheme='http://www.blogger.com/atom/ns#' term='ffmpeg'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='status'/><title type='text'>hs-ffmpeg now works from the box</title><content type='html'>&lt;div id="hs-ffmpeg-status"&gt;&lt;h4&gt;hs-ffmpeg status.&lt;/h4&gt;&lt;p&gt;Recently I have changed the way how &lt;a href="http://hackage.haskell.org/package/hs-ffmpeg"&gt;hs-ffmpeg&lt;/a&gt;. Now the package finds libraries locations by itself.&lt;/p&gt;&lt;p&gt;So, if you have developer libraries for &lt;em&gt;libavcodec&lt;/em&gt;, &lt;em&gt;libavformat&lt;/em&gt;, &lt;em&gt;libavutil&lt;/em&gt; and &lt;em&gt;libswscale&lt;/em&gt; installed, you could simply put:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cabal update&lt;br /&gt;cabal install hs-ffmpeg&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And this will install ffmpeg bindings to your machine. (If don't, please, drop me issue in &lt;a href="http://code.google.com/p/hs-ffmpeg/issues/list"&gt;bug tracker&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Shortly, I've used &lt;em&gt;autoconf&lt;/em&gt; to find &lt;em&gt;ffmpeg&lt;/em&gt; libraries and its dependencies, and also added support for &lt;em&gt;0.51.0&lt;/em&gt; versions of &lt;em&gt;ffmpeg&lt;/em&gt;. I found that binary packages from my &lt;em&gt;Ubuntu&lt;/em&gt; uses this version of library. Anyway, latest SVN versions of &lt;em&gt;ffmpeg&lt;/em&gt; should also work.&lt;/p&gt;&lt;/div&gt;&lt;div id="ffmpeg-tutorials-status"&gt;&lt;h4&gt;ffmpeg-tutorials status&lt;/h4&gt;&lt;p&gt;Another I found, that &lt;a href="http://hackage.haskell.org/package/SDL"&gt;SDL bindings&lt;/a&gt; were updated to &lt;em&gt;0.5.6&lt;/em&gt; version, and began conflict with my audio patch. So I created new &lt;a href="http://hs-ffmpeg.googlecode.com/files/SDL-0.5.6-audio.patch"&gt;audio patch&lt;/a&gt; for &lt;em&gt;SDL-0.5.6&lt;/em&gt;. To apply it, just simply run following commands in the SDL-0.5.6 directory:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;wget &amp;quot;http://hs-ffmpeg.googlecode.com/files/SDL-0.5.6-audio.patch&amp;quot;&lt;br /&gt;&lt;br /&gt;patch -p1 &amp;lt; SDL-0.5.6-audio.patch&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="to-do"&gt;&lt;h4&gt;To do&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;I think to slightly rewrite the code with buffers and use &lt;em&gt;ByteString&lt;/em&gt; instead. Don't know is it a good idea.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;I continue working on tutorials, next step is to implement &lt;a href="http://www.dranger.com/ffmpeg/tutorial04.html"&gt;tutorial 04&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Also I want to make more functional approach to the video processing, i.e. represent the sequence of packets as list, or use &lt;em&gt;Arrows&lt;/em&gt; to represent the decoding process.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-6326609122965136817?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/6326609122965136817/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/10/hs-ffmpeg-now-works-from-box.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/6326609122965136817'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/6326609122965136817'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/10/hs-ffmpeg-now-works-from-box.html' title='hs-ffmpeg now works from the box'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-4600308990438246226</id><published>2009-10-22T15:13:00.000-07:00</published><updated>2009-10-23T04:44:59.243-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='databases'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Working with databases in Haskell. Part 2</title><content type='html'>&lt;p&gt;Here I want to describe how I deal with tagged document storage in Haskell, using the relational database &lt;code&gt;sqlite3&lt;/code&gt; (through &lt;em&gt;HDBC&lt;/em&gt;). Yes, I know, that relational database work very poor with document-oriented and tagged data, but for a little amount of data it could be the proper way to implement storage.&lt;/p&gt;&lt;p&gt;The following code works with databases in the same way as described in my &lt;a href="http://progandprog.blogspot.com/2009/10/databases-in-haskell-or-release-power.html"&gt;previous post&lt;/a&gt;, and written in literate Haskell style, so you can easily save it in the &lt;code&gt;some_file.lhs&lt;/code&gt;, load it in &lt;code&gt;ghci&lt;/code&gt; and play with database.&lt;/p&gt;&lt;div id="task-description"&gt;&lt;h3&gt;Task description&lt;/h3&gt;&lt;p&gt;Let&amp;rsquo;s suppose we have to store documents with following properties:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;author &amp;mdash; the string, representing the author&lt;/li&gt;&lt;li&gt;title &amp;mdash; document&amp;rsquo;s title&lt;/li&gt;&lt;li&gt;body &amp;mdash; the content of the document&lt;/li&gt;&lt;li&gt;list of tags &amp;mdash; tags, assigned to the documents&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;All parameters will be &lt;code&gt;String&lt;/code&gt;s for simplicity, and we will not put any restrictions to the tags format.&lt;/p&gt;&lt;p&gt;The most obvious way to store these data is to use single table with separate fields for each property. But how to deal with tags? Document could have any amount of tags, or have nothing. We couldn&amp;rsquo;t create a table with infinite columns. Other solution, is to store all tags in one text field, and represent them as comma-separated lists, but in this case we have to put some restrictions to the tags (e.g. they shouldn&amp;rsquo;t hold commas) and each query involves different string matching and string updating, which should be done on client side, and those not efficient.&lt;/p&gt;&lt;p&gt;But there is the third way. We, along with the &lt;em&gt;database gurus&lt;/em&gt;, will use &lt;a href="http://en.wikipedia.org/wiki/Many-to-many_(data_model)"&gt;many-to-many relation&lt;/a&gt; to represent tagged documents.&lt;/p&gt;&lt;/div&gt;&lt;div id="begin-to-code"&gt;&lt;h3&gt;Begin to code&lt;/h3&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Database.HDBC&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- we will use sqlite3 driver here&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Database.HDBC.Sqlite3&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Control.Monad&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (liftM, when)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- all our database code will be enclosed in ReaderT monad&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- see previous post for details&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Control.Monad.Reader&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Data.Maybe&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Control.Applicative&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Define the internal Haskell types, from/to which we will serialize data:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;type&lt;/span&gt;&lt;span class="Normal NormalText"&gt; TagList = [&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;data&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Document = Doc {&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;       &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;author ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     , &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;title ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     , &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;content ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     } &lt;/span&gt;&lt;span class="Keyword"&gt;deriving&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (&lt;/span&gt;&lt;span class="Keyword Class"&gt;Eq&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, &lt;/span&gt;&lt;span class="Keyword Class"&gt;Show&lt;/span&gt;&lt;span class="Normal NormalText"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Our transaction type&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;type&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Transact a = ReaderT Connection &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; a&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Define some helper functions to work with database and the function that runs our transaction monad&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Get database connection object&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;conn ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Transact Connection&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; conn = ask&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Query the database without result (update queries)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;query ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; [SqlValue] -&amp;gt; Transact ()&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; query q v = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   c &amp;lt;- conn&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   liftM (&lt;/span&gt;&lt;span class="Function"&gt;const&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ()) $ liftIO $ run c q v&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Query the database with returning result&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;query' ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; [SqlValue] -&amp;gt; Transact [[SqlValue]]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; query' q v = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   c &amp;lt;- conn&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   liftIO $ quickQuery' c q v&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Run the transaction&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;runTransact ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; Transact a -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; a&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; runTransact dbname io = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   c &amp;lt;- connectSqlite3 dbname&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   runReaderT withCommit c&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;       &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;         withCommit = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           result &amp;lt;- io&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           conn &amp;gt;&amp;gt;= liftIO . commit&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; result&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;OK, all preparations are done, now start implementing the core of our document data-store.&lt;/p&gt;&lt;p&gt;In order to implement &lt;a href="http://en.wikipedia.org/wiki/Many-to-many_(data_model)"&gt;many-to-many relation&lt;/a&gt;, we have to create three tables: one for documents, one for tags and one is relation between tags and documents.&lt;/p&gt;&lt;/div&gt;&lt;div id="creating-databases"&gt;&lt;h3&gt;Creating databases&lt;/h3&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Create tables&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; createDocumentsTbl = &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;CREATE TABLE documents ( \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \docid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \author TEXT NOT NULL, \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \title TEXT NOT NULL, \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \content TEXT NOT NULL, \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \UNIQUE (author, title, content))&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; []&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; createTagsTbl =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;CREATE TABLE tags ( \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \tagid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \tag TEXT NOT NULL UNIQUE )&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; []&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; createDocTagsTbl =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;CREATE TABLE doctags ( \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \docid INTEGER, \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \tagid iNTEGER, \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \PRIMARY KEY (docid, tagid), \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \FOREIGN KEY (docid) REFERENCES documents (docid), \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \FOREIGN KEY (tagid) REFERENCES tags (tagid))&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; []&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Checks whether tables created, and create them otherwise&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; initializeDb = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     tables &amp;lt;- conn &amp;gt;&amp;gt;= liftIO . getTables&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     &lt;/span&gt;&lt;span class="Function"&gt;mapM_&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (\(tn,cf) -&amp;gt; when (tn &lt;/span&gt;&lt;span class="Others InfixOperator"&gt;`notElem`&lt;/span&gt;&lt;span class="Normal NormalText"&gt; tables) cf)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           [(&lt;/span&gt;&lt;span class="String"&gt;&amp;quot;documents&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, createDocumentsTbl)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           ,(&lt;/span&gt;&lt;span class="String"&gt;&amp;quot;tags&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, createTagsTbl)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           ,(&lt;/span&gt;&lt;span class="String"&gt;&amp;quot;doctags&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;, createDocTagsTbl)]&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you can load this file to the &lt;code&gt;ghci&lt;/code&gt; and execute:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;span class="Normal NormalText"&gt;runTransact &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;test.db&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; initializeDb&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;then check the database with &lt;code&gt;sqlite3 test.db&lt;/code&gt; command and see, that all tables were successfully created.&lt;/p&gt;&lt;/div&gt;&lt;div id="low-level-access-to-the-databases"&gt;&lt;h3&gt;Low-level access to the databases&lt;/h3&gt;&lt;p&gt;Lets start implementing basic functions to work with our documents:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Adds new tag to the database, do nothing if tag exists&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; addTag tag =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;INSERT OR IGNORE INTO tags (tag) VALUES (?)&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           [toSql tag]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Finds tag by name, returns id or Nothing&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; findTag tag =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     liftM (idListToMaybe) $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           query' &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;SELECT tagid FROM tags WHERE tag = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [toSql tag]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Return tag name by id&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; getTag tagid =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     liftM (&lt;/span&gt;&lt;span class="Function"&gt;fmap&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (fromSql . &lt;/span&gt;&lt;span class="Function"&gt;head&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) . listToMaybe) $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           query' &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;SELECT tag FROM tags WHERE tagid = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [tagid]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Remove tag from database&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; removeTagById tagid =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;DELETE FROM tags WHERE tagid = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [tagid]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Find document by name, author and content. Return docid or Nothing&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; findDocument doc = &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     liftM (idListToMaybe) $ &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           query' &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;SELECT docid FROM documents \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;                  \WHERE author = ? AND title = ? AND content = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;                  docToSqlValue doc [author, title, content]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Adds new document document to the database&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; addDocument doc =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;INSERT OR IGNORE INTO documents (author, title, content) \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;           \VALUES (?,?,?)&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $ docToSqlValue doc [author, title, content]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Return document by id&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; getDocument docid =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     liftM (listToDocument) $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           query' &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;SELECT author, title, content FROM documents \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;                  \WHERE docid = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [docid]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Remove document from the database&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; removeDocumentById docid =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;DELETE FROM documents WHERE docid = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [docid]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Adds link between document and tag&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; addLink docid tagid = &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;INSERT OR IGNORE INTO doctags VALUES (?, ?)&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           [docid, tagid]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Remove link between document and tag&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; removeLinkById docid tagid =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;DELETE FROM doctags WHERE docid = ? AND tagid = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           [docid, tagid]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Get tags for document, returns list of tagids&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; getDocumentTagsById docid =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     liftM (&lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Function"&gt;head&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           query' &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;SELECT tagid FROM doctags WHERE docid = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [docid]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Get document ids for the tag id&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; getTaggedDocumentsById tagid =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     liftM (&lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Function"&gt;head&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           query' &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;SELECT docid FROM doctags WHERE tagid = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [tagid]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="high-level-functions-to-work-with-data"&gt;&lt;h3&gt;High-level functions to work with data&lt;/h3&gt;&lt;p&gt;So the basic functions, that create separate entities in the database are implemented, now we could use that functions to combine high-level operations:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Adds new tag and return its id, or simply return id if tag exists&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- we used fromJust here, because tag should exists&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; addTag' tag = addTag tag &amp;gt;&amp;gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;               (liftM fromJust $ findTag tag)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Adds new document and its id, or simply return id if document exists&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; addDocument' doc = addDocument doc &amp;gt;&amp;gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;                    (liftM fromJust $ findDocument doc)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Adds complete document with tags&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; addCompleteDocument doc tags = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     docid &amp;lt;- addDocument' doc&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     tagids &amp;lt;- &lt;/span&gt;&lt;span class="Function"&gt;mapM&lt;/span&gt;&lt;span class="Normal NormalText"&gt; addTag' tags&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     &lt;/span&gt;&lt;span class="Function"&gt;mapM_&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (addLink docid) tagids&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; docid&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Get document tag names&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; getDocumentTagNames docid = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   tags &amp;lt;- getDocumentTagsById docid&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   liftM catMaybes $ &lt;/span&gt;&lt;span class="Function"&gt;mapM&lt;/span&gt;&lt;span class="Normal NormalText"&gt; getTag tags&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And so on. By the way, last function &lt;code&gt;getDocumentTagNames&lt;/code&gt; we could also implement as a single SQL query, using &lt;code&gt;JOIN&lt;/code&gt;:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Similar to `getDocumentTagNames` but implemented&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- through SQL JOINs&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; getDocumentTagNames' docid = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   liftM (&lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (fromSql . &lt;/span&gt;&lt;span class="Function"&gt;head&lt;/span&gt;&lt;span class="Normal NormalText"&gt;)) $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;         query' &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;SELECT tag \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;                \FROM tags LEFT JOIN doctags USING (tagid) \&lt;/span&gt;&lt;br /&gt;&lt;span class="String"&gt;&amp;gt;                \WHERE docid = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [docid]&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And finally, some helper functions, used before:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Converts select result into Just v or Nothing&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; idListToMaybe = &lt;/span&gt;&lt;span class="Function"&gt;fmap&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Function"&gt;head&lt;/span&gt;&lt;span class="Normal NormalText"&gt; . listToMaybe&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Converts Document structure to SqlValue list,&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- the second argument - list of fields to convert&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; docToSqlValue doc = &lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (toSql . ($ doc))&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Converts the list of SqlValues to the document&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; listToDocument = &lt;/span&gt;&lt;span class="Function"&gt;fmap&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (toDoc . &lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; fromSql) . listToMaybe&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;       toDoc [a,b,c] = Doc a b c&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;       toDoc _ = Doc &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id="conclusion"&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;I have just demonstrated how is easy to work with databases in Haskell, and also showed the basic principles of storing tagged documents, which is very important in the WEB (and often in desktop) applications.&lt;/p&gt;&lt;p&gt;The code is clearer than the code on &lt;code&gt;Visual Basic&lt;/code&gt; or &lt;code&gt;Delphi&lt;/code&gt; (from my memoirs) and simpler than code on &lt;code&gt;Python&lt;/code&gt;. And I think this proves that &lt;code&gt;Haskell&lt;/code&gt; is very good for real world applications.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-4600308990438246226?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/4600308990438246226/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/10/working-with-databases-in-haskell-part.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/4600308990438246226'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/4600308990438246226'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/10/working-with-databases-in-haskell-part.html' title='Working with databases in Haskell. Part 2'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-606966760401815629</id><published>2009-10-22T08:56:00.000-07:00</published><updated>2009-10-22T09:00:03.817-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='databases'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Databases in Haskell or "Release the power of monad transformers"</title><content type='html'>&lt;div id="databases-in-haskell-or-release-the-power-of-monad-transformers"&gt;&lt;p&gt;Many years ago almost all my programs were &amp;lsquo;&lt;em&gt;some kind of interface to database&lt;/em&gt;&amp;rsquo;. I have been programming on &lt;code&gt;C/C++&lt;/code&gt; so work with databases was simple and required a lot of &lt;span style="text-decoration: line-through;"&gt;stupid&lt;/span&gt; code.&lt;/p&gt;&lt;p&gt;Several days ago I returned to the databases but now with Haskell in my hands and functional-oriented mind in my head :). First time I coded in the &lt;code&gt;C&lt;/code&gt;-style from Haskell, but now, I think, I have found more functional way to work with databases. Of course this approach is not universal, and &lt;em&gt;database ninjas&lt;/em&gt; could blame me for it, but let me continue :).&lt;/p&gt;&lt;div id="combining-transactions-with-monad-transformers"&gt;&lt;h2&gt;Combining transactions with monad transformers&lt;/h2&gt;&lt;p&gt;I worked with &lt;code&gt;sqlite3&lt;/code&gt; database, but this approach should work with other databases too.&lt;/p&gt;&lt;p&gt;I enclosed all my database code into &lt;code&gt;ReaderT&lt;/code&gt; monad in the following way:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Database.HDBC&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Control.Monad&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;import&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Normal ModuleName"&gt;Control.Monad.Reader&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Keyword"&gt;type&lt;/span&gt;&lt;span class="Normal NormalText"&gt; DbTransaction a = ReaderT Connection &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; a&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- return the connection from the reader monad&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;conn ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; DbTransaction Connection&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; conn = ask&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- some useful functions to work with databases&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;    &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- query function executes simple query without returning any results&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- useful for create table and update queries&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;query ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; [SqlValue] -&amp;gt; DbTransaction ()&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; query q v = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   c &amp;lt;- conn&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   liftM (&lt;/span&gt;&lt;span class="Function"&gt;const&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ()) $ liftIO $ run c q v&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- query' function executes query with results (for select queries)&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;query' ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; [SqlValue] -&amp;gt; DbTransaction [[SqlValue]]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; query' q v = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   c &amp;lt;- conn&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   liftIO $ quickQuery' c q v&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From that moment we can create our own queries like following:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;span class="Comment"&gt;-- get the user id from the database&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;getUserId ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; DbTransaction (&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;Maybe&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; getUserId name = &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     liftM (maybeId) $ query' &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;SELECT id FROM users WHERE name = ?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [toSql name]&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;span class="Normal NormalText"&gt; maybeId [] = &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Nothing&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           maybeId [[&lt;/span&gt;&lt;span class="Function"&gt;id&lt;/span&gt;&lt;span class="Normal NormalText"&gt;]] = &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Just&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $ fromSql &lt;/span&gt;&lt;span class="Function"&gt;id&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Comment"&gt;-- add new article to the database&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;addArticle ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; DbTransaction ()&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; addArticle user_id title content =&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     query &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;INSERT INTO (user_id, title, body) VALUES (?,?,?)&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;           &lt;/span&gt;&lt;span class="Function"&gt;map&lt;/span&gt;&lt;span class="Normal NormalText"&gt; toSql [user_id, title, content]&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also we can combine all our actions under one big transaction, i. e. take a user name and article title and body, add article with user id:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;addArticle ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; DbTransaction ()&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; addArticle user title content = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;      uid &amp;lt;- getUserId user&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;      when (isJust uid) $ &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;        addArticle (fromJust uid) title content&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we have to explore how to run our transactions from the main program. I designed the function that takes the database name, connects to it, runs the transaction and commits everything:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; &lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;runTransaction ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; DbTransaction a -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; a&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; runTransaction dbname io = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   c &amp;lt;- connectSqlite3 dbname&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;   runReaderT withCommit  c&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;       withCommit = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;         result &amp;lt;- io&lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;         conn &amp;gt;&amp;gt;= liftIO . commit &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;         &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; result&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So each time we need to query database, we will do the following:&lt;/p&gt;&lt;pre class="sourceCode haskell"&gt;&lt;code&gt;&lt;span class="Normal NormalText"&gt;&amp;gt; runTransaction &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;mydb.db&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $ &lt;/span&gt;&lt;br /&gt;&lt;span class="Normal NormalText"&gt;&amp;gt;                addArticle &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Hamlet&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;The Question&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;To be or not to be&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Don&amp;rsquo;t know how are you, but I like this code. It is short, clear, database-dependent code wrote in the separate monad and couldn&amp;rsquo;t be randomly combined with other code, and also safe. If you will want to use another database interface, you only will have to change one function (&lt;code&gt;runTransaction&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;It is easy to add error handling to this code. Here is two ways:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;wrap the &lt;code&gt;DbTransaction&lt;/code&gt; into the &lt;code&gt;ErrorT&lt;/code&gt; monad, and wrap all HDBC functions to re-throw exceptions, or&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;use the internal HDBC exception handling mechanism (default behavior)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Anyway, this is only one example how to use monad transformers in real life, even for small projects. In the next posts I&amp;rsquo;ll show another uses of this approach.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-606966760401815629?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/606966760401815629/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/10/databases-in-haskell-or-release-power.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/606966760401815629'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/606966760401815629'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/10/databases-in-haskell-or-release-power.html' title='Databases in Haskell or &quot;Release the power of monad transformers&quot;'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-137846104233338274</id><published>2009-10-15T11:33:00.000-07:00</published><updated>2009-10-16T13:28:13.891-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='scripting'/><title type='text'>Control your ASUS WL500g Premium from the command line</title><content type='html'>&lt;p&gt;I have a nice router &lt;em&gt;Asus WL500g Premium&lt;/em&gt;, but I can&amp;rsquo;t say the same about my Internet provider. Several times a day it breaks the Internet connection in the way my router couldn&amp;rsquo;t restore it, so I have to manually disconnect and re-connect it. This is tremendously hard because of poor router&amp;rsquo;s admin interface. So, because I&amp;rsquo;m still learning Haskell, I decided to create simple command-line utility that will reconnect my router to the WAN.&lt;/p&gt;&lt;p&gt;After first release, I split the project into two components: a library, which connects to the router, and several command-line tools, that runs my usual activities automatically. You can install them using cabal:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cabal install WL500gPLib&lt;br /&gt;cabal install WL500gPControl&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;a href="http://hackage.haskell.org/package/WL500gPLib"&gt;WL500gP Library&lt;/a&gt; now could retrieve connection status, external IP address, DNS servers and log from the router, and could send &lt;code&gt;connect&lt;/code&gt;, &lt;code&gt;disconnect&lt;/code&gt; and &lt;code&gt;clear log&lt;/code&gt; commands. I implemented all these operations in the &lt;code&gt;Conn&lt;/code&gt; monad, which is simply Reader transformer over IO monad. This approach reduces boilerplate code and also adds automatic log off to the command chain.&lt;/p&gt;&lt;p&gt;The &lt;a href="http://hackage.haskell.org/package/WL500gPControl"&gt;WL500gP Remote Control&lt;/a&gt; package have two executables, to work with router. &lt;code&gt;WL500gPControl&lt;/code&gt; does everything, that library support. So to see log from the router, simply run:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;WL500gPControl -l&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To see all list of supported operations type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;WL500gPControl -h&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The connection info (user name, password and host address) &lt;code&gt;WL500gPControl&lt;/code&gt; takes from the &lt;code&gt;$HOME/.wl500gp&lt;/code&gt; file, but it could be any other file specified in the command line as the second argument. The file format is following:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;user: username&lt;br /&gt;password: password&lt;br /&gt;host: 192.168.1.1&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The second script is very simple and I wrote it only to show connection status in my &lt;code&gt;xmobar&lt;/code&gt;. It only prints &lt;code&gt;connected&lt;/code&gt; or &lt;code&gt;disconnected&lt;/code&gt; with color tags (green and red accordingly). Here is my config line from &lt;code&gt;.xmobarrc&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Run Com &amp;quot;WL500gPStatus&amp;quot; [&amp;quot;-c&amp;quot;, &amp;quot;$HOME/.wl500gp&amp;quot;] &amp;quot;Internet&amp;quot; 300&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;It took several hours to implement all these features, and from that time I never loaded my router&amp;rsquo;s admin page. And also I ensured that &lt;a href="http://hackage.haskell.org/package/tagsoup"&gt;tagsoup&lt;/a&gt; is the great library for fast and dirty HTML parsing.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-137846104233338274?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/137846104233338274/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/10/control-your-asus-wl500g-premium-from.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/137846104233338274'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/137846104233338274'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/10/control-your-asus-wl500g-premium-from.html' title='Control your ASUS WL500g Premium from the command line'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2187341306664604423.post-300096979645098817</id><published>2009-10-15T06:55:00.000-07:00</published><updated>2009-10-15T06:59:10.462-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hs-ffmpeg'/><category scheme='http://www.blogger.com/atom/ns#' term='ffmpeg'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Video processing on Haskell - easy</title><content type='html'>&lt;p&gt;Almost all my past jobs were in the field on video processing, creating custom filters, decoders and encoders. Several of these jobs were to implement complete video/audio players. I&amp;rsquo;ve used ffmpeg library to perform all these tasks, except once I&amp;rsquo;ve used GStreamer. I can&amp;rsquo;t say that GStreamer is bad, but it didn&amp;rsquo;t allow me to completely control the decoding process, stucks on malformed video files and it was really painful to develop the 24x7 application.&lt;/p&gt;&lt;p&gt;And I returned to ffmpeg again, but than I became a haskeller :) so the idea was to made ffmpeg closer to Haskell community. Today let me introduce the &lt;a href="http://hackage.haskell.org/package/hs-ffmpeg"&gt;hs-ffmpeg&lt;/a&gt; library for haskell.&lt;/p&gt;&lt;p&gt;Current version (&lt;em&gt;0.3.2&lt;/em&gt;) supports basic file opening, streams enumerating, video and audio decoding. To make the process more interesting I took &lt;a href="http://www.dranger.com/ffmpeg/"&gt;FFMpeg and SDL tutorial&lt;/a&gt; and implemented bindings in the sequence they are appeared in this tutorial. The second project, that shows how to use &lt;a href="http://hackage.haskell.org/package/hs-ffmpeg"&gt;hs-ffmpeg&lt;/a&gt; and implements tutorials is &lt;a href="http://hackage.haskell.org/package/ffmpeg-tutorials"&gt;ffmpeg-tutorials&lt;/a&gt;. It depends on &lt;a href="http://www.libsdl.org"&gt;SDL&lt;/a&gt;, so I&amp;rsquo;ve used &lt;a href="http://hackage.haskell.org/package/SDL"&gt;Haskell SDL bindings&lt;/a&gt; to work with video and audio.&lt;/p&gt;&lt;h2 id="several-notes-on-building-the-sources"&gt;Several notes on building the sources.&lt;/h2&gt;&lt;h3 id="building-hs-ffmpeg"&gt;Building &lt;a href="http://hackage.haskell.org/package/hs-ffmpeg"&gt;hs-ffmpeg&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;You have to change &lt;code&gt;hs-ffmpeg.cabal&lt;/code&gt; file to tune your ffmpeg library settings. In my case, ffmpeg installed under &lt;code&gt;/usr/local&lt;/code&gt; tree, so my include dirs have &lt;code&gt;/usr/local/include&lt;/code&gt; line and &lt;code&gt;extra-lib-dirs&lt;/code&gt; contains &lt;code&gt;/usr/local/lib&lt;/code&gt; value. Also in the &lt;code&gt;extra-libraries&lt;/code&gt; you should specify the complete list of libraries, against which your version of ffmpeg have been buit.&lt;/p&gt;&lt;p&gt;If you do everything right, than &lt;a href="http://hackage.haskell.org/package/hs-ffmpeg"&gt;hs-ffmpeg&lt;/a&gt; will build without any errors.&lt;/p&gt;&lt;h3 id="patching-the-sdl"&gt;Patching the SDL&lt;/h3&gt;&lt;p&gt;If you want to run &lt;a href="http://hackage.haskell.org/package/ffmpeg-tutorials"&gt;ffmpeg-tutorials&lt;/a&gt; (sure you want :)), than you have to patch SDL bindings first. Current SDL bindings version is 0.5.5, and I&amp;rsquo;ve created &lt;a href="http://hs-ffmpeg.googlecode.com/files/SDL-0.5.5-audio.patch"&gt;SDL audio patch&lt;/a&gt; to this version, so do the following:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;cabal fetch SDL&lt;br /&gt;cd /tmp&lt;br /&gt;tar xzvf ~/.cabal/packages/hackage.haskell.org/SDL/0.5.5/SDL-0.5.5.tar.gz&lt;br /&gt;cd SDL-0.5.5&lt;br /&gt;wget http://hs-ffmpeg.googlecode.com/files/SDL-0.5.5-audio.patch&lt;br /&gt;patch -p1 &amp;lt; SDL-0.5.5-audio.patch&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Than configure and build bindings. (&lt;em&gt;Note:&lt;/em&gt; you&amp;rsquo;ll have to install SDL development libraries on to your system).&lt;/p&gt;&lt;h3 id="running-ffmpeg-tutorials"&gt;Running &lt;a href="http://hackage.haskell.org/package/ffmpeg-tutorials"&gt;ffmpeg-tutorials&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Currently only three tutorials are implemented. &lt;code&gt;tutorial03&lt;/code&gt; is completely showing capabilities of ffmpeg in video and audio decoding. Just simply run it with video fil ename as an argument to see how it decodes and shows video and plays sound.&lt;/p&gt;&lt;h2 id="future-work"&gt;Future work&lt;/h2&gt;&lt;p&gt;I have only started this work, but I&amp;rsquo;d glad to hear comments and suggestions from people who interested in video processing on Haskell.&lt;/p&gt;&lt;p&gt;My future work plans are splitted onto several parts:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Continue implementing bindings to ffmpeg library&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Improve build system, profiling&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Started with multimedia combinators library, to program video and audio applications in more functional way (it is my dream)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id="credentials"&gt;Credentials:&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://hackage.haskell.org/package/hs-ffmpeg"&gt;hs-ffmpeg&lt;/a&gt; on Hackage&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://hackage.haskell.org/package/ffmpeg-tutorials"&gt;ffmpeg-tutorials&lt;/a&gt; on Hackage&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://patch-tag.com/r/VasylPasternak/hs-ffmpeg"&gt;hs-ffmpeg darcs repository&lt;/a&gt; on PatchTag&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="http://patch-tag.com/r/VasylPasternak/ffmpeg-tutorials"&gt;ffmpeg-tutorials darcs repository&lt;/a&gt; on PatchTag&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;project &lt;a href="http://code.google.com/p/hs-ffmpeg/issues/list"&gt;bug-tracker&lt;/a&gt; on Google Code&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-300096979645098817?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/300096979645098817/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/10/video-processing-on-haskell-easy.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/300096979645098817'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/300096979645098817'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/10/video-processing-on-haskell-easy.html' title='Video processing on Haskell - easy'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2187341306664604423.post-4746020124871278266</id><published>2009-04-13T14:42:00.000-07:00</published><updated>2009-04-13T15:01:25.247-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gettext'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='gtk'/><category scheme='http://www.blogger.com/atom/ns#' term='i18n'/><title type='text'>Multilingual UI and dynamic language selection with hgettext</title><content type='html'>&lt;p&gt;In the latest version of &lt;a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hgettext"&gt;hgettext&lt;/a&gt; I implemented all capabilities, supported by standard gettext module. The only thing is left --- to avoid the &lt;code&gt;unsafePerformIO&lt;/code&gt; in the code. Many haskers (Haskell hackers :) ) asked me to throw it away, and tried to help me with it, but I couldn't understood their solutions based on monads and monads transformers (yes, I'm only beginner in Haskell).&lt;/p&gt;&lt;p&gt;But the idea to remove &lt;code&gt;unsafePerformIO&lt;/code&gt; from the code chased me all last week, and I tried to make program translation in more Haskell's style.&lt;/p&gt;&lt;p&gt;Of course, when we will not use &lt;code&gt;unsafePerformIO&lt;/code&gt; our translation code will look like this:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;  __ ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;  __ = getText&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;  main = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       s &amp;lt;- __ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Some translation message&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       &lt;/span&gt;&lt;span class="Function"&gt;putStrLn&lt;/span&gt;&lt;span class="Normal NormalText"&gt; s&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;instead of:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;  __ ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;  __ = unsafePerformIO . getText&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;  main = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       &lt;/span&gt;&lt;span class="Function"&gt;putStrLn&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (__ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Some translation message&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;of course we could shorter it to:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;  __ ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;  __ = unsafePerformIO . getText&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;  main = &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;       __ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Some translation message&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &amp;gt;&amp;gt;= &lt;/span&gt;&lt;span class="Function"&gt;putStrLn&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;but, anyway, we have to type more code and put all translation into the &lt;code&gt;IO&lt;/code&gt; monad.&lt;/p&gt;&lt;p&gt;So, I think, if we have a little drawback in typing when we internationalize Haskell apps, we should have benefits of using them. These benefits should be:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;possibility to switch between languages during the program execution&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;ability to work with different translations simultaneously in the single proses (i.e. in different parts of code or different threads)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;should be some kind of notification to the rest program, that language is changed, to allow it redraw all visible elements on the screen&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;support of Windows platform. I don't know how &lt;code&gt;gettext&lt;/code&gt; works on Windows, but I am sure, that Haskell programmers shouldn't have any differences in build process on Linux or on Windows platforms.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;Henning Thielemann&lt;/strong&gt; proposed me to use monad transformers to implement these features, so I will try to follow his recommendations.&lt;/p&gt;&lt;p&gt;On this post, I'll try to show You how to implement the multilingual UI with runtime language switching. I think, these ideas will be implemented in the next &lt;a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hgettext"&gt;hgettext&lt;/a&gt; releases.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;All text below will be in literate Haskell, but complete sources with translation files you could download from &lt;a href="http://hgettext.googlecode.com/files/gtk-hello-0.0.2.tar.gz"&gt;Google code&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;First of all import all libraries. We will build &lt;strong&gt;Gtk&lt;/strong&gt; application.&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Keyword"&gt;module&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Main &lt;/span&gt;&lt;span class="Keyword"&gt;where&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Graphics.UI.Gtk hiding (get)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Text.Printf&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Text.I18N.GetText&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import System.Locale.SetLocale&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Codec.Binary.UTF8.&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Control.&lt;/span&gt;&lt;span class="Keyword Class"&gt;Monad&lt;/span&gt;&lt;span class="Normal NormalText"&gt;.State&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Control.&lt;/span&gt;&lt;span class="Keyword Class"&gt;Monad&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; import Data.IORef&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We will use &lt;code&gt;State&lt;/code&gt; monad to store our translation state. Our translation state is currently selected language and translation function&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Keyword"&gt;data&lt;/span&gt;&lt;span class="Normal NormalText"&gt; TransState = TransState {&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;       transLanguage ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt;       transFunction ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     }&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we will have only one trans function &lt;code&gt;getText&lt;/code&gt;, but to use it in the &lt;em&gt;Gtk&lt;/em&gt; application we have to decode string from UTF-8:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; getText' ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; getText' s = getText s &amp;gt;&amp;gt;= &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; . decodeString&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Lets introduce our &lt;code&gt;Translation&lt;/code&gt; monad. It will build with &lt;code&gt;StateT&lt;/code&gt; monad transformer and will contain our &lt;code&gt;TransState&lt;/code&gt;:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Keyword"&gt;type&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Translation a = StateT TransState &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; a &lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So all our functions, which uses internationalization capabilities, should work under &lt;code&gt;Translation&lt;/code&gt; monad.&lt;/p&gt;&lt;p&gt;Now, when all types are defined, we could write code from the main function:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; main ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ()&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; main = &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     translator &amp;lt;- makeTranslator __MESSAGE_CATALOG_DOMAIN__ __MESSAGE_CATALOG_DIR__&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     runWithLanguage &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; translator gtkGUI&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that &lt;code&gt;__MESSAGE_CATALOG_DOMAIN__&lt;/code&gt; and &lt;code&gt;__MESSAGE_CATALOG_DIR__&lt;/code&gt; are preprocessor definitions, and will be substituted with appropriate text only during cabal build (See the &lt;a href="http://progandprog.blogspot.com/2009/04/configure-and-install-internationalized.html"&gt;Configuring and install internationalized Haskell application&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;&lt;code&gt;makeTranslator&lt;/code&gt; simply wraps the &lt;code&gt;bindTextDomain&lt;/code&gt; and &lt;code&gt;textDomain&lt;/code&gt; functions and return the function which translate messages (in our case it is &lt;code&gt;getText'&lt;/code&gt;).&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; makeTranslator domain dir =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     bindTextDomain domain (&lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Just&lt;/span&gt;&lt;span class="Normal NormalText"&gt; dir)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     textDomain (&lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Just&lt;/span&gt;&lt;span class="Normal NormalText"&gt; domain)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; getText'&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;runWithLanguage&lt;/code&gt; just run our &lt;code&gt;gtkGUI&lt;/code&gt; function in the &lt;code&gt;Translation&lt;/code&gt; monad:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; runWithLanguage locale translator code = &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     result &amp;lt;- runStateT code (TransState locale translator)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $ &lt;/span&gt;&lt;span class="Function"&gt;fst&lt;/span&gt;&lt;span class="Normal NormalText"&gt; result&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;gtkGUI&lt;/code&gt; has a type:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; gtkGUI ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Translation ()&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and if we want to call function from the &lt;code&gt;IO&lt;/code&gt; monad, we have to use &lt;code&gt;liftIO&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Our Gtk application will have two similar panels, with different language settings on them. Window's title will be written in the default system language (could be changed with environment variable &lt;code&gt;LANG&lt;/code&gt;). Panels will be initialized by English (left) and German (right).&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; gtkGUI =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     liftIO $ initGUI&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     wndTitle &amp;lt;- __ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;i18n Test&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     mainWindow &amp;lt;- liftIO $ &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                            wnd &amp;lt;- windowNew&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                            set wnd [windowTitle := wndTitle]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                            &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; wnd&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                                   &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     box &amp;lt;- liftIO $ hBoxNew &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;False&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     firstPanel &amp;lt;- withLanguage &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;en_US.UTF-8&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $ constructPanel&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     secondPanel &amp;lt;- withLanguage &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;de_DE.UTF-8&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $ constructPanel&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                    &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Function"&gt;mapM_&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (\w -&amp;gt; liftIO $ boxPackStart box w PackNatural &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;10&lt;/span&gt;&lt;span class="Normal NormalText"&gt;) [firstPanel, secondPanel]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     liftIO $ &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;              containerAdd mainWindow box&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;              onDestroy mainWindow mainQuit&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;              widgetShowAll mainWindow&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;              mainGUI&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I kept &lt;code&gt;__&lt;/code&gt; function, because it is very easy to mark all strings to translate by underscores, but now it takes translation function from the &lt;code&gt;Translation&lt;/code&gt; monad:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; __ ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; Translation &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; __ str =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     (TransState locale translator) &amp;lt;- get&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     liftIO $ withLocale locale (translator str) (&lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; str)&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we want to switch between the languages during the program execution and simultaneously use several languages, we will have to set locale to the proper value each time before we call our translator function. &lt;code&gt;withLocale&lt;/code&gt; allow us to switch to the proper locale, run the code, and switch back to the previous locale setting.&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- Sets locale to `locale`, executes the `action` and returns&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- to previous local setting. Executes `err_action` when error&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="Comment"&gt;-- occurred (in locale setting)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; withLocale ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; m -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; m -&amp;gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; m&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; withLocale locale action err_action =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     mprevloc &amp;lt;- setLocale LC_MESSAGES &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Nothing&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     (&lt;/span&gt;&lt;span class="Function"&gt;flip&lt;/span&gt;&lt;span class="Normal NormalText"&gt; $ &lt;/span&gt;&lt;span class="Function"&gt;maybe&lt;/span&gt;&lt;span class="Normal NormalText"&gt; err_action) mprevloc $ \prevloc -&amp;gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         mthislocale &amp;lt;- setLocale LC_MESSAGES (&lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Just&lt;/span&gt;&lt;span class="Normal NormalText"&gt; locale)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         result &amp;lt;- &lt;/span&gt;&lt;span class="Function"&gt;maybe&lt;/span&gt;&lt;span class="Normal NormalText"&gt; err_action (\_ -&amp;gt; action) mthislocale&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         setLocale LC_MESSAGES (&lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Just&lt;/span&gt;&lt;span class="Normal NormalText"&gt; prevloc)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; result&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;withLanguage&lt;/code&gt; is similar to &lt;code&gt;runWithLanguage&lt;/code&gt; but it works in the &lt;code&gt;Translation&lt;/code&gt; monad and sets only locale:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; withLanguage ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;span class="Normal NormalText"&gt; -&amp;gt; Translation a -&amp;gt; Translation a&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; withLanguage locale code =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     (TransState locale' translator') &amp;lt;- get&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     liftIO $ runWithLanguage locale translator' code&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;constructPanel&lt;/code&gt; creates very simple UI&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; constructPanel =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     localeEntry &amp;lt;- liftIO $ entryNew&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     currentLocale &amp;gt;&amp;gt;= liftIO . entrySetText localeEntry&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     localeButton &amp;lt;- liftIO $ buttonNew&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     localeBox &amp;lt;- liftIO $ hBoxNew &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;False&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Function"&gt;mapM_&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (\w -&amp;gt; liftIO $ boxPackStart localeBox w PackNatural &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;               [toWidget localeEntry,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                toWidget localeButton]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     label &amp;lt;- liftIO $ labelNew &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Nothing&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     entry &amp;lt;- liftIO $ entryNew&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     label2 &amp;lt;- liftIO $ labelNew &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Nothing&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     button &amp;lt;- liftIO $ buttonNew&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     msgHandler &amp;lt;- liftIO $ onClicked button (&lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ()) &amp;gt;&amp;gt;= newIORef&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;               &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;let&lt;/span&gt;&lt;span class="Normal NormalText"&gt; translateActions = [&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;          __ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Set&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &amp;gt;&amp;gt;= liftIO . buttonSetLabel localeButton,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;          __ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Write down your name, please:&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &amp;gt;&amp;gt;= liftIO . labelSetText label,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;          __ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;And than press button:&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &amp;gt;&amp;gt;= liftIO . labelSetText label2,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;          __ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Button&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &amp;gt;&amp;gt;= liftIO . buttonSetLabel button,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;          __ &lt;/span&gt;&lt;span class="String"&gt;&amp;quot;Hello, %s, how are you?&amp;quot;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &amp;gt;&amp;gt;= \s -&amp;gt; liftIO $ &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                                                 &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                                                 readIORef msgHandler &amp;gt;&amp;gt;= signalDisconnect&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                                                 onClicked button (clickAction entry s) &amp;gt;&amp;gt;= writeIORef msgHandler&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                                                 &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ()]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;         &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Function"&gt;sequence_&lt;/span&gt;&lt;span class="Normal NormalText"&gt; translateActions&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     translateClick &amp;lt;- fromTranslation $ &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                       &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                       locale &amp;lt;- liftIO $ entryGetText localeEntry&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                       withLanguage locale $ &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                                    &lt;/span&gt;&lt;span class="Function"&gt;sequence_&lt;/span&gt;&lt;span class="Normal NormalText"&gt; translateActions&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                                              &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     liftIO $ onClicked localeButton translateClick&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     box &amp;lt;- liftIO $ vBoxNew &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;False&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;             &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Function"&gt;mapM_&lt;/span&gt;&lt;span class="Normal NormalText"&gt; (\w -&amp;gt; liftIO $ boxPackStart box w PackNatural &lt;/span&gt;&lt;span class="DecVal Decimal"&gt;0&lt;/span&gt;&lt;span class="Normal NormalText"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;           [toWidget localeBox,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;            toWidget label,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;            toWidget entry,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;            toWidget label2,&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;            toWidget button]&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;                  &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; box&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; clickAction entry greet =&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Keyword"&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     name &amp;lt;- entryGetText entry&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     dialog &amp;lt;- messageDialogNew &lt;/span&gt;&lt;span class="Keyword DataConstructor"&gt;Nothing&lt;/span&gt;&lt;span class="Normal NormalText"&gt; [DialogModal] &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;               MessageQuestion ButtonsOk (printf greet name)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     dialogRun dialog&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     widgetDestroy dialog&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;     &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; ()&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt;            &lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; currentLocale ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Translation &lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;String&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; currentLocale = get &amp;gt;&amp;gt;= &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; . transLanguage&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, to implement language switching, I grouped all locale specific actions in the one list, and execute it with &lt;code&gt;sequence_&lt;/code&gt;. The only hard part --- is to reset previous message button handler and set new, with correct translation. I've used &lt;code&gt;IORef&lt;/code&gt; here, but I think, there is better solution.&lt;/p&gt;&lt;p&gt;&lt;code&gt;fromTranslation&lt;/code&gt; runs the new Translation monad with current settings and returns action in the &lt;code&gt;IO&lt;/code&gt; monad:&lt;/p&gt;&lt;pre class="sourceCode literatehaskell"&gt;&lt;code&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Function FunctionDefinition"&gt; fromTranslation ::&lt;/span&gt;&lt;span class="Normal NormalText"&gt; Translation a -&amp;gt; Translation (&lt;/span&gt;&lt;span class="DataType TypeConstructor"&gt;IO&lt;/span&gt;&lt;span class="Normal NormalText"&gt; a)&lt;/span&gt;&lt;br /&gt;&lt;span class="Char Special"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Normal NormalText"&gt; fromTranslation trans = get &amp;gt;&amp;gt;= &lt;/span&gt;&lt;span class="Function"&gt;return&lt;/span&gt;&lt;span class="Normal NormalText"&gt; . evalStateT trans&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When You try to run this application, you should see the following:&lt;/p&gt;&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_4up_IEw7D7E/SeO1sznc88I/AAAAAAAAAjE/rU-7kTUx-BE/s1600-h/shot-initial.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 127px;" src="http://1.bp.blogspot.com/_4up_IEw7D7E/SeO1sznc88I/AAAAAAAAAjE/rU-7kTUx-BE/s400/shot-initial.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5324298965856613314" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Than edit locale field and press button at the right side, you shoud see panel will redraw in the currently setted locale:&lt;/p&gt;&lt;p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_4up_IEw7D7E/SeO1_NlAyRI/AAAAAAAAAjM/Jk2tinXQlcE/s1600-h/shot-next.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 115px;" src="http://2.bp.blogspot.com/_4up_IEw7D7E/SeO1_NlAyRI/AAAAAAAAAjM/Jk2tinXQlcE/s400/shot-next.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5324299282063345938" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Yes, I know this code is not beautiful, it reflects my knowledge of Haskell at this moment. There are several questions to experienced haskellers:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Are there possibility to avoid &lt;code&gt;liftIO&lt;/code&gt; calling so many times ?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;How to make code which updates language string more clear and readable ? Are there some method to avoid &lt;code&gt;IORef&lt;/code&gt; during creation internationalized message boxes ?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-4746020124871278266?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/4746020124871278266/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/04/multilingual-ui-and-dynamic-language.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/4746020124871278266'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/4746020124871278266'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/04/multilingual-ui-and-dynamic-language.html' title='Multilingual UI and dynamic language selection with hgettext'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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://1.bp.blogspot.com/_4up_IEw7D7E/SeO1sznc88I/AAAAAAAAAjE/rU-7kTUx-BE/s72-c/shot-initial.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2187341306664604423.post-504151952532666341</id><published>2009-04-03T09:39:00.000-07:00</published><updated>2009-04-03T10:00:04.352-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gettext'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='i18n'/><title type='text'>Configure and install internationalized Haskell application</title><content type='html'>&lt;p&gt;My tutorial about &lt;a href="http://progandprog.blogspot.com/2009/03/i18n-and-haskell.html"&gt;internationalization of Haskell programs&lt;/a&gt; will not be complete without explaining how to configure and install Haskell package with internationalization support. Who tried all steps of previous tutorial could admit, that installation of localized data is very long, complex and boring routine, so there must be a way to simplify it (a &lt;em&gt;Haskell&lt;/em&gt; way).&lt;/p&gt;&lt;p&gt;&lt;em&gt;Now it is.&lt;/em&gt; From the version 0.1.5 of &lt;a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hgettext"&gt;hgettext&lt;/a&gt; package, there is included module, that teaches &lt;em&gt;Cabal&lt;/em&gt; to install language files.&lt;/p&gt;&lt;p&gt;So, download new &lt;a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hgettext"&gt;hgettext&lt;/a&gt; and create for our &lt;a href="http://progandprog.blogspot.com/2009/03/i18n-and-haskell.html"&gt;hello program&lt;/a&gt; real world installer.&lt;/p&gt;&lt;h3 id="directory-structure"&gt;Directory structure&lt;/h3&gt;&lt;p&gt;Currently we have following files:&lt;/p&gt;&lt;dl&gt;&lt;dt&gt;&lt;code&gt;Main.hs&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;&lt;pre&gt;&lt;code&gt;The `hello` program itself.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/dd&gt;&lt;dt&gt;&lt;code&gt;messages.pot&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;&lt;pre&gt;&lt;code&gt;Template file, which contain all strings to be translated. This file&lt;br /&gt;should be included into the distribution to allow other users to&lt;br /&gt;generate translation file for their language.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/dd&gt;&lt;dt&gt;&lt;code&gt;en.po, de.po&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;&lt;pre&gt;&lt;code&gt;Translations to the English and German&lt;br /&gt;languages. These files should be installed to the `locale` folder and&lt;br /&gt;our program has to be able to find them (has to know where they going&lt;br /&gt;to be installed)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/dd&gt;&lt;/dl&gt;&lt;p&gt;Any other files could be generated from the previous, so they shouldn't be included to the distribution package.&lt;/p&gt;&lt;p&gt;Let's create the directory structure for our project. This is simple project, so directory structure should be simple too. Here it is:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;hello\&lt;br /&gt;   |&lt;br /&gt;   |-po\&lt;br /&gt;   |  |&lt;br /&gt;   |  |-messages.pot&lt;br /&gt;   |  |-en.po&lt;br /&gt;   |  |-de.po&lt;br /&gt;   |&lt;br /&gt;   |-src\&lt;br /&gt;       |&lt;br /&gt;       |-Main.hs&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="create-install-script"&gt;Create install script&lt;/h3&gt;&lt;p&gt;In order to create a cabal package, we have to add only two files. The first is &lt;code&gt;hello.cabal&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Name:                   hello&lt;br /&gt;Version:                0.1.3&lt;br /&gt;Cabal-Version:          &amp;gt;= 1.6&lt;br /&gt;&lt;br /&gt;License:                BSD3&lt;br /&gt;&lt;br /&gt;Author:                 James Bond&lt;br /&gt;Maintainer:             James.Bond@MI6.bi&lt;br /&gt;Copyright:              2009 James Bond&lt;br /&gt;Category:               Hello&lt;br /&gt;&lt;br /&gt;Synopsis:               Internationalized Hello sample&lt;br /&gt;Build-Type:             Simple&lt;br /&gt;&lt;br /&gt;Extra-Source-Files:     po/*.po po/*.pot&lt;br /&gt;&lt;br /&gt;x-gettext-po-files:     po/*.po &lt;br /&gt;x-gettext-domain-name:  hs-hello&lt;br /&gt;&lt;br /&gt;Executable hello&lt;br /&gt;        Main-Is:                Main.hs&lt;br /&gt;        Hs-Source-Dirs:         src      &lt;br /&gt;        Build-Depends:          base,hgettext &amp;gt;= 0.1.5, setlocale&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is standard &lt;code&gt;.cabal&lt;/code&gt; file, but there we added two more lines:&lt;/p&gt;&lt;dl&gt;&lt;dt&gt;&lt;code&gt;x-gettext-po-files&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;&lt;pre&gt;&lt;code&gt;Tells cabal where ar PO files to install&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/dd&gt;&lt;dt&gt;&lt;code&gt;x-gettext-domain-name&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;&lt;pre&gt;&lt;code&gt;Sets the domain name, under which files will be installed &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/dd&gt;&lt;/dl&gt;&lt;p&gt;For other details see documentation for &lt;a href="http://hackage.haskell.org/packages/archive/hgettext"&gt;hgettext&lt;/a&gt; &lt;code&gt;Distribution.Simple.I18N.GetText&lt;/code&gt; module.&lt;/p&gt;&lt;p&gt;Note that we also enumerated &lt;code&gt;*.po&lt;/code&gt; files in the &lt;code&gt;extra-source-files&lt;/code&gt; section to add them to the distribution package.&lt;/p&gt;&lt;p&gt;The second file to create --- &lt;code&gt;Setup.hs&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;import Distribution.Simple.I18N.GetText&lt;br /&gt;&lt;br /&gt;main = gettextDefaultMain&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;gettextDefaultMain&lt;/code&gt; function substitutes the &lt;code&gt;defaultMain&lt;/code&gt; function, but also adds several install hooks to the cabal package, to handle internationalization stuff.&lt;/p&gt;&lt;h3 id="update-the-program-code"&gt;Update the program code&lt;/h3&gt;&lt;p&gt;So our installer knows where to put the &lt;code&gt;*.po&lt;/code&gt; files and the domain name for them. Our code should know it too --- to make proper initialization. It is not Haskell way to duplicate same information twice, so let's modify the code to get the initialization parameters directly from the installer:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module Main where&lt;br /&gt;&lt;br /&gt;import Text.Printf&lt;br /&gt;import Text.I18N.GetText&lt;br /&gt;import System.Locale.SetLocale&lt;br /&gt;import System.IO.Unsafe&lt;br /&gt;&lt;br /&gt;__ :: String -&amp;gt; String&lt;br /&gt;__ = unsafePerformIO . getText&lt;br /&gt;&lt;br /&gt;main = do&lt;br /&gt;  setLocale LC_ALL (Just &amp;quot;&amp;quot;) &lt;br /&gt;  bindTextDomain __MESSAGE_CATALOG_DOMAIN__ (Just __MESSAGE_CATALOG_DIR__)&lt;br /&gt;  textDomain __MESSAGE_CATALOG_DOMAIN__&lt;br /&gt;&lt;br /&gt;  putStrLn (__ &amp;quot;Please enter your name:&amp;quot;)&lt;br /&gt;  name &amp;lt;- getLine&lt;br /&gt;  printf (__ &amp;quot;Hello, %s, how are you?\n&amp;quot;) name&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, the only lines were changed are:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;  bindTextDomain __MESSAGE_CATALOG_DOMAIN__ (Just __MESSAGE_CATALOG_DIR__)&lt;br /&gt;  textDomain __MESSAGE_CATALOG_DOMAIN__&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice. &lt;code&gt;__MESSAGE_CATALOG_DOMAIN__&lt;/code&gt; and &lt;code&gt;__MESSAGE_CATALOG_DIR__&lt;/code&gt; are macro definitions, whose hold configured strings from the Cabal.&lt;/p&gt;&lt;h3 id="thats-all"&gt;That's all?&lt;/h3&gt;&lt;p&gt;Actually, yes. Now you could configure, build and install newly created package by invoking commands:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;runhaskell Setup.hs configure&lt;br /&gt;runhaskell Setup.hs build&lt;br /&gt;runhaskell Setup.hs install&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And test it.&lt;/p&gt;&lt;p&gt;Have a nice weekend :)&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;PS: Complete project tarball you can find &lt;a href="http://hgettext.googlecode.com/files/hello-0.1.3.tar.gz"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-504151952532666341?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/504151952532666341/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/04/configure-and-install-internationalized.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/504151952532666341'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/504151952532666341'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/04/configure-and-install-internationalized.html' title='Configure and install internationalized Haskell application'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-8321037515026961624</id><published>2009-03-28T06:11:00.000-07:00</published><updated>2009-03-28T06:25:41.402-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='i18n'/><title type='text'>hgettext on Hackage</title><content type='html'>I've got access to the &lt;a href="http://www.haskell.org"&gt;Haskell.org&lt;/a&gt; and &lt;a href="http://hackage.haskell.org/"&gt;Hackage&lt;/a&gt;, so now you could get &lt;a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/hgettext"&gt;hgettext&lt;/a&gt; package from Hackage by typing &lt;br /&gt;&lt;br /&gt;&lt;tt&gt;cabal install --global hgettext&lt;/tt&gt;&lt;br /&gt;&lt;br /&gt;There is also a wiki page about &lt;a href="http://www.haskell.org/haskellwiki/Internationalization_of_Haskell_programs"&gt;internationalization of Haskell applications&lt;/a&gt; on the Haskell.org&lt;br /&gt;&lt;br /&gt;In future only wiki and Hackage will contain most recent versions of hgettext library and documentation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-8321037515026961624?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/8321037515026961624/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/03/hgettext-on-hackage.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/8321037515026961624'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/8321037515026961624'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/03/hgettext-on-hackage.html' title='hgettext on Hackage'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-192070231817728434</id><published>2009-03-27T08:00:00.001-07:00</published><updated>2009-04-02T03:24:34.457-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gettext'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='i18n'/><title type='text'>I18n and Haskell</title><content type='html'>&lt;p&gt;&lt;a href="http://progandprog.blogspot.com/2009/03/i18n-and-haskell.html"&gt;I18n and Haskell&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The first things I tried to code in Haskell were UI programs with Gtk and several console tools. It was the opposite approach to the one usual for learning Haskell; Haskellers mostly learn the language by solving hard algorithmic tasks. So my first problem was not about understanding monads, but to use UTF-8 in the code and to create multilingual interfaces.&lt;/p&gt;&lt;p&gt;The first of those problems seems to have been solved already (though I'd like to see native UTF-8 support in Haskell). But the second is not. Today I'll try to fill the gap in the internationalization (also known as i18n) of the Haskell programs.&lt;/p&gt;&lt;p&gt;The approach I'll talk about is based on GNU &lt;a href="http://www.gnu.org/software/gettext/"&gt;gettext&lt;/a&gt; utility. All my experience on this utility is taken from internationalizing Python applications. So I adapted this experience to the Haskell world.&lt;/p&gt;&lt;p&gt;Let's start with an example. Suppose that we want to make the following program multilingual:&lt;/p&gt;&lt;pre class="haskell numberlines"&gt;&lt;code&gt;module Main where&lt;br /&gt;&lt;br /&gt;import IO &lt;br /&gt;&lt;br /&gt;main = do&lt;br /&gt;  putStrLn &amp;quot;Please enter your name:&amp;quot;&lt;br /&gt;  name &amp;lt;- getLine&lt;br /&gt;  putStrLn $ &amp;quot;Hello, &amp;quot; ++ name ++ &amp;quot;, how are you?&amp;quot;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using these &lt;a href="http://www.gnu.org/software/gettext/manual/gettext.html#Preparing-Strings"&gt;recomendations&lt;/a&gt;, prepare strings and wrap them to some 'translation' function '__':&lt;/p&gt;&lt;pre class="haskell numberlines"&gt;&lt;code&gt;module Main where&lt;br /&gt;&lt;br /&gt;import IO &lt;br /&gt;import Text.Printf&lt;br /&gt;&lt;br /&gt;__ = id&lt;br /&gt;&lt;br /&gt;main = do&lt;br /&gt;  putStrLn (__ &amp;quot;Please enter your name:&amp;quot;)&lt;br /&gt;  name &amp;lt;- getLine&lt;br /&gt;  printf (__ &amp;quot;Hello, %s, how are you?&amp;quot;) name&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We will return to the definition of '__' a bit later; for now we will leave the function empty (&lt;code&gt;id&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;The next step is to generate a POT file (a template which contains all strings to needed to be translated). For Python, C, C++ and Scheme there is the xgettext utility, but it doesn't support Haskell. So I created simple utility, that does the same thing for haskell files --- &lt;strong&gt;hgettext&lt;/strong&gt;. You could find it on Hackage.&lt;/p&gt;&lt;p&gt;Now, from the directory that contains your project, run this command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;hgettext -k __ -o messages.pot Main.hs&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It will gather all strings containing the function '__' from the Main.hs and write everything to messages.pot.&lt;/p&gt;&lt;p&gt;Now look at the resulting pot file:&lt;/p&gt;&lt;pre class="numberlines"&gt;&lt;code&gt;&lt;br/&gt;# Translation file&lt;br /&gt;&lt;br /&gt;msgid &amp;quot;&amp;quot;&lt;br /&gt;msgstr &amp;quot;&amp;quot;&lt;br /&gt;&lt;br /&gt;&amp;quot;Project-Id-Version: PACKAGE VERSION\n&amp;quot;&lt;br /&gt;&amp;quot;Report-Msgid-Bugs-To: \n&amp;quot;&lt;br /&gt;&amp;quot;POT-Creation-Date: 2009-01-13 06:05-0800\n&amp;quot;&lt;br /&gt;&amp;quot;PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n&amp;quot;&lt;br /&gt;&amp;quot;Last-Translator: FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n&amp;quot;&lt;br /&gt;&amp;quot;Language-Team: LANGUAGE &amp;lt;LL@li.org&amp;gt;\n&amp;quot;&lt;br /&gt;&amp;quot;MIME-Version: 1.0\n&amp;quot;&lt;br /&gt;&amp;quot;Content-Type: text/plain; charset=UTF-8\n&amp;quot;&lt;br /&gt;&amp;quot;Content-Transfer-Encoding: 8bit\n&amp;quot;&lt;br /&gt;&lt;br /&gt;#: Main.hs:0&lt;br /&gt;msgid &amp;quot;Please enter your name:&amp;quot;&lt;br /&gt;msgstr &amp;quot;&amp;quot;&lt;br /&gt;&lt;br /&gt;#: Main.hs:0&lt;br /&gt;msgid &amp;quot;Hello, %s, how are you?\n&amp;quot;&lt;br /&gt;msgstr &amp;quot;&amp;quot;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are interested in the last part of this file -- the parts beginning with &lt;code&gt;#: Main.hs:...&lt;/code&gt;. Each is followed by a pair of lines beginning with &lt;code&gt;msgid&lt;/code&gt; and &lt;code&gt;msgstr&lt;/code&gt;. &lt;code&gt;msgid&lt;/code&gt; is the original text from the code, and &lt;code&gt;msgstr&lt;/code&gt; is the translated string. Each language should have its own translation file. I will create two translations: German and English.&lt;/p&gt;&lt;p&gt;To create a PO file for specific locale we should use the &lt;code&gt;msginit&lt;/code&gt; utility.&lt;br/&gt;To generate the German translation template run:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;msginit --input=messages.pot --locale=de.UTF-8&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And for English translations run:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;msginit --input=messages.pot --locale=en.UTF-8&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we look at the generated files (&lt;code&gt;en.po&lt;/code&gt; and &lt;code&gt;de.po&lt;/code&gt;), we will see that English translation is completely filled, only the German PO file needs to be edited. So we fill it with following strings:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#: Main.hs:0&lt;br /&gt;msgid &amp;quot;Please enter your name:&amp;quot;&lt;br /&gt;msgstr &amp;quot;Wie heißen Sie?&amp;quot;&lt;br /&gt;&lt;br /&gt;#: Main.hs:0&lt;br /&gt;msgid &amp;quot;Hello, %s, how are you?\n&amp;quot;&lt;br /&gt;msgstr &amp;quot;Hallo, %s, wie geht es Ihnen?\n&amp;quot;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we have to create directories where these translations should be placed. Originally all translation files are placed in the folder &lt;code&gt;/usr/share/locale/&lt;/code&gt; , but you are free to select a different place. Run:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;mkdir -p {de,en}/LC_MESSAGES&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will create two sub-directories 'de' and 'en', each containing &lt;code&gt;LC_MESSAGES&lt;/code&gt;, in the current directory. Now we use the &lt;code&gt;msgfmt&lt;/code&gt; tool to encode our po files to mo files (binary translation files):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;msgfmt --output-file=en/LC_MESSAGES/hello.mo en.po&lt;br /&gt;msgfmt --output-file=de/LC_MESSAGES/hello.mo de.po&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ok, now the preparatory tasks are done. The final step is to modify the code to support the internationalization:&lt;/p&gt;&lt;pre class="haskell numberlines"&gt;&lt;code&gt;module Main where&lt;br /&gt;&lt;br /&gt;import IO &lt;br /&gt;import Text.I18N.GetText&lt;br /&gt;import System.Locale.SetLocale&lt;br /&gt;import System.IO.Unsafe&lt;br /&gt;&lt;br /&gt;__ :: String -&amp;gt; String&lt;br /&gt;__ = unsafePerformIO . getText&lt;br /&gt;&lt;br /&gt;main = do&lt;br /&gt;  setLocale LC_ALL (Just &amp;quot;&amp;quot;) &lt;br /&gt;  bindTextDomain &amp;quot;hello&amp;quot; &amp;quot;.&amp;quot; &lt;br /&gt;  textDomain &amp;quot;hello&amp;quot; &lt;br /&gt;&lt;br /&gt;  putStrLn (__ &amp;quot;Please enter your name:&amp;quot;)&lt;br /&gt;  name &amp;lt;- getLine&lt;br /&gt;  printf (__ &amp;quot;Hello, %s, how are you?\n&amp;quot;) name&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here we added three initialization strings:&lt;/p&gt;&lt;pre class="haskell"&gt;&lt;code&gt;setLocale LC_ALL (Just &amp;quot;&amp;quot;)&lt;br /&gt;bindTextDomain &amp;quot;hello&amp;quot; &amp;quot;.&amp;quot;&lt;br /&gt;textDomain &amp;quot;hello&amp;quot; &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You'll have to download the &lt;code&gt;setlocale&lt;/code&gt; package to enable the first function: it sets the current locale to the default value. The next two functions tell &lt;code&gt;gettext&lt;/code&gt; to take the &amp;quot;hello.mo&amp;quot; message file from the locale directory (I set it to &amp;quot;.&amp;quot;, but in general case, this directory should be passed from the package configuration).&lt;/p&gt;&lt;p&gt;The final step is to define the function '__'. It simply calls &lt;code&gt;getText&lt;/code&gt; from the module &lt;code&gt;Text.I18N.GetText&lt;/code&gt;. Its type is &lt;code&gt;String -&amp;gt; IO String&lt;/code&gt; so I used &lt;code&gt;unsafePerformIO&lt;/code&gt; to make it simpler the. The &lt;code&gt;GetText&lt;/code&gt; library was written by me, so maybe in the future it will be possible to implement a version of &lt;code&gt;getText&lt;/code&gt; which will work outside the IO monad.&lt;/p&gt;&lt;p&gt;Now you can build and try the program in different locales:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;user&amp;gt; ghc --make Main.hs&lt;br /&gt;[1 of 1] Compiling Main         ( Main.hs, Main.o )&lt;br /&gt;Linking Main ...&lt;br /&gt;&lt;br /&gt;user&amp;gt; LOCALE=en_US.UTF-8 ./Main&lt;br /&gt;Please enter your name:&lt;br /&gt;Bond&lt;br /&gt;Hello, Bond, how are you?&lt;br /&gt;&lt;br /&gt;user&amp;gt; LOCALE=de_DE.UTF-8 ./Main&lt;br /&gt;Wie heißen Sie?&lt;br /&gt;Bond&lt;br /&gt;Hallo, Bond, wie geht es Ihnen?&lt;br /&gt;&lt;br /&gt;user&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That's all :), really it was much simpler than writing this blog entry. I hope this article will be helpful for you.&lt;/p&gt;&lt;p&gt;PS: &lt;a href="http://progandprog.blogspot.com/2009/03/hgettext-on-hackage.html"&gt;hgettext is on Hackage now&lt;/a&gt;&lt;/p&gt;&lt;p&gt;PPS: Thanks to &lt;em&gt;Michael Thompson&lt;/em&gt;, who corrected my poor English :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-192070231817728434?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/192070231817728434/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/03/i18n-and-haskell.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/192070231817728434'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/192070231817728434'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/03/i18n-and-haskell.html' title='I18n and Haskell'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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-2187341306664604423.post-5251750553462190908</id><published>2009-03-27T06:38:00.000-07:00</published><updated>2009-03-27T07:37:35.534-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Less code - more functionality</title><content type='html'>Yesterday I tried to implement one "simple" function. It should take a Haskell source and return a list of all parameters to the function &lt;pre&gt;abc :: String-&gt;String&lt;/pre&gt; E.g. for the part of code:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;xy = (abc "hello") ++ (abc "world")&lt;br /&gt;&lt;br /&gt;main = do putStrLn (abc "hi")&lt;br /&gt;          putStrLn xy&lt;br /&gt;          putStrLn (abc "bye") &lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;output should be: &lt;pre&gt;["hello", "world", "hi", "bye"]&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Haskell has library &lt;a href="http://hackage.haskell.org/packages/archive/haskell-src/1.0.1.3/doc/html/Language-Haskell-Parser.html"&gt;Language.Haskell.Parser&lt;/a&gt; to parse its own source files, but the output has very complex structure. For example, previous part of code, will be represented like:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(HsModule (SrcLoc {srcFilename = "&lt;unknown&gt;", srcLine = 1, srcColumn = 1}) &lt;br /&gt;(Module "Main") (Just [HsEVar (UnQual (HsIdent "main"))]) [] &lt;br /&gt;[HsPatBind (SrcLoc {srcFilename = "&lt;unknown&gt;", srcLine = 1, srcColumn = 1}) &lt;br /&gt;(HsPVar (HsIdent "xy")) (HsUnGuardedRhs (HsInfixApp (HsParen (HsApp (HsVar (UnQual &lt;br /&gt;(HsIdent "abc"))) (HsLit (HsString "hello")))) (HsQVarOp (UnQual (HsSymbol "++"))) &lt;br /&gt;(HsParen (HsApp (HsVar (UnQual (HsIdent "abc"))) (HsLit (HsString "world")))))) [],&lt;br /&gt;HsPatBind (SrcLoc {srcFilename = "&lt;unknown&gt;", srcLine = 3, srcColumn = 1}) &lt;br /&gt;(HsPVar (HsIdent "main")) (HsUnGuardedRhs (HsDo [HsQualifier (HsApp (HsVar &lt;br /&gt;(UnQual (HsIdent "putStrLn"))) (HsParen (HsApp (HsVar (UnQual (HsIdent "abc"))) &lt;br /&gt;(HsLit (HsString "hi"))))),HsQualifier (HsApp (HsVar (UnQual (HsIdent "putStrLn"))) &lt;br /&gt;(HsVar (UnQual (HsIdent "xy")))),HsQualifier (HsApp (HsVar (UnQual (HsIdent "putStrLn"))) &lt;br /&gt;(HsParen (HsApp (HsVar (UnQual (HsIdent "abc"))) (HsLit (HsString "bye")))))])) []])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ugghhhh, looks terrible. The straightforward way to solve my task is to write a bunch of functions that will parse all datatypes, until they extract something like &lt;pre&gt;(HsApp (HsVar (UnQual (HsIdent "abc"))) (HsList (HsString s)))&lt;/pre&gt; Maybe it simplier to regexp through haskell code? &lt;br /&gt;&lt;br /&gt;No, and let me introduce TemplateHaskell. I haven't used it yet, but heard, that it is very powerfull part of the Haskell. It works like C++ Templates or macros, i.e. during program compilation. On the &lt;a href="http://news.gmane.org/gmane.comp.lang.haskell.cafe"&gt;Haskell-Cafe&lt;/a&gt; Neil Mitchel pointed me to the use &lt;a href="http://community.haskell.org/~ndm/uniplate"&gt;uniplate&lt;/a&gt; generic library. Without deep explorations and understanding how it work, I wrote a &lt;b&gt;one-line&lt;/b&gt; function that solves my problem:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;getParamList hscode= [x | &lt;br /&gt;             HsApp (HsVar (UnQual (HsIdent "abc"))) (HsList (HsString x)) &lt;- &lt;br /&gt;               universeBi (parseModule hscode)]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A really brilliant result. Even beginner haskeller could easily understand what is happen here.&lt;br /&gt;&lt;br /&gt;PS: It is amazing how fast and helpful haskell community is. Thank you guys :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2187341306664604423-5251750553462190908?l=progandprog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://progandprog.blogspot.com/feeds/5251750553462190908/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://progandprog.blogspot.com/2009/03/less-code-more-functionality.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/5251750553462190908'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2187341306664604423/posts/default/5251750553462190908'/><link rel='alternate' type='text/html' href='http://progandprog.blogspot.com/2009/03/less-code-more-functionality.html' title='Less code - more functionality'/><author><name>Vasyl Pasternak</name><uri>http://www.blogger.com/profile/12470846133921048991</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>
