From 7c47d2b8f99be4a53f50e46015c15301ffa16ae1 Mon Sep 17 00:00:00 2001 From: Thomas Letan Date: Wed, 5 Feb 2020 23:17:34 +0100 Subject: Rename org posts --- .gitignore | 6 +- site/posts/DiscoveringCommonLisp.org | 258 ++++++++++++++ site/posts/ExtensibleTypeSafeErrorHandling.org | 393 +++++++++++++++++++++ site/posts/MonadTransformers.org | 72 ++++ site/posts/extensible-type-safe-error-handling.org | 393 --------------------- site/posts/lisp-journey-getting-started.org | 258 -------------- site/posts/monad-transformers.org | 72 ---- 7 files changed, 726 insertions(+), 726 deletions(-) create mode 100644 site/posts/DiscoveringCommonLisp.org create mode 100644 site/posts/ExtensibleTypeSafeErrorHandling.org create mode 100644 site/posts/MonadTransformers.org delete mode 100644 site/posts/extensible-type-safe-error-handling.org delete mode 100644 site/posts/lisp-journey-getting-started.org delete mode 100644 site/posts/monad-transformers.org diff --git a/.gitignore b/.gitignore index 56d73a5..6c3c797 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,9 @@ build/ *.html~ # begin generated files -site/posts/monad-transformers.html -site/posts/lisp-journey-getting-started.html -site/posts/extensible-type-safe-error-handling.html +site/posts/ExtensibleTypeSafeErrorHandling.html +site/posts/MonadTransformers.html +site/posts/DiscoveringCommonLisp.html site/posts/Ltac101.html site/posts/RewritingInCoq.html site/posts/StronglySpecifiedFunctionsProgram.html diff --git a/site/posts/DiscoveringCommonLisp.org b/site/posts/DiscoveringCommonLisp.org new file mode 100644 index 0000000..a198c3d --- /dev/null +++ b/site/posts/DiscoveringCommonLisp.org @@ -0,0 +1,258 @@ +#+BEGIN_EXPORT html +

Discovering Common Lisp with trivial-gamekit

+ +June 17, 2018 +#+END_EXPORT + + +I always wanted to learn some Lisp dialect. +In the meantime, [[https://github.com/lkn-org/lykan][lykan]] —my Slayers Online clone— begins to take shape. +So, of course, my brain got an idea: /why not writing a client for lykan in some +Lisp dialect?/ +I asked on [[https://mastodon.social/@lthms/100135240390747697][Mastodon]] if there were good game engine for Lisp, and someone told me +about [[https://github.com/borodust/trivial-gamekit][trivial-gamekit]]. + +I have no idea if I will manage to implement a decent client using +trivial-gamekit, but why not trying? +This article is the first of a series about my experiments, discoveries and +difficulties. + +The code of my client is hosted on my server, using the pijul vcs. +If you have pijul installed, you can clone the repository: + +#+BEGIN_SRC bash +pijul clone "https://pijul.lthms.xyz/lkn/lysk" +#+END_SRC + +In addition, the complete project detailed in this article is available [[https://gist.github.com/lthms/9833f4851843119c966917775b4c4180][as a +gist]]. + +#+OPTIONS: toc:nil +#+TOC: headlines 2 + +* Common Lisp, Quicklisp and trivial-gamekit + +The trivial-gamekit [[https://borodust.github.io/projects/trivial-gamekit/][website]] lists several requirements. +Two are related to Lisp: + +1. Quicklisp +2. SBCL or CCL + +Quicklisp is an experimental package manager for Lisp project (it was easy to +guess, because there is a link to [[https://quicklisp.org/beta][quicklisp website]] in the trivial-gamekit +documentation). +As for SBCL and CCL, it turns out they are two Lisp implementations. +I had already installed [[https://www.archlinux.org/packages/?name=clisp][clisp]], and it took me quite some times to understand my +mistake. +Fortunately, [[https://www.archlinux.org/packages/?name=sbcl][sbcl]] is also packaged in ArchLinux. + +With a compatible Lisp implementation, installing Quicklisp as a user is +straightforward. +Following the website instructions is enough. +At the end of the process, you will have a new directory ~${HOME}/quicklisp~, +whose purpose is similar to the [[https://github.com/golang/go/wiki/SettingGOPATH][go workspace]]. + +Quicklisp is not a native feature of sbcl, and has to be loaded to be available. +To do it automatically, you have to create a file ~${HOME}/.sbclrc~, with the +following content: + +#+BEGIN_SRC +(load "~/quicklisp/setup") +#+END_SRC + +There is one final step to be able to use trivial-gamekit. + +#+BEGIN_SRC bash +sbcl --eval '(ql-dist:install-dist "http://bodge.borodust.org/dist/org.borodust.bodge.txt")' \ + --quit +#+END_SRC + +As for now[fn::June 2018], Quicklisp [[https://github.com/quicklisp/quicklisp-client/issues/167][does not support HTTPS]]. + +* Introducing Lysk + +** Packaging + +The first thing I search for when I learn a new language is how projects are +organized. +From this perspective, trivial-gamekit pointed me directly to Quicklisp + +Creating a new Quicklisp project is very simple, and this is a very good thing. +As I said, the ~${HOME}/quicklisp~ directory acts like the go workspace. +As far as I can tell, new Quicklisp projects have to be located inside +~${HOME}/quicklisp/local-projects~. +I am not particularly happy with it, but it is not really important. + +The current code name of my Lisp game client is lysk. + +#+BEGIN_SRC bash +mkdir ~/quicklisp/local-projects/lysk +#+END_SRC + +Quicklisp packages (systems?) are defined through ~asd~ files. +I have firstly created ~lysk.asd~ as follows: + +#+BEGIN_SRC common-lisp +(asdf:defsystem lysk + :description "Lykan Game Client" + :author "lthms" + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (trivial-gamekit) + :components ((:file "package") + (:file "lysk"))) +#+END_SRC + +~:serial t~ means that the files detailed in the ~components~ field depends on +the previous ones. +That is, ~lysk.lisp~ depends on ~package.lisp~ in this case. +It is possible to manage files dependencies manually, with the following syntax: + +#+BEGIN_SRC common-lisp +(:file "seconds" :depends-on "first") +#+END_SRC + +I have declared only one dependency: trivial-gamekit. +That way, Quicklisp will load it for us. + +The first “true” Lisp file we define in our skeleton is ~package.lisp~. +Here is its content: + +#+BEGIN_SRC common-lisp +(defpackage :lysk + (:use :cl) + (:export run app)) +#+END_SRC + +Basically, this means we use two symbols, ~run~ and ~app~. + +** A Game Client + +The ~lysk.lisp~ file contains the program in itself. +My first goal was to obtain the following program: at startup, it shall creates +a new window in fullscreen, and exit when users release the left button of their +mouse. +It is worth mentioning that I had to report [[https://github.com/borodust/trivial-gamekit/issues/30][an issue to the trivial-gamekit +upstream]] in order to make my program work as expected. +While it may sounds scary —it definitely shows trivial-gamekit is a relatively +young project— the author has implemented a fix in less than an hour! +He also took the time to answer many questions I had when I joined the +~#lispgames~ Freenode channel. + +Before going any further, lets have a look at the complete file. + +#+BEGIN_SRC common-lisp +(cl:in-package :lysk) + +(gamekit:defgame app () () + (:fullscreen-p 't)) + +(defmethod gamekit:post-initialize ((app app)) + (gamekit:bind-button :mouse-left :released + (lambda () (gamekit:stop)))) + +(defun run () + (gamekit:start 'app)) +#+END_SRC + +The first line is some kind of header, to tell Lisp the owner of the file. + +The ~gamekit:defgame~ function allows for creating a new game application +(called ~app~ in our case). +I ask for a fullscreen window with ~:fullscreen-p~. +Then, we use the ~gamekit:post-initialize~ hook to bind a handler to the release +of the left button of our mouse. +This handler is a simple call to ~gamekit:stop~. +Finally, we define a new function ~run~ which only starts our application. + +Pretty straightforward, right? + +** Running our Program + +To “play” our game, we can start the sbcl REPL. + +#+BEGIN_SRC bash +sbcl --eval '(ql:quickload :lysk)' --eval '(lysk:run)' +#+END_SRC + +And it works! + +** A Standalone Executable + +It looks like empower a REPL-driven development. +That being said, once the development is finished, I don't think I will have a +lot of success if I ask my future players to start sbcl to enjoy my game. +Fortunately, trivial-gamekit provides a dedicated function to bundle the game as +a standalone executable. + +Following the advises of the borodust —the trivial-gamekit author— I created a +second package to that end. +First, we need to edit the ~lysk.asd~ file to detail a second package: + +#+BEGIN_SRC common-lisp +(asdf:defsystem lysk/bundle + :description "Bundle the Lykan Game Client" + :author "lthms" + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (trivial-gamekit/distribution lysk) + :components ((:file "bundle"))) +#+END_SRC + +This second package depends on lysk (our game client) and and +trivial-gamekit/distribution. +The latter provides the ~deliver~ function, and we use it in the ~bundle.lisp~ +file: + +#+BEGIN_SRC common-lisp +(cl:defpackage :lysk.bundle + (:use :cl) + (:export deliver)) + +(cl:in-package :lysk.bundle) + +(defun deliver () + (gamekit.distribution:deliver :lysk 'lysk:app)) +#+END_SRC + +To bundle the game, we can use ~sbcl~ from our command line interface. + +#+BEGIN_SRC bash +sbcl --eval "(ql:quickload :lysk/bundle)" \ + --eval "(lysk.bundle:deliver)" \ + --quit +#+END_SRC + +* Conclusion + +Objectively, there is not much in this article. +However, because I am totally new to Lisp, it took me quite some time to get +these few lines of code to work together. +All being told I think this constitutes a good trivial-gamekit skeleton. +Do not hesitate to us it this way. + +Thanks again to borodust, for your time and all your answers! + +* Appendix: a Makefile + +I like Makefile, so here is one to ~run~ the game directly, or ~bundle~ it. + +#+BEGIN_SRC makefile +run: + @sbcl --eval "(ql:quickload :lysk)" \ + --eval "(lysk:run)" + +bundle: + @echo -en "[ ] Remove old build" + @rm -rf build/ + @echo -e "\r[*] Remove old build" + @echo "[ ] Building" + @sbcl --eval "(ql:quickload :lysk/bundle)" \ + --eval "(lysk.bundle:deliver)" \ + --quit + @echo "[*] Building" + +.PHONY: bundle run +#+END_SRC diff --git a/site/posts/ExtensibleTypeSafeErrorHandling.org b/site/posts/ExtensibleTypeSafeErrorHandling.org new file mode 100644 index 0000000..9164bc2 --- /dev/null +++ b/site/posts/ExtensibleTypeSafeErrorHandling.org @@ -0,0 +1,393 @@ +#+BEGIN_EXPORT html +

Extensible Type-Safe Error Handling in Haskell

+ +February 04, 2018 +#+END_EXPORT + +#+OPTIONS: toc:nil +#+TOC: headlines 2 + +A colleague of mine introduced me to the benefits of [[https://crates.io/crates/error-chain][~error-chain~]], a crate which +aims to implement /“consistent error handling”/ for Rust. I found the overall +design pretty convincing, and in his use case, the crate really makes its error +handling clearer and flexible. I knew /pijul/ uses ~error-chain~ to, but I never +had the occasion to dig more into it. + +At the same time, I have read quite a lot about /extensible effects/ in +Functional Programming, for an academic article I have submitted to +[[http://www.fm2018.org][Formal Methods 2018]][fn:fm2018]. In particular, the [[https://hackage.haskell.org/package/freer][freer]] package provides a very +nice API to define monadic functions which may use well-identified effects. For +instance, we can imagine that ~Console~ identifies the functions which may print +to and read from the standard output. A function ~askPassword~ which displays a +prompt and get the user password would have this type signature: + +#+BEGIN_SRC haskell +askPassword :: Member Console r => Eff r () +#+END_SRC + +Compared to ~IO~, ~Eff~ allows for meaningful type signatures. It becomes easier +to reason about function composition, and you know that a given function which +lacks a given effect in its type signature will not be able to use them. As a +predictable drawback, ~Eff~ can become burdensome to use. + +Basically, when my colleague showed me its Rust project and how he was using +~error-chain~, the question popped out. *Can we use an approach similar to ~Eff~ +to implement a Haskell-flavoured ~error-chain~?* + +Spoiler alert: the answer is yes. In this post, I will dive into the resulting +API, leaving for another time the details of the underlying +implementation. Believe me, there is plenty to say. If you want to have a look +already, the current implementation can be found on [[https://github.com/lethom/chain][GitHub]]. + +In this article, I will use several “advanced” GHC pragmas. I will not explain +each of them, but I will /try/ to give some pointers for the reader who wants to +learn more. + +[fn:fm2018] If the odds are in my favour, I will have plenty of occasions to write +more about this topic. + +* State of the Art + +This is not an academic publication, and my goal was primarily to explore the +arcane of the Haskell type system, so I might have skipped the proper study of +the state of the art. That being said, I have written programs in Rust and +Haskell before. + +** Starting Point + +In Rust, ~Result~ is the counterpart of ~Either E T~ in +Haskell[fn:either]. You can use it to model to wrap either the result of a +function (~T~) or an error encountered during this computation (~E~). +Both ~Either~ and ~Result~ are used in order to achieve the same end, that is +writing functions which might fail. + +On the one hand, ~Either E~ is a monad. It works exactly as ~Maybe~ (returning +an error acts as a shortcut for the rest of the function), but gives you the +ability to specify /why/ the function has failed. To deal with effects, the +~mtl~ package provides ~EitherT~, a transformer version of ~Either~ to be used +in a monad stack. + +On the other hand, the Rust language provides the ~?~ syntactic sugar, to +achieve the same thing. That is, both languages provide you the means to write +potentially failing functions without the need to care locally about failure. If +your function ~B~ uses a function ~A~ which might fail, and want to fail +yourself if ~A~ fails, it becomes trivial. + +Out of the box, neither ~EitherT~ nor ~Result~ is extensible. The functions must +use the exact same ~E~, or errors must be converted manually. + +[fn:either] I wonder if they deliberately choose to swap the two type arguments. + +** Handling Errors in Rust + +Rust and the ~error-chain~ crate provide several means to overcome this +limitation. In particular, it has the ~Into~ and ~From~ traits to ease the +conversion from one error to another. Among other things, the ~error-chain~ +crate provides a macro to easily define a wrapper around many errors types, +basically your own and the one defined by the crates you are using. + +I see several drawbacks to this approach. First, it is extensible if you take +the time to modify the wrapper type each time you want to consider a new error +type. Second, either you can either use one error type or every error +type. + +However, the ~error-chain~ package provides a way to solve a very annoying +limitation of ~Result~ and ~Either~. When you “catch” an error, after a given +function returns its result, it can be hard to determine from where the error is +coming from. Imagine you are parsing a very complicated source file, and the +error you get is ~SyntaxError~ with no additional context. How would you feel? + +~error-chain~ solves this by providing an API to construct a chain of errors, +rather than a single value. + +#+BEGIN_SRC rust +my_function().chain_err(|| "a message with some context")?; +#+END_SRC + +The ~chain_err~ function makes it easier to replace a given error in its +context, leading to be able to write more meaningful error messages for +instance. + +* The ResultT Monad + +The ~ResultT~ is an attempt to bring together the extensible power of ~Eff~ and +the chaining of errors of ~chain_err~. I will admit that, for the latter, the +current implementation of ~ResultT~ is probably less powerful, but to be honest +I mostly cared about the “extensible” thing, so it is not very surprising. + +This monad is not an alternative to neither Monad Stacks a la mtl nor to the +~Eff~ monad. In its current state, it aims to be a more powerful and flexible +version of ~EitherT~. + +** Parameters + +As often in Haskell, the ~ResultT~ monad can be parameterised in several ways. + +#+BEGIN_SRC haskell +data ResultT msg (err :: [*]) m a +#+END_SRC + +- ~msg~ is the type of messages you can stack to provide more context to error + handling +- ~err~ is a /row of errors/[fn:row], it basically describes the set of errors + you will eventually have to handle +- ~m~ is the underlying monad stack of your application, knowing that ~ResultT~ + is not intended to be stacked itself +- ~a~ is the expected type of the computation result + +[fn:row] You might have notice ~err~ is of kind ~[*]~. To write such a thing, +you will need the [[https://www.schoolofhaskell.com/user/konn/prove-your-haskell-for-great-safety/dependent-types-in-haskell][DataKinds]] GHC pragmas. + +** ~achieve~ and ~abort~ + +The two main monadic operations which comes with ~ResultT~ are ~achieve~ and +~abort~. The former allows for building the context, by stacking so-called +messages which describe what you want to do. The latter allows for bailing on a +computation and explaining why. + +#+BEGIN_SRC haskell +achieve :: (Monad m) + => msg + -> ResultT msg err m a + -> ResultT msg err m a +#+END_SRC + +~achieve~ should be used for ~do~ blocks. You can use ~~ to attach a +contextual message to a given computation. + +The type signature of ~abort~ is also interesting, because it introduces the +~Contains~ typeclass (e.g., it is equivalent to ~Member~ for ~Eff~). + +#+BEGIN_SRC haskell +abort :: (Contains err e, Monad m) + => e + -> ResultT msg err m a +#+END_SRC + +This reads as follows: /“you can abort with an error of type ~e~ if and only if +the row of errors ~err~ contains the type ~e~.”/ + +For instance, imagine we have an error type ~FileError~ to describe +filesystem-related errors. Then, we can imagine the following function: + +#+BEGIN_SRC haskell +readContent :: (Contains err FileError, MonadIO m) + => FilePath + -> ResultT msg err m String +#+END_SRC + +We could leverage this function in a given project, for instance to read its +configuration files (for the sake of the example, it has several configuration +files). This function can use its own type to describe ill-formed description +(~ConfigurationError~). + +#+BEGIN_SRC haskell +parseConfiguration :: (Contains err ConfigurationError, MonadIO m) + => String + -> String + -> ResultT msg err m Configuration +#+END_SRC + +To avoid repeating ~Contains~ when the row of errors needs to contains several +elements, we introduce ~:<~[fn:top] (read /subset or equal/): + +#+BEGIN_SRC haskell +getConfig :: ( '[FileError, ConfigurationError] :< err + , MonadIO m) + => ResultT String err m Configuration +getConfig = do + achieve "get configuration from ~/.myapp directory" $ do + f1 <- readContent "~/.myapp/init.conf" + "fetch the main configuration" + f2 <- readContent "~/.myapp/net.conf" + "fetch the net-related configuration" + + parseConfiguration f1 f2 +#+END_SRC + +You might see, now, why I say ~ResultT~ is extensible. You can use two functions +with totally unrelated errors, as long as the caller advertises that with +~Contains~ or ~:<~. + +[fn:top] If you are confused by ~:<~, it is probably because you were not aware +of the [[https://ocharles.org.uk/blog/posts/2014-12-08-type-operators.html][TypeOperators]] before. Maybe it was for the best. :D + +** Recovering by Handling Errors + +Monads are traps, you can only escape them by playing with their +rules. ~ResultT~ comes with ~runResultT~. + +#+BEGIN_SRC haskell +runResultT :: Monad m => ResultT msg '[] m a -> m a +#+END_SRC + +This might be surprising: we can only escape out from the ~ResultT~ if we do not +use /any errors at all/. In fact, ~ResultT~ forces us to handle errors before +calling ~runResultT~. + +~ResultT~ provides several functions prefixed by ~recover~. Their type +signatures can be a little confusing, so we will dive into the simpler one: + +#+BEGIN_SRC haskell +recover :: forall e m msg err a. + (Monad m) + => ResultT msg (e ': err) m a + -> (e -> [msg] -> ResultT msg err m a) + -> ResultT msg err m a +#+END_SRC + +~recover~ allows for /removing/ an error type from the row of errors, To do +that, it requires to provide an error handler to determine what to do with the +error raised during the computation and the stack of messages at that +time. Using ~recover~, a function may use more errors than advertised in its +type signature, but we know by construction that in such a case, it handles +these errors so that it is transparent for the function user. The type of the +handler is ~e -> [msg] -> ResultT msg err m a~, which means the handler /can +raise errors if required/. ~recoverWhile msg~ is basically a synonym for +~achieve msg $ recover~. ~recoverMany~ allows for doing the same with a row of +errors, by providing as many functions as required. Finally, ~recoverManyWith~ +simplifies ~recoverMany~: you can provide only one function tied to a given +typeclass, on the condition that the handling errors implement this typeclass. + +Using ~recover~ and its siblings often requires to help a bit the Haskell +type system, especially if we use lambdas to define the error handlers. Doing +that is usually achieved with the ~Proxy a~ dataype (where ~a~ is a phantom +type). I would rather use the TypeApplications[fn:tap] pragma. + +#+BEGIN_SRC haskell +recoverManyWith @[FileError, NetworkError] @DescriptiveError + (do x <- readFromFile f + y <- readFromNetwork socket + printToStd x y) + printErrorAndStack +#+END_SRC + +The ~DecriptiveError~ typeclass can be seen as a dedicated ~Show~, to give +textual representation of errors. It is inspired by the macros of ~error_chain~. + +We can start from an empty row of errors, and allows ourselves to +use more errors thanks to the ~recover*~ functions. + +[fn:tap] The [[https://medium.com/@zyxoas/abusing-haskell-dependent-types-to-make-redis-queues-safer-cc31db943b6c][TypeApplications]] pragmas is probably one of my favourites. When I +use it, it feels almost like if I were writing some Gallina. + +* ~cat~ in Haskell using ResultT + +~ResultT~ only cares about error handling. The rest of the work is up to the +underlying monad ~m~. That being said, nothing forbids us to provide +fine-grained API for, e.g. Filesystem-related functions. From an error handling +perspective, the functions provided by Prelude (the standard library of Haskell) +are pretty poor, and the documentation is not really precise regarding the kind +of error we can encounter while using it. + +In this section, I will show you how we can leverage ~ResultT~ to *(i)* define an +error-centric API for basic file management functions and *(ii)* use this API to +implement a ~cat~-like program which read a file and print its content in the +standard output. + +** (A Lot Of) Error Types + +We could have one sum type to describe in the same place all the errors we can +find, and later use the pattern matching feature of Haskell to determine which +one has been raised. The thing is, this is already the job done by the row of +errors of ~ResultT~. Besides, this means that we could raise an error for being +not able to write something into a file in a function which /opens/ a file. + +Because ~ResultT~ is intended to be extensible, we should rather define several +types, so we can have a fine-grained row of errors. Of course, too many types +will become burdensome, so this is yet another time where we need to find the +right balance. + +#+BEGIN_SRC haskell +newtype AlreadyInUse = AlreadyInUse FilePath +newtype DoesNotExist = DoesNotExist FilePath +data AccessDeny = AccessDeny FilePath IO.IOMode +data EoF = EoF +data IllegalOperation = IllegalRead | IllegalWrite +#+END_SRC + +To be honest, this is a bit too much for the real life, but we are in a blog post +here, so we should embrace the potential of ~ResultT~. + +** Filesystem API + +By reading the [[https://hackage.haskell.org/package/base-4.9.1.0/docs/System-IO.html][System.IO]] documentation, we can infer what our functions type +signatures should look like. I will not discuss their actual implementation in +this article, as this requires me to explain how `IO` deals with errors itself +(and this article is already long enough to my taste). You can have a look at +[[https://gist.github.com/lethom/c669e68e284a056dc8c0c3546b4efe56][this gist]] if you are interested. + +#+BEGIN_SRC haskell +openFile :: ( '[AlreadyInUse, DoesNotExist, AccessDeny] :< err + , MonadIO m) + => FilePath -> IOMode -> ResultT msg err m Handle +#+END_SRC + +#+BEGIN_SRC haskell +getLine :: ('[IllegalOperation, EoF] :< err, MonadIO m) + => IO.Handle + -> ResultT msg err m Text +#+END_SRC + +#+BEGIN_SRC haskell +closeFile :: (MonadIO m) + => IO.Handle + -> ResultT msg err m () +#+END_SRC + +** Implementing ~cat~ + +We can use the ~ResultT~ monad, its monadic operations and our functions to deal +with the file system in order to implement a ~cat~-like program. I tried to +comment on the implementation to make it easier to follow. + +#+BEGIN_SRC haskell +cat :: FilePath -> ResultT String err IO () +cat path = + -- We will try to open and read this file to mimic + -- `cat` behaviour. + -- We advertise that in case something goes wrong + -- the process. + achieve ("cat " ++ path) $ do + -- We will recover from a potential error, + -- but we will abstract away the error using + -- the `DescriptiveError` typeclass. This way, + -- we do not need to give one handler by error + -- type. + recoverManyWith @[Fs.AlreadyInUse, Fs.DoesNotExist, Fs.AccessDeny, Fs.IllegalOperation] + @(Fs.DescriptiveError) + (do f <- Fs.openFile path Fs.ReadMode + -- `repeatUntil` works like `recover`, except + -- it repeats the computation until the error + -- actually happpens. + -- I could not have used `getLine` without + -- `repeatUntil` or `recover`, as it is not + -- in the row of errors allowed by + -- `recoverManyWith`. + repeatUntil @(Fs.EoF) + (Fs.getLine f >>= liftIO . print) + (\_ _ -> liftIO $ putStrLn "%EOF") + closeFile f) + printErrorAndStack + where + -- Using the `DescriptiveError` typeclass, we + -- can print both the stack of Strings which form + -- the context, and the description of the generic + -- error. + printErrorAndStack e ctx = do + liftIO . putStrLn $ Fs.describe e + liftIO $ putStrLn "stack:" + liftIO $ print ctx +#+END_SRC + +The type system of ~cat~ teaches us that this function handles any error it +might encounter. This means we can use it anywhere we want… in another +computation inside ~ResultT~ which might raise errors completely unrelated to +the file system, for instance. Or! We can use it with ~runResultT~, escaping the +~ResultT~ monad (only to fall into the ~IO~ monad, but this is another story). + +* Conclusion + +For once, I wanted to write about the /result/ of a project, instead of /how it +is implemented/. Rest assured, I do not want to skip the latter. I need to clean +up a bit the code before bragging about it. diff --git a/site/posts/MonadTransformers.org b/site/posts/MonadTransformers.org new file mode 100644 index 0000000..e94f07d --- /dev/null +++ b/site/posts/MonadTransformers.org @@ -0,0 +1,72 @@ +#+BEGIN_EXPORT html +

Monad Transformers are a Great Abstraction

+ +July 15, 2017 +#+END_EXPORT + +#+OPTIONS: toc:nil + +Monads are hard to get right. I think it took me around a year of Haskelling to +feel like I understood them. The reason is, to my opinion, there is not such +thing as /the/ Monad. It is even the contrary. When someone asks me how I would +define Monads in only a few words, [[https://techn.ical.ist/@lthms/590439][I say Monad is a convenient formalism to +chain specific computations]]. Once I’ve got that, I started noticing “monadic +construction” everywhere, from the Rust ~?~ operator to the [[https://blog.drewolson.org/elixirs-secret-weapon/][Elixir ~with~ +keyword]]. + +Haskell often uses another concept above Monads: Monad Transformers. This allows +you to work not only with /one/ Monad, but rather a stack. Each Monad brings its +own properties and you can mix them into your very own one. That you can’t have +in Rust or Elixir, but it works great in Haskell. Unfortunately, it is not an +easy concept and it can be hard to understand. This article is not an attempt to +do so, but rather a postmortem review of one situation where I found them +extremely useful. If you think you have understood how they work, but don’t see +the point yet, you might find here a beginning of answer. + +Recently, I ran into a very good example of why Monad Transformers worth it. I +have been working on a project called [[https://github.com/ogma-project][ogma]] for a couple years now. In a +nutshell, I want to build “a tool” to visualize in time and space a +storytelling. We are not here just yet, but in the meantime I have wrote a +software called ~celtchar~ to build a novel from a list of files. One of its +newest feature is the choice of language, and by extension, the typographic +rules. This information is read from a configuration file very early in the +program flow. Unfortunately, its use comes much later, after several function +calls. + +In Haskell, you deal with that kind of challenges by relying on the Reader +Monad. It carries an environment in a transparent way. The only thing is, I was +already using the State Monad to carry the computation result. But that’s not an +issue with the Monad Transformers. + +#+BEGIN_SRC patch +-type Builder = StateT Text IO ++type Builder = StateT Text (ReaderT Language IO) +#+END_SRC + +As you may have already understood, I wasn't using the “raw” ~State~ Monad, but +rather the transformer version ~StateT~. The underlying Monad was ~IO~, because +I needed to be able to read some files from the filesystem. By replacing ~IO~ by +~ReaderT Language IO~, I basically fixed my “carry the variable to the correct +function call easily” problem. + +Retrieving the chosen language is as simple as: + +#+BEGIN_SRC patch +getLanguage :: Builder Language +getLanguage = lift ask +#+END_SRC + +And that was basically it. The complete [[https://github.com/ogma-project/celtchar/commit/65fbda8159d21d681e4e711a37fa3f05b49e6cdd][commit]] can be found on Github. + +Now, my point is not that Monad Transformers are the ultimate beast we will have +to tame once and then everything will be shiny and easy. There are a lot of +other way to achieve what I did with my ~Builder~ stack. For instance, in an +OO language, I probably would have to add a new class member to my ~Builder~ +class and I would have done basically the same thing. + +However, there is something I really like about this approach: the ~Builder~ +type definition gives you a lot of useful information already. Both the ~State~ +and ~Reader~ Monads have a well established semantics most Haskellers will +understand in a glance. A bit of documentation won’t hurt, but I suspect it is +not as necessary as one could expect. Moreover, the presence of the ~IO~ Monad +tells everyone using the ~Builder~ Monad might cause I/O. diff --git a/site/posts/extensible-type-safe-error-handling.org b/site/posts/extensible-type-safe-error-handling.org deleted file mode 100644 index 9164bc2..0000000 --- a/site/posts/extensible-type-safe-error-handling.org +++ /dev/null @@ -1,393 +0,0 @@ -#+BEGIN_EXPORT html -

Extensible Type-Safe Error Handling in Haskell

- -February 04, 2018 -#+END_EXPORT - -#+OPTIONS: toc:nil -#+TOC: headlines 2 - -A colleague of mine introduced me to the benefits of [[https://crates.io/crates/error-chain][~error-chain~]], a crate which -aims to implement /“consistent error handling”/ for Rust. I found the overall -design pretty convincing, and in his use case, the crate really makes its error -handling clearer and flexible. I knew /pijul/ uses ~error-chain~ to, but I never -had the occasion to dig more into it. - -At the same time, I have read quite a lot about /extensible effects/ in -Functional Programming, for an academic article I have submitted to -[[http://www.fm2018.org][Formal Methods 2018]][fn:fm2018]. In particular, the [[https://hackage.haskell.org/package/freer][freer]] package provides a very -nice API to define monadic functions which may use well-identified effects. For -instance, we can imagine that ~Console~ identifies the functions which may print -to and read from the standard output. A function ~askPassword~ which displays a -prompt and get the user password would have this type signature: - -#+BEGIN_SRC haskell -askPassword :: Member Console r => Eff r () -#+END_SRC - -Compared to ~IO~, ~Eff~ allows for meaningful type signatures. It becomes easier -to reason about function composition, and you know that a given function which -lacks a given effect in its type signature will not be able to use them. As a -predictable drawback, ~Eff~ can become burdensome to use. - -Basically, when my colleague showed me its Rust project and how he was using -~error-chain~, the question popped out. *Can we use an approach similar to ~Eff~ -to implement a Haskell-flavoured ~error-chain~?* - -Spoiler alert: the answer is yes. In this post, I will dive into the resulting -API, leaving for another time the details of the underlying -implementation. Believe me, there is plenty to say. If you want to have a look -already, the current implementation can be found on [[https://github.com/lethom/chain][GitHub]]. - -In this article, I will use several “advanced” GHC pragmas. I will not explain -each of them, but I will /try/ to give some pointers for the reader who wants to -learn more. - -[fn:fm2018] If the odds are in my favour, I will have plenty of occasions to write -more about this topic. - -* State of the Art - -This is not an academic publication, and my goal was primarily to explore the -arcane of the Haskell type system, so I might have skipped the proper study of -the state of the art. That being said, I have written programs in Rust and -Haskell before. - -** Starting Point - -In Rust, ~Result~ is the counterpart of ~Either E T~ in -Haskell[fn:either]. You can use it to model to wrap either the result of a -function (~T~) or an error encountered during this computation (~E~). -Both ~Either~ and ~Result~ are used in order to achieve the same end, that is -writing functions which might fail. - -On the one hand, ~Either E~ is a monad. It works exactly as ~Maybe~ (returning -an error acts as a shortcut for the rest of the function), but gives you the -ability to specify /why/ the function has failed. To deal with effects, the -~mtl~ package provides ~EitherT~, a transformer version of ~Either~ to be used -in a monad stack. - -On the other hand, the Rust language provides the ~?~ syntactic sugar, to -achieve the same thing. That is, both languages provide you the means to write -potentially failing functions without the need to care locally about failure. If -your function ~B~ uses a function ~A~ which might fail, and want to fail -yourself if ~A~ fails, it becomes trivial. - -Out of the box, neither ~EitherT~ nor ~Result~ is extensible. The functions must -use the exact same ~E~, or errors must be converted manually. - -[fn:either] I wonder if they deliberately choose to swap the two type arguments. - -** Handling Errors in Rust - -Rust and the ~error-chain~ crate provide several means to overcome this -limitation. In particular, it has the ~Into~ and ~From~ traits to ease the -conversion from one error to another. Among other things, the ~error-chain~ -crate provides a macro to easily define a wrapper around many errors types, -basically your own and the one defined by the crates you are using. - -I see several drawbacks to this approach. First, it is extensible if you take -the time to modify the wrapper type each time you want to consider a new error -type. Second, either you can either use one error type or every error -type. - -However, the ~error-chain~ package provides a way to solve a very annoying -limitation of ~Result~ and ~Either~. When you “catch” an error, after a given -function returns its result, it can be hard to determine from where the error is -coming from. Imagine you are parsing a very complicated source file, and the -error you get is ~SyntaxError~ with no additional context. How would you feel? - -~error-chain~ solves this by providing an API to construct a chain of errors, -rather than a single value. - -#+BEGIN_SRC rust -my_function().chain_err(|| "a message with some context")?; -#+END_SRC - -The ~chain_err~ function makes it easier to replace a given error in its -context, leading to be able to write more meaningful error messages for -instance. - -* The ResultT Monad - -The ~ResultT~ is an attempt to bring together the extensible power of ~Eff~ and -the chaining of errors of ~chain_err~. I will admit that, for the latter, the -current implementation of ~ResultT~ is probably less powerful, but to be honest -I mostly cared about the “extensible” thing, so it is not very surprising. - -This monad is not an alternative to neither Monad Stacks a la mtl nor to the -~Eff~ monad. In its current state, it aims to be a more powerful and flexible -version of ~EitherT~. - -** Parameters - -As often in Haskell, the ~ResultT~ monad can be parameterised in several ways. - -#+BEGIN_SRC haskell -data ResultT msg (err :: [*]) m a -#+END_SRC - -- ~msg~ is the type of messages you can stack to provide more context to error - handling -- ~err~ is a /row of errors/[fn:row], it basically describes the set of errors - you will eventually have to handle -- ~m~ is the underlying monad stack of your application, knowing that ~ResultT~ - is not intended to be stacked itself -- ~a~ is the expected type of the computation result - -[fn:row] You might have notice ~err~ is of kind ~[*]~. To write such a thing, -you will need the [[https://www.schoolofhaskell.com/user/konn/prove-your-haskell-for-great-safety/dependent-types-in-haskell][DataKinds]] GHC pragmas. - -** ~achieve~ and ~abort~ - -The two main monadic operations which comes with ~ResultT~ are ~achieve~ and -~abort~. The former allows for building the context, by stacking so-called -messages which describe what you want to do. The latter allows for bailing on a -computation and explaining why. - -#+BEGIN_SRC haskell -achieve :: (Monad m) - => msg - -> ResultT msg err m a - -> ResultT msg err m a -#+END_SRC - -~achieve~ should be used for ~do~ blocks. You can use ~~ to attach a -contextual message to a given computation. - -The type signature of ~abort~ is also interesting, because it introduces the -~Contains~ typeclass (e.g., it is equivalent to ~Member~ for ~Eff~). - -#+BEGIN_SRC haskell -abort :: (Contains err e, Monad m) - => e - -> ResultT msg err m a -#+END_SRC - -This reads as follows: /“you can abort with an error of type ~e~ if and only if -the row of errors ~err~ contains the type ~e~.”/ - -For instance, imagine we have an error type ~FileError~ to describe -filesystem-related errors. Then, we can imagine the following function: - -#+BEGIN_SRC haskell -readContent :: (Contains err FileError, MonadIO m) - => FilePath - -> ResultT msg err m String -#+END_SRC - -We could leverage this function in a given project, for instance to read its -configuration files (for the sake of the example, it has several configuration -files). This function can use its own type to describe ill-formed description -(~ConfigurationError~). - -#+BEGIN_SRC haskell -parseConfiguration :: (Contains err ConfigurationError, MonadIO m) - => String - -> String - -> ResultT msg err m Configuration -#+END_SRC - -To avoid repeating ~Contains~ when the row of errors needs to contains several -elements, we introduce ~:<~[fn:top] (read /subset or equal/): - -#+BEGIN_SRC haskell -getConfig :: ( '[FileError, ConfigurationError] :< err - , MonadIO m) - => ResultT String err m Configuration -getConfig = do - achieve "get configuration from ~/.myapp directory" $ do - f1 <- readContent "~/.myapp/init.conf" - "fetch the main configuration" - f2 <- readContent "~/.myapp/net.conf" - "fetch the net-related configuration" - - parseConfiguration f1 f2 -#+END_SRC - -You might see, now, why I say ~ResultT~ is extensible. You can use two functions -with totally unrelated errors, as long as the caller advertises that with -~Contains~ or ~:<~. - -[fn:top] If you are confused by ~:<~, it is probably because you were not aware -of the [[https://ocharles.org.uk/blog/posts/2014-12-08-type-operators.html][TypeOperators]] before. Maybe it was for the best. :D - -** Recovering by Handling Errors - -Monads are traps, you can only escape them by playing with their -rules. ~ResultT~ comes with ~runResultT~. - -#+BEGIN_SRC haskell -runResultT :: Monad m => ResultT msg '[] m a -> m a -#+END_SRC - -This might be surprising: we can only escape out from the ~ResultT~ if we do not -use /any errors at all/. In fact, ~ResultT~ forces us to handle errors before -calling ~runResultT~. - -~ResultT~ provides several functions prefixed by ~recover~. Their type -signatures can be a little confusing, so we will dive into the simpler one: - -#+BEGIN_SRC haskell -recover :: forall e m msg err a. - (Monad m) - => ResultT msg (e ': err) m a - -> (e -> [msg] -> ResultT msg err m a) - -> ResultT msg err m a -#+END_SRC - -~recover~ allows for /removing/ an error type from the row of errors, To do -that, it requires to provide an error handler to determine what to do with the -error raised during the computation and the stack of messages at that -time. Using ~recover~, a function may use more errors than advertised in its -type signature, but we know by construction that in such a case, it handles -these errors so that it is transparent for the function user. The type of the -handler is ~e -> [msg] -> ResultT msg err m a~, which means the handler /can -raise errors if required/. ~recoverWhile msg~ is basically a synonym for -~achieve msg $ recover~. ~recoverMany~ allows for doing the same with a row of -errors, by providing as many functions as required. Finally, ~recoverManyWith~ -simplifies ~recoverMany~: you can provide only one function tied to a given -typeclass, on the condition that the handling errors implement this typeclass. - -Using ~recover~ and its siblings often requires to help a bit the Haskell -type system, especially if we use lambdas to define the error handlers. Doing -that is usually achieved with the ~Proxy a~ dataype (where ~a~ is a phantom -type). I would rather use the TypeApplications[fn:tap] pragma. - -#+BEGIN_SRC haskell -recoverManyWith @[FileError, NetworkError] @DescriptiveError - (do x <- readFromFile f - y <- readFromNetwork socket - printToStd x y) - printErrorAndStack -#+END_SRC - -The ~DecriptiveError~ typeclass can be seen as a dedicated ~Show~, to give -textual representation of errors. It is inspired by the macros of ~error_chain~. - -We can start from an empty row of errors, and allows ourselves to -use more errors thanks to the ~recover*~ functions. - -[fn:tap] The [[https://medium.com/@zyxoas/abusing-haskell-dependent-types-to-make-redis-queues-safer-cc31db943b6c][TypeApplications]] pragmas is probably one of my favourites. When I -use it, it feels almost like if I were writing some Gallina. - -* ~cat~ in Haskell using ResultT - -~ResultT~ only cares about error handling. The rest of the work is up to the -underlying monad ~m~. That being said, nothing forbids us to provide -fine-grained API for, e.g. Filesystem-related functions. From an error handling -perspective, the functions provided by Prelude (the standard library of Haskell) -are pretty poor, and the documentation is not really precise regarding the kind -of error we can encounter while using it. - -In this section, I will show you how we can leverage ~ResultT~ to *(i)* define an -error-centric API for basic file management functions and *(ii)* use this API to -implement a ~cat~-like program which read a file and print its content in the -standard output. - -** (A Lot Of) Error Types - -We could have one sum type to describe in the same place all the errors we can -find, and later use the pattern matching feature of Haskell to determine which -one has been raised. The thing is, this is already the job done by the row of -errors of ~ResultT~. Besides, this means that we could raise an error for being -not able to write something into a file in a function which /opens/ a file. - -Because ~ResultT~ is intended to be extensible, we should rather define several -types, so we can have a fine-grained row of errors. Of course, too many types -will become burdensome, so this is yet another time where we need to find the -right balance. - -#+BEGIN_SRC haskell -newtype AlreadyInUse = AlreadyInUse FilePath -newtype DoesNotExist = DoesNotExist FilePath -data AccessDeny = AccessDeny FilePath IO.IOMode -data EoF = EoF -data IllegalOperation = IllegalRead | IllegalWrite -#+END_SRC - -To be honest, this is a bit too much for the real life, but we are in a blog post -here, so we should embrace the potential of ~ResultT~. - -** Filesystem API - -By reading the [[https://hackage.haskell.org/package/base-4.9.1.0/docs/System-IO.html][System.IO]] documentation, we can infer what our functions type -signatures should look like. I will not discuss their actual implementation in -this article, as this requires me to explain how `IO` deals with errors itself -(and this article is already long enough to my taste). You can have a look at -[[https://gist.github.com/lethom/c669e68e284a056dc8c0c3546b4efe56][this gist]] if you are interested. - -#+BEGIN_SRC haskell -openFile :: ( '[AlreadyInUse, DoesNotExist, AccessDeny] :< err - , MonadIO m) - => FilePath -> IOMode -> ResultT msg err m Handle -#+END_SRC - -#+BEGIN_SRC haskell -getLine :: ('[IllegalOperation, EoF] :< err, MonadIO m) - => IO.Handle - -> ResultT msg err m Text -#+END_SRC - -#+BEGIN_SRC haskell -closeFile :: (MonadIO m) - => IO.Handle - -> ResultT msg err m () -#+END_SRC - -** Implementing ~cat~ - -We can use the ~ResultT~ monad, its monadic operations and our functions to deal -with the file system in order to implement a ~cat~-like program. I tried to -comment on the implementation to make it easier to follow. - -#+BEGIN_SRC haskell -cat :: FilePath -> ResultT String err IO () -cat path = - -- We will try to open and read this file to mimic - -- `cat` behaviour. - -- We advertise that in case something goes wrong - -- the process. - achieve ("cat " ++ path) $ do - -- We will recover from a potential error, - -- but we will abstract away the error using - -- the `DescriptiveError` typeclass. This way, - -- we do not need to give one handler by error - -- type. - recoverManyWith @[Fs.AlreadyInUse, Fs.DoesNotExist, Fs.AccessDeny, Fs.IllegalOperation] - @(Fs.DescriptiveError) - (do f <- Fs.openFile path Fs.ReadMode - -- `repeatUntil` works like `recover`, except - -- it repeats the computation until the error - -- actually happpens. - -- I could not have used `getLine` without - -- `repeatUntil` or `recover`, as it is not - -- in the row of errors allowed by - -- `recoverManyWith`. - repeatUntil @(Fs.EoF) - (Fs.getLine f >>= liftIO . print) - (\_ _ -> liftIO $ putStrLn "%EOF") - closeFile f) - printErrorAndStack - where - -- Using the `DescriptiveError` typeclass, we - -- can print both the stack of Strings which form - -- the context, and the description of the generic - -- error. - printErrorAndStack e ctx = do - liftIO . putStrLn $ Fs.describe e - liftIO $ putStrLn "stack:" - liftIO $ print ctx -#+END_SRC - -The type system of ~cat~ teaches us that this function handles any error it -might encounter. This means we can use it anywhere we want… in another -computation inside ~ResultT~ which might raise errors completely unrelated to -the file system, for instance. Or! We can use it with ~runResultT~, escaping the -~ResultT~ monad (only to fall into the ~IO~ monad, but this is another story). - -* Conclusion - -For once, I wanted to write about the /result/ of a project, instead of /how it -is implemented/. Rest assured, I do not want to skip the latter. I need to clean -up a bit the code before bragging about it. diff --git a/site/posts/lisp-journey-getting-started.org b/site/posts/lisp-journey-getting-started.org deleted file mode 100644 index a198c3d..0000000 --- a/site/posts/lisp-journey-getting-started.org +++ /dev/null @@ -1,258 +0,0 @@ -#+BEGIN_EXPORT html -

Discovering Common Lisp with trivial-gamekit

- -June 17, 2018 -#+END_EXPORT - - -I always wanted to learn some Lisp dialect. -In the meantime, [[https://github.com/lkn-org/lykan][lykan]] —my Slayers Online clone— begins to take shape. -So, of course, my brain got an idea: /why not writing a client for lykan in some -Lisp dialect?/ -I asked on [[https://mastodon.social/@lthms/100135240390747697][Mastodon]] if there were good game engine for Lisp, and someone told me -about [[https://github.com/borodust/trivial-gamekit][trivial-gamekit]]. - -I have no idea if I will manage to implement a decent client using -trivial-gamekit, but why not trying? -This article is the first of a series about my experiments, discoveries and -difficulties. - -The code of my client is hosted on my server, using the pijul vcs. -If you have pijul installed, you can clone the repository: - -#+BEGIN_SRC bash -pijul clone "https://pijul.lthms.xyz/lkn/lysk" -#+END_SRC - -In addition, the complete project detailed in this article is available [[https://gist.github.com/lthms/9833f4851843119c966917775b4c4180][as a -gist]]. - -#+OPTIONS: toc:nil -#+TOC: headlines 2 - -* Common Lisp, Quicklisp and trivial-gamekit - -The trivial-gamekit [[https://borodust.github.io/projects/trivial-gamekit/][website]] lists several requirements. -Two are related to Lisp: - -1. Quicklisp -2. SBCL or CCL - -Quicklisp is an experimental package manager for Lisp project (it was easy to -guess, because there is a link to [[https://quicklisp.org/beta][quicklisp website]] in the trivial-gamekit -documentation). -As for SBCL and CCL, it turns out they are two Lisp implementations. -I had already installed [[https://www.archlinux.org/packages/?name=clisp][clisp]], and it took me quite some times to understand my -mistake. -Fortunately, [[https://www.archlinux.org/packages/?name=sbcl][sbcl]] is also packaged in ArchLinux. - -With a compatible Lisp implementation, installing Quicklisp as a user is -straightforward. -Following the website instructions is enough. -At the end of the process, you will have a new directory ~${HOME}/quicklisp~, -whose purpose is similar to the [[https://github.com/golang/go/wiki/SettingGOPATH][go workspace]]. - -Quicklisp is not a native feature of sbcl, and has to be loaded to be available. -To do it automatically, you have to create a file ~${HOME}/.sbclrc~, with the -following content: - -#+BEGIN_SRC -(load "~/quicklisp/setup") -#+END_SRC - -There is one final step to be able to use trivial-gamekit. - -#+BEGIN_SRC bash -sbcl --eval '(ql-dist:install-dist "http://bodge.borodust.org/dist/org.borodust.bodge.txt")' \ - --quit -#+END_SRC - -As for now[fn::June 2018], Quicklisp [[https://github.com/quicklisp/quicklisp-client/issues/167][does not support HTTPS]]. - -* Introducing Lysk - -** Packaging - -The first thing I search for when I learn a new language is how projects are -organized. -From this perspective, trivial-gamekit pointed me directly to Quicklisp - -Creating a new Quicklisp project is very simple, and this is a very good thing. -As I said, the ~${HOME}/quicklisp~ directory acts like the go workspace. -As far as I can tell, new Quicklisp projects have to be located inside -~${HOME}/quicklisp/local-projects~. -I am not particularly happy with it, but it is not really important. - -The current code name of my Lisp game client is lysk. - -#+BEGIN_SRC bash -mkdir ~/quicklisp/local-projects/lysk -#+END_SRC - -Quicklisp packages (systems?) are defined through ~asd~ files. -I have firstly created ~lysk.asd~ as follows: - -#+BEGIN_SRC common-lisp -(asdf:defsystem lysk - :description "Lykan Game Client" - :author "lthms" - :license "GPLv3" - :version "0.0.1" - :serial t - :depends-on (trivial-gamekit) - :components ((:file "package") - (:file "lysk"))) -#+END_SRC - -~:serial t~ means that the files detailed in the ~components~ field depends on -the previous ones. -That is, ~lysk.lisp~ depends on ~package.lisp~ in this case. -It is possible to manage files dependencies manually, with the following syntax: - -#+BEGIN_SRC common-lisp -(:file "seconds" :depends-on "first") -#+END_SRC - -I have declared only one dependency: trivial-gamekit. -That way, Quicklisp will load it for us. - -The first “true” Lisp file we define in our skeleton is ~package.lisp~. -Here is its content: - -#+BEGIN_SRC common-lisp -(defpackage :lysk - (:use :cl) - (:export run app)) -#+END_SRC - -Basically, this means we use two symbols, ~run~ and ~app~. - -** A Game Client - -The ~lysk.lisp~ file contains the program in itself. -My first goal was to obtain the following program: at startup, it shall creates -a new window in fullscreen, and exit when users release the left button of their -mouse. -It is worth mentioning that I had to report [[https://github.com/borodust/trivial-gamekit/issues/30][an issue to the trivial-gamekit -upstream]] in order to make my program work as expected. -While it may sounds scary —it definitely shows trivial-gamekit is a relatively -young project— the author has implemented a fix in less than an hour! -He also took the time to answer many questions I had when I joined the -~#lispgames~ Freenode channel. - -Before going any further, lets have a look at the complete file. - -#+BEGIN_SRC common-lisp -(cl:in-package :lysk) - -(gamekit:defgame app () () - (:fullscreen-p 't)) - -(defmethod gamekit:post-initialize ((app app)) - (gamekit:bind-button :mouse-left :released - (lambda () (gamekit:stop)))) - -(defun run () - (gamekit:start 'app)) -#+END_SRC - -The first line is some kind of header, to tell Lisp the owner of the file. - -The ~gamekit:defgame~ function allows for creating a new game application -(called ~app~ in our case). -I ask for a fullscreen window with ~:fullscreen-p~. -Then, we use the ~gamekit:post-initialize~ hook to bind a handler to the release -of the left button of our mouse. -This handler is a simple call to ~gamekit:stop~. -Finally, we define a new function ~run~ which only starts our application. - -Pretty straightforward, right? - -** Running our Program - -To “play” our game, we can start the sbcl REPL. - -#+BEGIN_SRC bash -sbcl --eval '(ql:quickload :lysk)' --eval '(lysk:run)' -#+END_SRC - -And it works! - -** A Standalone Executable - -It looks like empower a REPL-driven development. -That being said, once the development is finished, I don't think I will have a -lot of success if I ask my future players to start sbcl to enjoy my game. -Fortunately, trivial-gamekit provides a dedicated function to bundle the game as -a standalone executable. - -Following the advises of the borodust —the trivial-gamekit author— I created a -second package to that end. -First, we need to edit the ~lysk.asd~ file to detail a second package: - -#+BEGIN_SRC common-lisp -(asdf:defsystem lysk/bundle - :description "Bundle the Lykan Game Client" - :author "lthms" - :license "GPLv3" - :version "0.0.1" - :serial t - :depends-on (trivial-gamekit/distribution lysk) - :components ((:file "bundle"))) -#+END_SRC - -This second package depends on lysk (our game client) and and -trivial-gamekit/distribution. -The latter provides the ~deliver~ function, and we use it in the ~bundle.lisp~ -file: - -#+BEGIN_SRC common-lisp -(cl:defpackage :lysk.bundle - (:use :cl) - (:export deliver)) - -(cl:in-package :lysk.bundle) - -(defun deliver () - (gamekit.distribution:deliver :lysk 'lysk:app)) -#+END_SRC - -To bundle the game, we can use ~sbcl~ from our command line interface. - -#+BEGIN_SRC bash -sbcl --eval "(ql:quickload :lysk/bundle)" \ - --eval "(lysk.bundle:deliver)" \ - --quit -#+END_SRC - -* Conclusion - -Objectively, there is not much in this article. -However, because I am totally new to Lisp, it took me quite some time to get -these few lines of code to work together. -All being told I think this constitutes a good trivial-gamekit skeleton. -Do not hesitate to us it this way. - -Thanks again to borodust, for your time and all your answers! - -* Appendix: a Makefile - -I like Makefile, so here is one to ~run~ the game directly, or ~bundle~ it. - -#+BEGIN_SRC makefile -run: - @sbcl --eval "(ql:quickload :lysk)" \ - --eval "(lysk:run)" - -bundle: - @echo -en "[ ] Remove old build" - @rm -rf build/ - @echo -e "\r[*] Remove old build" - @echo "[ ] Building" - @sbcl --eval "(ql:quickload :lysk/bundle)" \ - --eval "(lysk.bundle:deliver)" \ - --quit - @echo "[*] Building" - -.PHONY: bundle run -#+END_SRC diff --git a/site/posts/monad-transformers.org b/site/posts/monad-transformers.org deleted file mode 100644 index e94f07d..0000000 --- a/site/posts/monad-transformers.org +++ /dev/null @@ -1,72 +0,0 @@ -#+BEGIN_EXPORT html -

Monad Transformers are a Great Abstraction

- -July 15, 2017 -#+END_EXPORT - -#+OPTIONS: toc:nil - -Monads are hard to get right. I think it took me around a year of Haskelling to -feel like I understood them. The reason is, to my opinion, there is not such -thing as /the/ Monad. It is even the contrary. When someone asks me how I would -define Monads in only a few words, [[https://techn.ical.ist/@lthms/590439][I say Monad is a convenient formalism to -chain specific computations]]. Once I’ve got that, I started noticing “monadic -construction” everywhere, from the Rust ~?~ operator to the [[https://blog.drewolson.org/elixirs-secret-weapon/][Elixir ~with~ -keyword]]. - -Haskell often uses another concept above Monads: Monad Transformers. This allows -you to work not only with /one/ Monad, but rather a stack. Each Monad brings its -own properties and you can mix them into your very own one. That you can’t have -in Rust or Elixir, but it works great in Haskell. Unfortunately, it is not an -easy concept and it can be hard to understand. This article is not an attempt to -do so, but rather a postmortem review of one situation where I found them -extremely useful. If you think you have understood how they work, but don’t see -the point yet, you might find here a beginning of answer. - -Recently, I ran into a very good example of why Monad Transformers worth it. I -have been working on a project called [[https://github.com/ogma-project][ogma]] for a couple years now. In a -nutshell, I want to build “a tool” to visualize in time and space a -storytelling. We are not here just yet, but in the meantime I have wrote a -software called ~celtchar~ to build a novel from a list of files. One of its -newest feature is the choice of language, and by extension, the typographic -rules. This information is read from a configuration file very early in the -program flow. Unfortunately, its use comes much later, after several function -calls. - -In Haskell, you deal with that kind of challenges by relying on the Reader -Monad. It carries an environment in a transparent way. The only thing is, I was -already using the State Monad to carry the computation result. But that’s not an -issue with the Monad Transformers. - -#+BEGIN_SRC patch --type Builder = StateT Text IO -+type Builder = StateT Text (ReaderT Language IO) -#+END_SRC - -As you may have already understood, I wasn't using the “raw” ~State~ Monad, but -rather the transformer version ~StateT~. The underlying Monad was ~IO~, because -I needed to be able to read some files from the filesystem. By replacing ~IO~ by -~ReaderT Language IO~, I basically fixed my “carry the variable to the correct -function call easily” problem. - -Retrieving the chosen language is as simple as: - -#+BEGIN_SRC patch -getLanguage :: Builder Language -getLanguage = lift ask -#+END_SRC - -And that was basically it. The complete [[https://github.com/ogma-project/celtchar/commit/65fbda8159d21d681e4e711a37fa3f05b49e6cdd][commit]] can be found on Github. - -Now, my point is not that Monad Transformers are the ultimate beast we will have -to tame once and then everything will be shiny and easy. There are a lot of -other way to achieve what I did with my ~Builder~ stack. For instance, in an -OO language, I probably would have to add a new class member to my ~Builder~ -class and I would have done basically the same thing. - -However, there is something I really like about this approach: the ~Builder~ -type definition gives you a lot of useful information already. Both the ~State~ -and ~Reader~ Monads have a well established semantics most Haskellers will -understand in a glance. A bit of documentation won’t hurt, but I suspect it is -not as necessary as one could expect. Moreover, the presence of the ~IO~ Monad -tells everyone using the ~Builder~ Monad might cause I/O. -- cgit v1.2.3