17 Commits

Author SHA1 Message Date
afcfc7bb07 wip 2019-03-27 23:00:36 +02:00
4c8df0c5df wip 2019-03-27 22:26:48 +02:00
58209a2c6e Better css 2019-03-27 22:23:43 +02:00
9d18db19a2 Refactor 2019-03-27 21:39:43 +02:00
7557f5041c Remove post 2019-03-24 22:50:19 +02:00
e2a213b08d Remove guides at least for now 2019-03-24 22:43:55 +02:00
91cea649c9 demobot 2018-12-24 23:43:38 +02:00
5661770508 Bump versions 2018-12-24 22:36:23 +02:00
4120b2fff5 Build incomplete posts as well 2018-12-24 13:32:52 +02:00
213442ac0f Try out showing the IPFS hash 2018-09-29 22:08:05 +03:00
f355516c1d Simple ipfs pinning deployment 2018-09-20 23:15:22 +03:00
c348b2c834 Disable shared executables 2018-09-20 23:10:18 +03:00
9867ecdbb1 Add license 2018-09-20 23:03:51 +03:00
73a237aec8 Wording 2018-09-20 22:57:25 +03:00
2db7c774a2 Change layout 2018-09-20 22:54:59 +03:00
4e2d6342b4 guide: Basic nix 2018-09-20 22:33:02 +03:00
06778c219a Styling changes 2018-09-20 22:24:04 +03:00
23 changed files with 388 additions and 78 deletions

5
.gitignore vendored
View File

@ -1,2 +1,3 @@
_site _site/
_cache _cache/
dist/

30
LICENSE Normal file
View File

@ -0,0 +1,30 @@
Copyright (c) 2018, Mats Rauhala
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Mats Rauhala nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,29 +0,0 @@
---
title: About
---
![](./images/profile.jpg)
I'm a software developer from southern Finland. I'm currently working as a
Haskell developer at Relex Oy involved in an internal tool. I also have
experience with systems integrations and backend web development.
I'm also an aviation enthusiast. I have a glider pilots license and a touring
motor glider pilots license (LAPL(S)+TMG).
My [GPG key](./resources/2104943D6033C.txt)
### Notable experience and interests
- **Haskell**
- Working on an internal Haskell based tool at Relex Oy
- Some contributions to [darcshub](https://hub.darcs.net/)
- Some contributions to [xmonad](https://github.com/xmonad)
- Dozens of personal projects, most of which ended up being just tests for
different libraries, techniques or algorithms.
- **Systems integrations**
- Working as a consultant from Avoltus Oy to different companies using
[Mulesoft](https://developer.mulesoft.com/). Systems include webshops,
different databases, crms and erps.
- **Java**
- Bunch of different smaller projects while working at Avoltus Oy

View File

@ -111,7 +111,7 @@ article .header {
@media (min-width: 640px) { @media (min-width: 640px) {
body { body {
width: 60rem; width: 85rem;
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
} }

30
css/highlight.css Normal file
View File

@ -0,0 +1,30 @@
/* Generated by pandoc. */
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre.sourceCode
{
margin: 0;
padding: 0;
/* border: 0; */
vertical-align: baseline;
}
pre.sourceCode
{
border: 1px solid #ccc;
background-color: rgb(238,238,255);
padding: 10px;
overflow: auto;
}
td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
td.sourceCode { padding-left: 5px; }
.sourceCode span.kw { color: #007020; font-weight: bold; }
.sourceCode span.dt { color: #902000; }
.sourceCode span.dv { color: #40a070; }
.sourceCode span.bn { color: #40a070; }
.sourceCode span.fl { color: #40a070; }
.sourceCode span.ch { color: #4070a0; }
.sourceCode span.st { color: #4070a0; }
.sourceCode span.co { color: #60a0b0; font-style: italic; }
.sourceCode span.ot { color: #007020; }
.sourceCode span.al { color: red; font-weight: bold; }
.sourceCode span.fu { color: #06287e; }
.sourceCode span.re { }
.sourceCode span.er { color: red; font-weight: bold; }

View File

@ -1,3 +1,3 @@
{ haskellPackages }: { haskellPackages, haskell }:
haskellPackages.callCabal2nix "site" ./. {} haskell.lib.disableSharedExecutables (haskellPackages.callCabal2nix "site" ./. {})

BIN
images/Git-Icon-Black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
images/git_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

BIN
images/git_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -1,7 +0,0 @@
---
title: Home
---
<h2>Welcome</h2>
<p>Not much here yet. See the top bar</p>

31
index.markdown Normal file
View File

@ -0,0 +1,31 @@
---
title: Mats Rauhala
---
![](./images/profile.jpg)
I'm a software developer from southern Finland. I'm currently working as a
Haskell developer at Relex Oy involved in an internal tool. I also have
experience with systems integrations and backend web development.
I'm also an aviation enthusiast. I have a glider pilots license and a touring
motor glider pilots license (LAPL(S)+TMG).
My [GPG key](./resources/2104943D6033C.txt)
### Notable experience and interests
- **Haskell**
- Working on an internal Haskell based tool at Relex Oy
- Some contributions to [darcshub](https://hub.darcs.net/)
- Some contributions to [xmonad](https://github.com/xmonad)
- Dozens of personal projects, most of which ended up being just tests for
different libraries, techniques or algorithms.
- **Systems integrations**
- Working as a consultant from Avoltus Oy to different companies using
[Mulesoft](https://developer.mulesoft.com/). Systems include webshops,
different databases, CRMs and ERPs.
- **Java**
- Bunch of different smaller projects while working at Avoltus Oy.
- **Other**
- I was involved in creating an email solicit platform.

22
js/api.js Normal file
View File

@ -0,0 +1,22 @@
var getApiIpfsCurrent = function(onSuccess, onError) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/ipfs/current', true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function () {
var res = null;
if (xhr.readyState === 4) {
if (xhr.status === 204 || xhr.status === 205) {
onSuccess();
} else if (xhr.status >= 200 && xhr.status < 300) {
try { res = JSON.parse(xhr.responseText); } catch (e) { onError(e); }
if (res) onSuccess(res);
} else {
try { res = JSON.parse(xhr.responseText); } catch (e) { onError(e); }
if (res) onError(res);
}
}
};
xhr.send(null);
};

10
js/app.js Normal file
View File

@ -0,0 +1,10 @@
$(document).ready(function() {
var success = function(x) {
$("#ipfs > em").html(x);
};
var error = function(x) {
$("#ipfs").hide();
console.log("ipfs hash not found: " + x);
};
getApiIpfsCurrent(success, error);
});

2
js/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

78
posts/configs.md Normal file
View File

@ -0,0 +1,78 @@
---
title: Extensible configuration Pt. 1
date: 2019-03-27
---
This is the first part of a series where I'm going through how to make
extensible configuration. There is nothing groundbreaking or new in this
series, it's just me going through different implementations and trying to
understand them.
The source material for this post is [the fixed point implementation](https://github.com/NixOS/nixpkgs/blob/master/lib/fixed-points.nix) for nix.
By extensible configuration, I'm talking about nix style extensible
configuration, like overlays, overrides and extensions. Let's see an example of
an extensible configuration.
``` nix
{ haskellPackages, fetchFromGitHub }:
let
purescript = fetchFromGitHub {
owner = "purescript";
repo = "purescript";
rev = "2cb4a6496052db726e099539be682b87585af494";
sha256 = "1v4gs08xnqgym6jj3drkzbic7ln3hfmflpbpij3qzwxsmqd2abr7";
}
hp = haskellPackages.extend (self: super: {
purescript = super.callCabal2nix "purescript" purescript {};
});
in
hp.purescript;
```
On a high level we are augmenting the `haskellPackages` attrset by replacing
the existing purescript package with a different one. The extension is a
function that takes two arguments, `self` and `super`. `super` is the original
non-lazy value and `self` is the lazy value that corresponds to the value at
end.
The first step on this journey is done by getting to know `fix`. Fix is
described being the least fixed point of a function. In practice it's a
function allowing declaring recursive functions without explicit recursion.
``` nix
fix = f: let x = f x; in x
```
With fix you can have access to the lazy `self` value. It's value is whatever
would have been computed in the end. As it is lazy, it is possible to end up in
a recursive loop if there is a cyclic dependency.
``` nix
let recursive = fix (self: {
foo = 3;
bar = self.foo + 1;
});
infinite = fix (self: {
foo = self.bar + 1;
bar = self.foo + 1;
});
```
You can try those yourself. The first version is fine and returns an attrset
like you would expect. The second one has a cyclic dependency and nix helpfully
errors out.
The next step is making a function that has access to the unmodified original
values. This is managed through the `extends` function. It took a while for me to understand what's happening in there, but luckily nix has [good documentation](https://github.com/NixOS/nixpkgs/blob/67b1265fb3d38ead5a57fee838405a2d997777c2/lib/fixed-points.nix#L37-L65) for it.
``` nix
extends = f: rattrs: self: let super = rattrs self; in super // f self super
```
- https://elvishjerricco.github.io/2017/04/01/nix-style-configs-in-haskell.html
- https://github.com/NixOS/nixpkgs/blob/master/lib/fixed-points.nix
- https://chshersh.github.io/posts/2019-03-25-comonadic-builders

148
posts/demobot.md Normal file
View File

@ -0,0 +1,148 @@
---
title: Functional architecture Pt. 1
date: 2018-12-25
---
I'm lucky enough to work with Haskell professionally which gives me some view
to good and maintainable real world architecture. In my opinion, one of the
biggest contributing factors to how your general architecture is defined, is
determined by the base application monad stack you are using.
Our actual product is mostly in the regular `LoggingT (ReaderT app IO)` base
monad with whatever style you would imagine with that base monad in place. It's
not entirely consistent, but close enough.
With all the talk about just having `IO`, `ReaderT app IO`, free monads or
tagless final monads, I thought of trying different styles. For this post I'm
focusing on the tagless final since it's most interesting for me right now.
`IO`
: The most basic style. This is pretty much only suitable for the most basic
of needs.
`ReaderT app IO`
: How we mostly define the base monad. This is a really good way of doing
things, it gives you a lot of leeway on how you can define the rest of your
application.
`Free monads`
: Free monads are a way of having a small constrained DSL or monad stack for
defining your application. By constraining the user, you are also reducing the
area for bugs. There is also some possibility for introspection, but usually
this isn't a usable feature. Also since free monad applications need the full
AST, they're quite a bit slower than the other solutions.
`Tagless final`
: This is something I'm the least familiar with. If I have understood
correctly, free monads and tagless final are more or less equivalent solutions
in their power, but in tagless final you aren't creating the AST anywhere,
which also means that you aren't paying for it either.
That out of the way, I had a small project idea for a bot that's easy to
contribute to, difficult to make errors and easy to reason about. The project
is at most a proof-of-concept and most definitely not production quality.
Still, I hope it's complex enough to showcase the architecture.
The full source code is available [at my git repository](https://git.rauhala.info/MasseR/demobot).
For the architecture to make sense, let me introduce two different actors: a
*core contributor* that's familiar with Haskell and a *external contributor*
that's familiar with programming, not necessarily with Haskell.
The repository is split into two parts, the library and the application.
The library
: Provides the restricted monad classes (tagless final), extension points and
the core bot main loop.
The application
: Provides the implementation for the tagless final type classes, meaning
that the application defines how the networking stack is handled, how database
connectivity is done and so on. It also collects all the extensions for that
specific application.
The *core contributor* is responsible for maintaining the library as well as
the type class instances for the application type. The *external contributor*
is responsible for maintaining one or multiple extensions that are restricted
in their capability and complexity.
I'm restricting the capabilities of the monad in the library and extensions,
meaning that I'm not allowing any IO. For example the networking is handled by
a single `MonadNetwork` type class. This is the most complex type class in the
library right now, using type families for defining a specific extension point
for the messages. This could be something like 'event type' for Flowdock
messages or 'source channel' for IRC messages.
~~~haskell
data Request meta = Request { content :: Text
, meta :: meta }
data Response meta = Response { content :: Text
, meta :: meta }
class Monad m => MonadNetwork m where
type Meta m :: *
recvMsg :: m (Request (Meta m))
putMsg :: Response (Meta m) -> m ()
~~~
Then we have the extension point which is more or less just a `Request -> m (Maybe Response)`. I'm using rank n types here for qualifying the `Meta`
extension point and forcing the allowed type classes to be a subset of the
application monad stack, I don't want extension writers to be able to write
messages to the bot network by themselves.
~~~haskell
data Extension meta =
Extension { act :: forall m. (meta ~ Meta m, MonadExtension m) => Request meta -> m (Maybe (Response meta))
, name :: String }
~~~
Last part of the library is the main loop, which is basically a free monad
(tagless final) waiting for an interpreter. At least in this POC I find this
style to be really good, it's really simplified, easy to read and hides a lot
of the complexity, while bringing forth the core algorithm.
~~~haskell
mainLoop :: forall m. (MonadCatch m, MonadBot m) => [Extension (Meta m)] -> m ()
mainLoop extensions = forever $ catch go handleFail
where
handleFail :: SomeException -> m ()
handleFail e = logError $ tshow e
go :: m ()
go = do
msg <- recvMsg
responses <- catMaybes <$> mapM (`act` msg) extensions
mapM_ putMsg responses
~~~
Then comes the actual application where we write the effectful interpreters. In
this POC the interpreter is just a `LoggingT IO a` with the semantics of
stdin/stdout. This is the only file where we're actually interacting with the
outside world, everything else is just pure code.
~~~haskell
instance MonadNetwork AppM where
type Meta AppM = ()
recvMsg = Request <$> liftIO T.getLine <*> pure ()
putMsg Response{..} = liftIO . T.putStrLn $ content
~~~
Writing the extensions was the responsibility of *external contributors* and we
already saw how the actual extension point was defined above. Using these
extension points is really simple and here we see how the implementation is
just a simple `Request -> m (Maybe Response)`.
~~~haskell
extension :: Extension ()
extension = Extension{..}
where
name = "hello world"
act Request{..} | "hello" `T.isPrefixOf` content = return $ Just $ Response "Hello to you" ()
| otherwise = return Nothing
~~~

View File

@ -2,11 +2,15 @@ name: site
version: 0.1.0.0 version: 0.1.0.0
build-type: Simple build-type: Simple
cabal-version: >= 1.10 cabal-version: >= 1.10
license: BSD3
license-file: LICENSE
author: Mats Rauhala
maintainer: mats.rauhala@iki.fi
executable site executable site
main-is: site.hs main-is: site.hs
build-depends: base == 4.* build-depends: base == 4.*
, hakyll == 4.10.* , hakyll >= 4.10
, time , time
ghc-options: -threaded ghc-options: -threaded
default-language: Haskell2010 default-language: Haskell2010

46
site.hs
View File

@ -1,14 +1,12 @@
--------------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
import Data.Monoid (mappend) import Data.List (sortOn)
import Data.Time (defaultTimeLocale, formatTime)
import Hakyll import Hakyll
import Data.List (sortBy, sortOn)
import Data.Time (formatTime, defaultTimeLocale)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
main :: IO () main :: IO ()
main = hakyll $ do main = hakyllWith defaultConfiguration{ deployCommand = "ipfs add -Q -r _site" } $do
match "images/*" $ do match "images/*" $ do
route idRoute route idRoute
compile copyFileCompiler compile copyFileCompiler
@ -21,33 +19,30 @@ main = hakyll $ do
route idRoute route idRoute
compile compressCssCompiler compile compressCssCompiler
match (fromList ["about.markdown", "contact.markdown"]) $ do match "js/*" $ do
route idRoute
compile compressCssCompiler
match (fromList ["index.markdown", "contact.markdown"]) $ do
route $ setExtension "html" route $ setExtension "html"
compile $ pandocCompiler compile $ pandocCompiler
>>= loadAndApplyTemplate "templates/default.html" defaultContext >>= loadAndApplyTemplate "templates/default.html" defaultContext
>>= relativizeUrls >>= relativizeUrls
match "posts/guides/*" $ do match "posts/*" $ do
route $ setExtension "html" route $ setExtension "html"
compile $ pandocCompiler compile $ pandocCompiler
>>= loadAndApplyTemplate "templates/post.html" postCtx >>= loadAndApplyTemplate "templates/post.html" postCtx
>>= loadAndApplyTemplate "templates/default.html" postCtx >>= loadAndApplyTemplate "templates/default.html" postCtx
>>= relativizeUrls >>= relativizeUrls
match "posts/brainstorming/*" $ do create ["posts.html"] $ do
route $ setExtension "html"
compile $ pandocCompiler
>>= loadAndApplyTemplate "templates/post.html" postCtx
>>= loadAndApplyTemplate "templates/default.html" postCtx
>>= relativizeUrls
create ["guides.html"] $ do
route idRoute route idRoute
compile $ do compile $ do
posts <- modFirst =<< loadAll "posts/guides/*" posts <- modFirst =<< loadAll "posts/*"
let archiveCtx = let archiveCtx =
listField "posts" postCtx (return posts) `mappend` listField "posts" postCtx (return posts) <>
constField "title" "Guides" `mappend` constField "title" "Posts" <>
defaultContext defaultContext
makeItem "" makeItem ""
@ -55,17 +50,6 @@ main = hakyll $ do
>>= loadAndApplyTemplate "templates/default.html" archiveCtx >>= loadAndApplyTemplate "templates/default.html" archiveCtx
>>= relativizeUrls >>= relativizeUrls
match "index.html" $ do
route idRoute
compile $ do
let indexCtx =
constField "title" "Home" `mappend`
defaultContext
getResourceBody
>>= applyAsTemplate indexCtx
>>= loadAndApplyTemplate "templates/default.html" indexCtx
>>= relativizeUrls
match "templates/*" $ compile templateBodyCompiler match "templates/*" $ compile templateBodyCompiler
@ -78,8 +62,8 @@ modFirst = fmap reverse . modified
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
postCtx :: Context String postCtx :: Context String
postCtx = postCtx =
dateField "date" "%B %e, %Y" `mappend` dateField "date" "%B %e, %Y" <>
modifiedField "modified" "%B %e, %Y" `mappend` modifiedField "modified" "%B %e, %Y" <>
defaultContext defaultContext
where where
modifiedField key format = field key $ \i -> do modifiedField key format = field key $ \i -> do

View File

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>rauhala.info - $title$</title> <title>rauhala.info - $title$</title>
<link rel="stylesheet" href="/css/default.css" /> <link rel="stylesheet" href="/css/default.css" />
<link rel="stylesheet" href="/css/highlight.css">
</head> </head>
<body> <body>
<header> <header>
@ -13,10 +14,11 @@
<a href="/">rauhala.info</a> <a href="/">rauhala.info</a>
</div> </div>
<nav> <nav>
<a href="/">Home</a> <!-- Git logo from https://git-scm.com/downloads/logos -->
<a href="/guides.html">Guides</a> <!-- Logo by Jason Long -->
<a href="/about.html">About</a> <a href="https://git.rauhala.info"><img src="/images/git_16.png" alt="git" /></a>
<a href="/contact.html">Contact</a> <a href="/contact.html">Contact</a>
<a href="/posts.html">Posts</a>
</nav> </nav>
</header> </header>
@ -28,6 +30,10 @@
<footer> <footer>
Site proudly generated by Site proudly generated by
<a href="http://jaspervdj.be/hakyll">Hakyll</a> <a href="http://jaspervdj.be/hakyll">Hakyll</a>
<span id="ipfs">and found on IPFS as <em></em></ipfs>
</footer> </footer>
</body> </body>
<script type="application/javascript" src="/js/jquery-3.3.1.min.js"></script>
<script type="application/javascript" src="/js/api.js"></script>
<script type="application/javascript" src="/js/app.js"></script>
</html> </html>

View File

@ -1,3 +1,3 @@
A list of small and big guides. Me writing out interesting ideas and experiments.
$partial("templates/post-list.html")$ $partial("templates/post-list.html")$

View File

@ -1,7 +1,7 @@
<ul> <ul>
$for(posts)$ $for(posts)$
<li> <li>
<a href="$url$">$title$</a> - $date$ <a href="$url$">$title$</a> - $modified$
</li> </li>
$endfor$ $endfor$
</ul> </ul>