# Introduction

as I mentioned I want to try some real-life Haskell and of course this means using some kind of database. I looked around a bit and found the really great Persistent package from Micheal Snoyman. Not only is this wonderfuly documented – no you get an yesod integration and a online-book(chapter) explaining it for free – thank you Micheal!

This post will only detail the setup and installation I had to do on top of my PostgresSQL installation and it’s more or less my notes I wrote along the way (I hope you enjoy this experiment).

So here we go – with opened SublimeText and console we start…

# Set up the playground

• made a directory and initialized a git repository in it git init
• copied my generic haskell .gitignore into it
• initialized cabal cabal init setting up the basic names and stuff
• added persistent, persistent-sqlite and persistent-postgresql as dependencies
• shamelessly copied the code from here into main.hs:
{-# LANGUAGE EmptyDataDecls    #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE GADTs             #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}
import           Database.Persist
import           Database.Persist.Sqlite
import           Database.Persist.TH

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int Maybe
deriving Show
BlogPost
title String
authorId PersonId
deriving Show
|]

main :: IO ()
main = runSqlite ":memory:" $do runMigration migrateAll johnId <- insert$ Person "John Doe" $Just 35 janeId <- insert$ Person "Jane Doe" Nothing

insert $BlogPost "My fr1st p0st" johnId insert$ BlogPost "One more for good measure" johnId

oneJohnPost <- selectList [BlogPostAuthorId ==. johnId] [LimitTo 1]
liftIO $print (oneJohnPost :: [Entity BlogPost]) john <- get johnId liftIO$ print (john :: Maybe Person)

delete janeId
deleteWhere [BlogPostAuthorId ==. johnId]


• obviously wanting a sandbox: cabal sandbox init
• get the stuff from the net and write this here in the meantime ;) cabal install --dependencies-only

# the usual cabal oddysee starts.. (but do not fear – Penelope does not have to wait long)

So this was the first thing that failed – persistent-sqlite went like a charm but persistent-postgresql had a issue.

Ok – comment out the postgres part for now – the code does not need it.

But as cabal build showed I had a missing module Database.Persist.TH – and as I could not find the right package at once, let’s comment it out for now.

Yeah that got me another missing module Control.Monad.IO.Class – but this time ghc (and me) knows where to find it: transformers – so in it.

Now I get some more information – as you could guess the .TH stands for TemplateHaskell and sure enough ghc complains about things like share and mkPersist.

And sure enough there is a persistent-template package with those functions and the missing module – so another cabal install later, we finally have a compiling program.

Yes there are a few warnings (unused constructors and so forth) but let’s forget about this right now.

# let’s run

a cabal run yields

Migrating: CREATE TABLE "person"("id" INTEGER PRIMARY KEY,"name" VARCHAR NOT NULL,"age" INTEGER NULL)
Migrating: CREATE TABLE "blog_post"("id" INTEGER PRIMARY KEY,"title" VARCHAR NOT NULL,"author_id" INTEGER NOT NULL REFERENCES "person")
[Entity {entityKey = Key {unKey = PersistInt64 1}, entityVal = BlogPost {blogPostTitle = "My fr1st p0st", blogPostAuthorId = Key {unKey = PersistInt64 1}}}]
Just (Person {personName = "John Doe", personAge = Just 35})


Which looks like just what the copied code should do … great!

# back to postgres

But of course I want to run this in my local postgresql db – so there’s still one thing left before I could call it a day – so remove let’s get it back in there.

Retrying the install I saw this nice little helper-message:

setup: The program pg_config is required but it could not be found.


And indeed – I did not have this on my system – running openSUSE it’s no big deal to find out where the bugger lives: cnf pg_config – voila postgresql92-devel so give it to me sudo zypper install postgresql92-devel (note: of course you might have to do some sudo apt-get or something similar if you are on a debian based system – but I’m sure you get it – if you are on windows … well let’s say I keep this task for another day and propably ms-sql or mysql instead of postgres).

After this little intermezzo persistent-postgresql builds/installs and we can look at how to use it with our “copy&paste” example.

Turns out that this too is already mentioned in the book chapter I linked earlier – right at the bottom of the doc. The changes are very slight:

• import Database.Persist.Postgresql instead of .Sqlite
• get a connection string for the database:

connStr = "host=localhost dbname=test user=??? password=**** port=5432"


(fill in whatever you need – obviously you should use a known user/password).

• run withPostgresqlPool with runSqlPersistMPool instead of just runSqlite like this:
main = withPostgresqlPool connStr 10 $\pool -> do flip runSqlPersistMPool pool$ do



The rest may stay untouched.

And YEAH – cabal run yields

Migrating: CREATe TABLE "person"("id" SERIAL PRIMARY KEY UNIQUE,"name" VARCHAR NOT NULL,"age" INT8 NULL)
HINWEIS:  CREATE TABLE erstellt implizit eine Sequenz »person_id_seq« für die »serial«-Spalte »person.id«
HINWEIS:  CREATE TABLE / PRIMARY KEY erstellt implizit einen Index »person_pkey« für Tabelle »person«
Migrating: CREATe TABLE "blog_post"("id" SERIAL PRIMARY KEY UNIQUE,"title" VARCHAR NOT NULL,"author_id" INT8 NOT NULL)
HINWEIS:  CREATE TABLE erstellt implizit eine Sequenz »blog_post_id_seq« für die »serial«-Spalte »blog_post.id«
HINWEIS:  CREATE TABLE / PRIMARY KEY erstellt implizit einen Index »blog_post_pkey« für Tabelle »blog_post«
Migrating: ALTER TABLE "blog_post" ADD CONSTRAINT "blog_post_author_id_fkey" FOREIGN KEY("author_id") REFERENCES "person"("id")
[Entity {entityKey = Key {unKey = PersistInt64 1}, entityVal = BlogPost {blogPostTitle = "My fr1st p0st", blogPostAuthorId = Key {unKey = PersistInt64 1}}}]
Just (Person {personName = "John Doe", personAge = Just 35})


Checking my test database with pgAdminIII I see that indeed I now have a person and a blog_post table with the right columns set up. The first having an “John Deo” entry and the second beeing empty.

Rerunning again adds another “John Doe” with id “3″ as it should. As a last test I removed the “delete” so I can check for a blog post … and yes I now have yet another “John 5″ who has 2 posts … so yes it’s working as it should.

This should wrap up the first tests – rather painless which is great!

# Introduction

I want to try some real life style CRUD applications with Haskell and co. To do this I wanted to install PostgreSQL as I heard lot’s of good stuff about it.

### Disclaimer

I am in no way an expert or even competent with PostgreSQL but this got me a working instance together with an UI tool to administrate it.

# Packages to install

I installed the followig packages following this and some other tutorials on the net:

• postgresql-init
• libpq5
• postgres92
• libqt4-sql-postgresql
• postgresl92-server
• postgresql
• postgresql-server
• libwx-gtk2u_stc-2_8-0-wxcontainer
• libwx-gtk2u_xrc-2_8-0-wxcontainer
• libreoffice-base-drivers-postgresql

Obviously some of those got auto-selected (using YAST) and I added pgadmin3 and the plugin for libre-office as I saw it in the repose ;)

# Post-Install (setting up the user, password, authentication method and data-path)

Following the short guide I

• created a data-directory mkdir /usr/local/pgsql/data
• made (already configured user) postgres the owner of this chown postgres /usr/local/pgsql/data
• switched to this user su - postgres
• initialised the database initdb -D /usr/local/pgsql/data
• started postgres using this folder postgres -D /usr/local/pgsql/data
• created a test database createdb test

(do not forget using godmode for this before you go – su)

Next I had to change the default password (again as su) – following the steps here:

• start postgres (if not already) rcpostgresql start
• switch to the user postgres and open the master-db: su postgres -c psql postgres
• in the sql-command alter the defaultusers password: ALTER USER postgres WITH PASSWORD '****' (of course using you password instead of the stars)

Next on the checklist is changing the authentication mode (I guess this is only a good idea for your local test-instance – but this way it’s much easier to get everything going – indeed pgAdminIII will propose the same if it has problems connecting). So open the file /var/lib/pgsql/data/pg_hba.conf (you will need administrative rights to access it) and change the uncommented occurrences of ident to md5. Save the file and restart the database with rcpostgresql restart

Now everything should be fine to use pgAdminIII.

# Setting up pgAdmin III

Start pgAdminIII and add a new server using

• Host = localhost
• Port = 5432 (this is configured in /var/lib/pgsql/data/postgres.conf),
• User = postgres (of course you can create another user with createuser -D -p <username> – without -p if you do not need a password)
• Passwort = whatever you choose in the last step

That’s it – now you should be able to access everything using this tool or the sql-command (pgsql)

As always: have fun :D

# fold and accumulate

(You can also look at this post using the great my FPComplete tutorial for this)

# what is this about?

Recently I played a bit with threepenny-gui and soon ran into the situation where I wanted to update a behavior (my state) using the old state and certain events. Not having to much real functional approach to reactive programming my mind at once cried “give me some fold man” … but alas: there is nothing that has a close shape to a fold in this library.

So I skimmed through the libraray and could really not get how I could ever get to use “old-state” together with an event …

After a while I finally saw some “accumulate” functions in there, and as I knew their close relationship with folds (for example in .net/linq fold is named Accumulate) I knew I might have found a way out … but wait this is the type-signature of the one I finally used:

accumB :: MonadIO m => a -> Event (a -> a) -> m (Behavior a)



let’s rephrase that a bit and you get basically

accum :: acc -> [acc -> acc] -> acc



It’s quite a bit away from

foldl :: (acc -> b -> acc) -> acc -> [b] -> acc



And I really struggled to finally see how you could use accum to get foldl.

If you like me don’t see how you could possible to this read on – if you find this trivial better close the site now.

# the idea

After 20 minutes or so (searching around for other stuff, coming back, scratching my head, …) I finally saw how to do it:

Just map the bs into functions acc -> acc and vóila: done.

# let’s implement this

So let’s try – here is functional version of the basic accum function:

accum :: acc -> [acc -> acc] -> acc
accum acc []     = acc
accum acc (f:fs) = accum (f acc) fs

main = do
putStrLn $show . accum 0$ [(+1), (+2), (+3), (+4)]



And of course, using f :: acc -> b -> acc and given a b we map this into \acc -> f acc b or flip f b:

fold :: (acc -> b -> acc) -> acc -> [b] -> acc
fold f acc = accum acc . map (flip f)



Seems to be correct (at least the compiler is happy and the results math).

# a bit eq. reasoning

So let’s get a bit further by actually showing that this is correct. Using the definition of accum we see that (abusing the syntax highlighter and eq. reasoning here – so == is meta-equals instead of a lang. construct):

fold f acc [] == accum acc . map (flip f) $[] == accum acc [] == acc  So the base-case is the same as foldl – check. The non-empty list case is not much more: fold f acc (b:bs) == accum acc . map (flip f)$ (b:bs)
== accum acc $((\a -> f a b) : map (flip f) bs) == accum ((\a -> f a b) acc)$ map (flip f) bs



# coin-change kata

given a list of coins $$[c_1,..,c_n]$$ and a amount of money $$A$$ we shall find a list $$[a_1,..,a_n]$$ of integers such that $$\sum_i a_i c_i = A$$ – but not just any: We want to minimize the number of coins given: $$minimize \sum_i a_i$$

See here for more on this problem.

I stumpled on this reading twitter, where Mike Bild solved this using various LINQ-like expressions (well the first version of what I give you here) … as I could not stand the not-optimal solution I had to brute-force a better solution (… and maybe I come back and try to improve on that some time)

### remark (shameless plug)

You can find the same article on school of haskell – where you can even play with it. But make sure to give me some nice comments here :D

## a trivial algorithm (that does give you correct change but not with fewest coins)

First define the coins used (as a Integer-list):

coins :: [Integer]
coins = [100, 25, 10, 5, 1]



Then the idea is to fold over the coins using the remaining amount we have to change and the count of used coins so far (so a 3 at position 2 will mean 2 – 25ct coins used)

change :: Integer -> [Integer]
change n = reverse . snd $foldl next (n, []) coins where next (remaining, cs) coin | coin <= remaining = (r', cnt:cs) | otherwise = (remaining, 0:cs) where r' = remaining mod coin cnt = remaining div coin  ## Why this might fail in the general case Well sometimes … let’s change the coins and look for 30cts … change :: Integer -> [Integer] change n = reverse . snd$ foldl next (n, []) coins
where next (remaining, cs) coin
| coin <= remaining = (r', cnt:cs)
| otherwise         = (remaining, 0:cs)
where r' = remaining mod coin
cnt = remaining div coin



so 6 coins (1x25ct and 5*1ct) – WTF … well the algorithm is too stupid in this case (the answer is of course 2x15ct) … stay tuned – I gonna fix it … soon

## brute-forcing our way

An easy way to fix this is to try every possible combination for small cases like the samples it’s still ok

import Data.List(minimumBy)
import Data.Maybe(mapMaybe)
import Control.Applicative ( (<$>) ) findChange :: [Coin] -> Cents -> Maybe [Integer] findChange coins amount = snd <$> calc amount coins
where calc r []
| r == 0    = Just (0, [])
| otherwise = Nothing
calc r (c:cs)
| r < 0     = Nothing
| otherwise = findMinimum . mapMaybe try $[0 .. r div c] where try n = do (n', ns) <- calc (r-n*c) cs return (n + n', n:ns) findMinimum [] = Nothing findMinimum vs = Just . minimumBy (\ (a,_) (b, _) -> compare a b)$ vs



As you can see, the code has changed a bit – but produces the right answer (2x15ct)!

Instead of trying to find the right fold-function I went for manual recursion for now: You will find the algorithm inside of calc. The first few guards handle the edge-cases (nothing more to change, no-coins left to try, to much change given). But in the case that there is still something to give-back and where there are coins left to change with the algorithm tries every combination and finaly turns back the one with fewest coins given recursivley.

## brute-force brakes down

Remember: I told you “it’s still ok”? Well I lied – try the brute-force algorithm for your normal coins [100, 50, 25, 10, 5, 2, 1] and (say) 2$34ct … this will take the algorithm on my machine almost 10seconds! Try the same for 100$ and get yourself some coffee…

## What can we do…

If you look carefully you’ll see that we do the recursive-call not one time but two times – and just as with fibonacci numbers we will sureley hit the same spot more than once – but every time we calculate the thing again. Just write the algorithm down as a tree and you will see that we recalculate the same branches time-and-time again…

So how can we change this?

We have to memoize the caluclated values! This is more or less the basic idea in dynamic programming (DP) – and while I will not give a complete introduction here I will try to show how you can change the algorithm to use this.

## First step towards DP

The first thing we are going to do is modify the algorithm a bit so that we can see better where the recursion is (the case expression below):

-- | tries change (using coins form the first parameter) to the amount of money in the second parameter with the fewest number of pieces in the change
-- | the first parameter should be a decreasing list of distinct values
takeCoin :: [Coin] -> Cents -> Maybe (Integer, [Coin])
takeCoin [] cents
| cents == 0   = Just (0, [])
| otherwise    = Nothing
takeCoin coins@(coin:coins') cents
| cents < 0    = Nothing
| coin > cents = takeCoin coins' cents
| otherwise    = case (takeCoin coins (cents-coin), takeCoin coins' cents) of
(Just (n, t), Just (n',t')) -> Just $if n+1 <= n' then (n+1, coin:t) else (n', t') (Nothing, Just (n',t')) -> Just (n', t') (Just (n,t), Nothing) -> Just (n+1, coin:t)  This will still take long (indeed it will do slightly worse) but you can see better where the magic (aka recursion) is and where the edge-cases gets handled. ## enter lazy-arrays Now how to memoize … well the easiest thing I can think of (and indeed almost the same as with real DP) is to use an array. Well now you cannot mutate arrays in Haskell but as Haskell is lazy this will be no problem! How so? Well we just put lots of chunks into the array and let the algorithm lazily evalute those at needed. BTW: this is the point where this algorithm can break down: the chunks need quite some memory and if your DP-table is large this will exhaust your memory (look for the knapsackproblem with large data-sets …) but again (this time for sure): for this it will be sufficient (for me)! So here it is – the final (quite quick) DP-solution to the problem import Data.Array takeCoinDP :: [Coin] -> Cents -> Maybe (Integer, [Coin]) takeCoinDP coins cents = get ltCoin cents where arr = array ((0,0), (ltCoin, cents)) [((i,c), takeC i c) | i <- [0..ltCoin], c <- [0..cents]] get i c | i < 0 && c == 0 = Just (0, []) | i < 0 || c < 0 = Nothing | otherwise = arr!(i,c) ltCoin = length coins - 1 takeC cNr cts | coin > cts = get (cNr-1) cts | otherwise = case (get cNr (cts-coin), get (cNr-1) cts) of (Just (n, t), Just (n',t')) -> Just$ if n+1 <= n' then (n+1, coin:t) else (n', t')
(Nothing,     Just (n',t')) -> Just (n', t')
(Just (n,t),  Nothing)      -> Just (n+1, coin:t)
(Nothing,     Nothing)      -> Nothing
where coin = coins !! cNr

main :: IO ()
main = do
putStrLn "let's change 2$34ct" let coins = defaultCoins let amount = 234 putStrLn "here we go ... this should be alot quicker..." let res = takeCoinDP coins amount print res  I hope you see the similarity to the version above. The only trick here is to move the edge cases to the selection from the array (to avoid index-out-of-range exceptions and stuff). Enjoy! # Installing GLPK and GLPK-hs on Windows As I want to try some LP (looking into a coursera-course) I tried to setup GLPK on Windows. (BTW: doing this in Linux is trivial: apt-get or you friendly packet-manager). The goal was to get this simple skript: import Control.Monad.LPMonad import Data.LinearProgram import Data.LinearProgram.GLPK objFun :: LinFunc String Int objFun = linCombination [(10, "x1"), (6, "x2"), (4, "x3")] lp :: LP String Int lp = execLPM$ do
setDirection Max
setObjective objFun
leqTo (varSum ["x1", "x2", "x3"]) 100
leqTo (linCombination [(10, "x1"), (4, "x2"), (5, "x3")]) 600
leqTo (linCombination [(2, "x1"), (2, "x2"), (6, "x3")]) 300
varGeq "x1" 0
varBds "x2" 0 50
varGeq "x3" 0
setVarKind "x1" IntVar
setVarKind "x2" ContVar

main = print =<< glpSolveVars mipDefaults lp



running.

Well here are the steps that got me going:

• in the /w32 folder edit the Build_GLPK_with_VC10.bat file to update your installation folder for VS2010 (mine was under Programm files (x86))
• run the batch-file and make sure you are in the right environment (I put it into programm files so I had to run the developers console from VS2010 tools as administrator!)
• copy the build version of glpk dll and lib without it’s version (for example copy glpk_4_52.dll glpk.dll)
• use cabal install with extra-libs and extra-sources like this:
cabal install glpk-hs --extra-lib-dirs="c:\Program Files (x86)\GNU\glpk\w32" --extra-include-dirs="c:\Program Files (x86)\GNU\glpk\src" 
• now runhaskell example.hs should run and result in $$x_1 = 40$$, $$x_2 = 50$$ and $$x_3 = 0$$.

# BangPatterns in Haskell

I’ve seen syntax like:

let !s = f x


from time to time (for example in Marlows Book) but I never reall knew what this really is, nor where I should look for it.

Today I stumpled upon the Bang patterns extension to GHC – and voila – here we go.

## So what is it?

If we add the extension we can force evalutation wo WHNF (weak head normal form – more or less evaluated “one step”) by just adding a !(Bang) in front of an identifier – and this extents to pattern-matching as well (this and the fact that is’s shorter to write and better to read makes it so much better then seq).

Here are a few examples showing a bit of it’s power/capabilities:

let makeTuple() = (1+1, 2+2)
let f = makeTuple()
:sprint f
>> f = _

let !f = makeTuple()
:sprint f
>>f = (_,_)



# Haskell and Snap … yet another cabal adventure

I wanted to try out some of Haskells web-frameworks for quite some time now. The problem so far was always the huge pile of things those come bundeled with (template haskell, conduit, comonads, etc.) and I somehow wanted to understand all this first.

To make this short: NO I do not understand all this but I think I will never really be ready to try it if I wait for me to turn genius overnight (guess will never happen) – so I just hold my breath and jump right into it!

# but say … which one?

That leaves me with the choice of the framework. After looking a bit at the online tutorials I wanted to go for either snap or yesod. But on first glance (well …) snap seemed to be a bit easier to get startet. Having said that I think I will try yesod too someday soon.

# Installation Odyssee

## First try: Windows

I work mainly on Windows and so of course I tried to get this working in windows. I even can remember that I tried to install snap some time ago and it seemed to work … but … Well after

 cabal-dev install snap
I soon (well it took quite some time to compile all this) got the dreaded “failed to install because of …” message …

About an hour (with yet another complete reinstall of Haskell-Plattform and various test with different versions) later I finally contacted the snap guys on their IRC channel and after describing the problems the final verdict was like this:

we have no Windows developer on our team … this is about the help you can expect

To be honest: the guy was quite helpful and it seems that the problem is in some crypto-package and not with snap but I’m quite disappointed anyway.

Anyhow: forget Windows for now …

## Ubuntu to the rescue….

Luckily I have quite a nice Ubuntu-VM installed on my machine (where I can just do a simple snapshot-restore to reset my cabal-hell) and so I went there next. Here the installation was quite simple (that is

cabal-dev install snap
) worked just fine.

### TIP OF THE DAY

I did not really want to do this installation for each project I am going to try but on Linux (have to try this on Windows as well) you can add a symbolic link to the created cabal-dev folder in each new project you are going to make (do not forget to place a symbolic link to the snap executable in cabal-dev/bin somewhere your PATH is pointing – or add this folder to your path. This works quite nice for me and I do not soil my global or user cabal repository.

# first toy project

For my first project I want to implement a basic calculator on a webpage. Finally it should work with AJAX/jQuery or even do all it’s calculation in the browser using Fay.

## First Iteration – getting something up

I worked through the two basic tutorials on the snap homepage: hello world and snaplets tutorial but two be honest – the second one just produced “WTF”s on my side – so I decided to keep it dumb for now and just get snap to serve me static HTML files together with a simple CSS stylesheet and of course a jQuery-plugin I am going to use (yeah … it’s JavaScript … for now).

You can find the project on github but so far it’s only a very basic extension of the first example above.

### some details on the code

To get snap to serve me the stylesheets and possible scripts I had to extend the routes in the site-function:

site :: Snap ()
site =
ifTop (serveDirectory "./content/") <|>
route [ ("api/sum/:operants", sumHandler)
] <|>
dir "scripts" (serveDirectory "./scripts/") <|>
dir "styles" (serveDirectory "./styles/")



This basically says that everything requested in with only the root-path is served (with some so called default-configuration) via serveDirectory straight from the content-folder (the default configuration sees that index.html is served … btw: Index.html will get you a 404)

The next part will serve for AJAX-requests – it binds a handler sumHandler to a URL like this http://mySite/api/sum/2%202 (see below).

The rest are static routes: /scripts/xyz tries to find the file xyz in the ./scripts folder and similar for styles (this is where I put the javascript files and the stylesheets).

The handler for the very simple API is implemented like this:

sumHandler :: Snap ()
sumHandler = do
param <- getParam "operants"
maybe (writeJSON errorMsg)
(writeJSON . sumString . unpack) param
where errorMsg :: ByteString
errorMsg = pack "please give some operants"

sumString :: String -> Integer
sumString = sumUp . words
where sumUp :: [String] -> Integer
sumUp = sum . map read



It gets the string xyz in http://mySite/api/sum/xyz, splits it by spaces (words) and sums the integers (no there is no exception handling yet) up. It finally serves the answer as JSON (for this I use the snap-extras package).

I’m sure there is an easier way to do all this but this is what I was able to find in a reasonable time using my google-ninja-skills.

## next Iteration: getting a basic web-page and AJAX queries to work …

well … but next time

# to my dear reader:

I am sure some/most of you know better then me, so pls let me know what and how I can improve. For example: is this the right way to get to some kind of RESTful API (using handlers like this) or is there some special snap-way I just have not found yet?

# my OpenPGP/GnuPG public Key

You can use this to send me secure mails and fuck the NSA :D

ASC file with my key

 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2.0.20 (MingW32)

 mQENBFH7YaABCAClwtwRGmTMAnD+cvtRnfmj9ZhqaX6ahh61r0kKxj/ow9nVJJTE jkI2tlVNhh01bV+HvDSvX3Cuy2cuPDJ2QpeoRCPhtMTsECae0cHrdDFtu9zPQ9v4 yswQA/JPH4zl/H5hQo/KXjbMzybmrvKq8oW5PBKYTF5jRRdoAmqawkENo1Douj0P HG2pcQj6lZwMPUsHwykwdsYbVXEfUYQr3pnU0ZFjB3cS74hD4NmEpXMYUTzGexR+ cuW1rBLwl8I2RPiIcH0ztUPqoCDMeaRS0PVyPJ7D1z2yp5kK1vmH7zrlSe5nFZS/ RGChbSISZiVD1CJZXf1U3+bZHzY0iN77cyefABEBAAG0K0NhcnN0ZW4gS8O2bmln IDxDYXJzdGVuQktvZW5pZ0B0LW9ubGluZS5kZT6JATkEEwECACMFAlH7Y24CGw8H CwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAAKCRDmjt5r1nNDxRWTB/0e4O9e2QE8 2JPa0debyttCyrH5Bzy4IUm5DM1X2/fOWmdfQEAenGjcwJcbu/fp4AoPkI3B9RFk bzoG64eCuQk7p9OMjPW8R+EE0e1/WcrxJZsngJ/IZ0v43+mDRP/ZwT0v+dnJyLuA ClhvxL0meyeWLI9J2PFC8MrIOAbcZ+63jM90N1/mS19wM6W6BFe8PTBXTqz5J5Md hUo/4L2qQUAxcyLaidK0Zlg8LzDDiu/qz2jjhNRkpBXLITe56Bq7tmmpka+EPQ/C 1ynXLGvfEe3ojd5qC59o+tXiyVgHgzExe7IREXh5tV58Z4i+YhfaovzZMUbPnwwS 4sZi0ckUA7T8tCpDYXJzdGVuIEvDtm5pZyA8Q2Fyc3RlbkBnZXR0aW5nc2hhcnBl ci5kZT6JATkEEwECACMFAlH7YaACGw8HCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX gAAKCRDmjt5r1nNDxf74B/4yxLabM1bnLCp/Zx/NyslLGXzr5gZBTOZ2vYUNjEvz b6vb36P1EoA8zBygEdX9CX63e1G50bR4qwljlRfHwlEIHmXRiqeUb2feNVjLriCP ThqVD1Tw47gTFTcmE/Pe/+V/1G+znMtLzaoHAnpc4GFM4dzs5KFLG7o6GAyt4b8e rPKhlbzTUt2h8Sl5PtWtHjVmSM2EFOfWOoQW4OfpADD2Pfgik8QaWS/bn1GaBkMC 2vcbIE+USIXc88Wto1w5P74DGREM7EZnC462noqqihA62UbWpXA5n1N2tI1D3ULI rscDqaC9j3gqljv9DDEMo/VF4shagJE2j5NwN41IbtMJtC9DYXJzdGVuIEvDtm5p ZyA8Q2Fyc3Rlbi5Lb2VuaWdAd2llZ2FuZC1nbGFzLmRlPokBOQQTAQIAIwUCUftj hgIbDwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheAAAoJEOaO3mvWc0PFfdEH/2Ef wfrqH/pDrpiWqwW+n7NQ1dUVa6Lfb5r0Cce+gBOWzshH67OgGxmp5yPFa+vlG+Gi EnOL0JUVlM1gywbDw34+CXlopFA32lDyPXGCDg4zA5ZHqv6nO92NJrRCPikRp5O0 cUPFmHJs6S+jpGwCaGQ72dIpi8RaKKW9NKTQ2sx68ObLlpfrLosjh/R6rpnzPC8o mHeqaMlMMAcFcOCb2KK9gOUDvuB9Nl+gLSGQtAQsCwWFTHCscj6qmCB/pZGMrQu0 k2FYxscZntNhhjmrf68iylB021RxTb49EMiYXozL8s0C+Etr9/Mw2K+HP+seLcsQ CT2X2qPxj5oxi9FaMuA= =mg2D -----END PGP PUBLIC KEY BLOCK-----