guix-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[bug#61214] [PATCH guix-artwork v3] website: posts: Add Dissecting Guix,


From: Christopher Baines
Subject: [bug#61214] [PATCH guix-artwork v3] website: posts: Add Dissecting Guix, Part 2: The Store Monad.
Date: Sun, 12 Feb 2023 10:47:11 +0000
User-agent: mu4e 1.8.11; emacs 28.2

"( via Guix-patches" via <guix-patches@gnu.org> writes:

> +Hello again!
> +
> +In [the last 
> post](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-1-derivations/),
> +we briefly mentioned the `with-store` and `run-with-store` APIs.  Today, 
> we'll
> +be looking at those in further detail, along with the related monad API and 
> the
> +`%store-monad`!
> +
> +Monads are a little hard to explain, and from a distance, they seem more 
> than a
> +bit confusing.  So, I want you to erase monads from your mind for now.  We'll
> +come back to them later.

I think there's some room to improve the introduction here. Linking to
the previous post in the series is fine, but what I think is missing is
some context around the topic and setting some expectations for the
reader.

I'm not sure who you're pitching this post at, but I'll assume that you
want it to be accessible and interesting to people who don't know
anything about Guix, but maybe have some programing experience.

I think this introduction here [1] is a really good one. It's not too
long, but it puts the topic in some context, sets expectations, and does
all of that in a way that I think would be understood by someone who
doesn't know about Guix.

1: https://guix.gnu.org/en/blog/2021/the-big-change/

> +# Yes, No, Maybe So
> +
> +Let's instead implement another M of functional programming, _`maybe`_ 
> values,
> +representing a value that may or may not exist.  `maybe` is a very common
> +feature of strongly-typed functional languages, and you'll see it all over 
> the
> +place in Haskell and OCaml code. However, Guile is dynamically typed, so we
> +usually use ad-hoc `#f`s and `'()`s for null values instead of a proper
> +"optional" value.

I think the s's after the `#f` and `'()` here don't aid
readability. Something like:

  usually use ad-hoc `#false` and `'()` (empty list) values instead

> +Just for fun, though, we'll implement a proper `maybe` in Guile.  Fire up 
> that
> +REPL once again, and let's import a bunch of modules that we'll need:

...

> +A more formal definition would be that a monad is a mathematical object 
> composed
> +of three parts: a type, a `bind` function, and a `return` function.  So, how 
> do
> +monads relate to Guix?
> +
> +# New Wheel, Old Wheel
> +
> +Now that we've reinvented the wheel, we'd better learn to use the original
> +wheel.  Guix provides a generic, high-level monads API, along with the two
> +generic monads `%identity-monad` and `%state-monad`, and the Guix-specific
> +`%store-monad`.  Since `maybe` is not one of them, let's integrate our 
> version
> +into the Guix monad system!
> +
> +First we'll make the API available:
> +
> +```scheme
> +(use-modules (guix monads))
> +```
> +
> +To define a monad's API in Guix, we simply use the `define-monad` macro, and
> +provide two procedures: `bind`, and `return`.

At least when I read this, I'm drawn to the use of "API" numerous times
and keeping track of what's being talked about.

- Guix provides a generic, high-level monads API

Maybe "Guix includes a generic monads module providing syntax and types,
along with the two generic monads ..." would be more informative here.

- we'll make the API available

I'm not too fussed about this.

- To define a monad's API in Guix, we

Maybe API here refers to the same API as just mentioned previously, but
I guess you're now talking about a different API, but this is confusing.

I think it would be clearer to say "To define the maybe monad, we use
the define-monad macro.", then there's no need to keep track of what API
is being discussed. I'm also not sure it's useful to talk about things
within Guix as APIs unless you're talking about a specific case of using
Guix from some external program/software.

> +```scheme
> +(define-monad %maybe-monad
> +  (bind maybe-chain)
> +  (return something))
> +```
> +
> +`bind` is just the procedure that we use to compose monadic procedure calls
> +together, and `return` is the procedure that wraps values in the most basic 
> form
> +of the monad.  A properly implemented `bind` and `return` must follow these
> +laws:

I think this would be confusing for someone who's encountering monads
for the first time. I think it's good to try and avoid going to deep,
but if there's mention of the "laws", I think it's important to say that
these laws come from category theory.

...

> +But Guix provides many higher-level APIs than `>>=` and `return`, as we will
> +see.  There's `mbegin`, which evaluates monadic expressions without binding 
> them
> +to symbols, returning the last one:
> +
> +```scheme
> +(mbegin %maybe-monad
> +  (remove-a "abc"))
> +;; #<<maybe> is?: #t value: "bc">
> +```

This is stretching my understanding of monads here, but would this
example be better if the (mbegin bit included two expressions rather
than one?

> +And there's `mlet` and `mlet*`, which can bind them, and are essentially
> +equivalent to a chain of `(>>= MEXPR (lambda (BINDING) ...))`:

...

> +This is all well and good, you may be thinking, but why does Guix need a 
> monad
> +API?  The answer is technically that it doesn't.  But building on the monad 
> API
> +makes a lot of things much easier, and to learn why, we're going to look at 
> one
> +of Guix's built-in monads.

The "API" returns. At least when I think of an "API" in the context of
Guix, I'm thinking of that interface providing a way to use Guix, from
an external prospective. Obviously that doesn't really match up with
what's going on here.

I think the point is still good here, but maybe it's simpler to say "but
why does Guix use monads?".

> +# In a State
> +
> +Guix implements a monad called `%state-monad`, and it works with 
> single-argument
> +procedures returning two values.  Behold:
> +
> +```scheme
> +(with-monad %state-monad
> +  (return 33))
> +;; #<procedure 21dc9a0 at <unknown port>:1106:22 (state)>
> +```
> +
> +The `run-with-state` value turns this procedure into an actually useful 
> value,
> +or, rather, two values:
> +
> +```scheme
> +(run-with-state (with-monad %state-monad (return 33))
> +  (list "foo" "bar" "baz"))
> +;; 33
> +;; ("foo" "bar" "baz")
> +```
> +
> +What can this actually do for us, though? Well, it gets interesting if we do
> +some `>>=`ing:
> +
> +```scheme
> +(define state-seq
> +  (mlet* %state-monad ((number (return 33)))
> +    (state-push number)))
> +result
> +;; #<procedure 7fcb6f466960 at <unknown port>:1484:24 (state)>
> +
> +(run-with-state state-seq (list 32))
> +;; (32)
> +;; (33 32)
> +
> +(run-with-state state-seq (list 30 99))
> +;; (30 99)
> +;; (33 30 99)
> +```
> +
> +What is `state-push`?  It's a monadic procedure for `%state-monad` that takes
> +whatever's currently in the first value (the primary value) and pushes it 
> onto
> +the second value (the state value), which is assumed to be a list, returning 
> the
> +old state value as the primary value and the new list as the state value.
> +
> +So, when we do `(run-with-state result (list 32))`, we're passing `(list 
> 32)` as
> +the initial state value, and then the `>>=` form passes that and `33` to
> +`state-push`.  What `%state-monad` allows us to do is thread together some
> +procedures that require some kind of state, while pretending the state isn't
> +there, and then retrieve both the final state and the result at the end!

I'm not sure the "pretending the state isn't there" but is helpful here,
if you're pretending the state doesn't exist, why is writing monadic
code helpful?

> +If you're a bit confused, don't worry.  We'll write some of our own
> +`%state-monad`-based monadic procedures and hopefully all will become clear.
> +Consider, for instance, the
> +[Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_number), in 
> which
> +each value is computed by adding the previous two.  We could use the
> +`%state-monad` to compute Fibonacci numbers by storing the previous number as
> +the primary value and the number before that as the state value:

...

> +This is all very nifty, and possibly useful in general, but what does this 
> have
> +to do with Guix?  Well, many Guix store-based operations are meant to be used
> +in concert with yet another monad, called the `%store-monad`.  But if we 
> look at
> +`(guix store)`, where `%store-monad` is defined...
> +
> +```scheme
> +(define-alias %store-monad %state-monad)
> +(define-alias store-return state-return)
> +(define-alias store-bind state-bind)
> +```
> +
> +It was all a shallow façade!  All the "store monad" is is a special case of 
> the
> +state monad, where a value representing the store is passed as the state 
> value.
> +
> +# Lies, Damned Lies, and Abstractions
> +
> +We mentioned that, technically, we didn't need monads for Guix.  Indeed, many
> +(now deprecated) procedures take a store value as the argument, such as
> +`build-expression->derivation`.  However, using monads both helps ensure 
> purity
> +and simply looks nicer.

I'm not sure what you mean by purity here?

> +`build-expression->derivation`, being deprecated, should never of course be
> +used.  For one thing, it uses the "quoted build expression" style, rather 
> than
> +G-expressions (we'll discuss gexps another time).  The best way to create a
> +derivation from some basic build code is to use the new-fangled
> +`gexp->derivation` procedure:
> +
> +```scheme
> +(use-modules (guix gexp)
> +             (gnu packages irc))
> +
> +(define symlink-irssi
> +  (gexp->derivation "link-to-irssi"
> +    #~(symlink #$(file-append irssi "/bin/irssi") #$output)))
> +;; #<procedure 7fddcc7b81e0 at guix/gexp.scm:1180:2 (state)>
> +```
> +
> +You don't have to understand the `#~(...)` form yet, only everything 
> surrounding
> +it.  We can see that this `gexp->derivation` returns a procedure taking the
> +initial state (store), just like our `%state-monad` procedures did, and like 
> we
> +used `run-with-state` to pass the initial state to a `%state-monad` monadic
> +value, we use our old friend `run-with-store` when we have a `%store-monad`
> +monadic value!
> +
> +```scheme
> +(define symlink-irssi-drv
> +  (with-store store
> +    (run-with-store store
> +      symlink-irssi)))
> +;; #<derivation 
> /gnu/store/q7kwwl4z6psifnv4di1p1kpvlx06fmyq-link-to-irssi.drv => 
> /gnu/store/6a94niigx4ii0ldjdy33wx9anhifr25x-link-to-irssi 7fddb7ef52d0>
> +```
> +
> +Let's just check this derivation is as expected by reading the code from the
> +builder script.
> +
> +```scheme
> +(define symlink-irssi-builder
> +  (list-ref (derivation-builder-arguments symlink-irssi-drv) 1))
> +
> +(call-with-input-file symlink-irssi-builder
> +  (lambda (port)
> +    (read port)))
> +    
> +;; (symlink
> +;;  "/gnu/store/hrlmypx1lrdjlxpkqy88bfrzg5p0bn6d-irssi-1.4.3/bin/irssi"
> +;;  ((@ (guile) getenv) "out"))
> +```
> +
> +And indeed, it symlinks the `irssi` binary to the output path.  Some other,
> +higher-level, monadic procedures include `interned-file`, which copies a file
> +from outside the store into it, and `text-file`, which copies some text into 
> it.
> +Generally, these procedures aren't used, as there are higher-level procedures
> +that perform similar functions (which we will discuss later), but for the 
> sake
> +of this blog post, here's an example:
> +
> +```scheme
> +(with-store store
> +  (run-with-store store
> +    (text-file "unmatched-paren"
> +      "( <paren@disroot.org>")))
> +;; "/gnu/store/v6smacxvdk4yvaa3s3wmd54lixn1dp3y-unmatched-paren"
> +```

I think the build up to this section is pretty good, but then I'm not
sure what this last section is trying to explain.

Maybe at this point it would be good to leave the REPL and give some
concrete examples of non-trivial monadic code in Guix, and discuss what
that would look like if implemented without using monads.

> +# Conclusion
> +
> +What have we learned about monads?  The key points we can take away are:
> +
> +1. Monads are a way of composing together procedures and values that are 
> wrapped
> +   in containers that give them extra context, like `maybe` values.
> +2. Guix provides a high-level monad API that compensates for Guile's lack of
> +   strong types or an interface-like system.

I'd say that Guile is a strongly typed language. I'm also not sure what
the point about compensating for something lacking in Guile means.

> +3. This API provides the state monad, which allows you to thread state 
> through
> +   procedures such that you can pretend it doesn't exist.
> +4. Guix uses the store monad frequently to thread a store connection through
> +   procedures that need it.
> +5. The store monad is really just the state monad in disguise, where the 
> state
> +   value is used to thread the store object through monadic procedures.

4 and 5 here are observations, but not very useful conclusions. I think
the more interesting question to ask is why are things implemented this
way?

Ideally the closing points would be well made in the previous section,
and this final bit would be a summary.

> +If you've read this post in its entirety but still don't yet quite get it, 
> don't
> +worry.  Try to modify and tinker about with the examples, and hopefully it 
> will
> +all click eventually!

Maybe this could be a call to get involved in the community (talk on IRC
or the mailing list?

Attachment: signature.asc
Description: PGP signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]