From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp11.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id EDfMIxfV6GPqXQAAbAwnHQ (envelope-from ) for ; Sun, 12 Feb 2023 13:01:27 +0100 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp11.migadu.com with LMTPS id kHrgIxfV6GNLOwAA9RJhRA (envelope-from ) for ; Sun, 12 Feb 2023 13:01:27 +0100 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 3CBB042A10 for ; Sun, 12 Feb 2023 13:01:27 +0100 (CET) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pRB2Y-0007ll-MB; Sun, 12 Feb 2023 07:01:06 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pRB2U-0007kX-TB for guix-patches@gnu.org; Sun, 12 Feb 2023 07:01:03 -0500 Received: from debbugs.gnu.org ([209.51.188.43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1pRB2U-0001KG-KD for guix-patches@gnu.org; Sun, 12 Feb 2023 07:01:02 -0500 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1pRB2U-0007qE-GE for guix-patches@gnu.org; Sun, 12 Feb 2023 07:01:02 -0500 X-Loop: help-debbugs@gnu.org Subject: [bug#61214] [PATCH guix-artwork v3] website: posts: Add Dissecting Guix, Part 2: The Store Monad. Resent-From: Christopher Baines Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Sun, 12 Feb 2023 12:01:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 61214 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: "(" Cc: 61214@debbugs.gnu.org X-Debbugs-Original-Cc: 61214@debbugs.gnu.org, guix-patches@gnu.org Received: via spool by 61214-submit@debbugs.gnu.org id=B61214.167620322530064 (code B ref 61214); Sun, 12 Feb 2023 12:01:02 +0000 Received: (at 61214) by debbugs.gnu.org; 12 Feb 2023 12:00:25 +0000 Received: from localhost ([127.0.0.1]:44218 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pRB1s-0007op-G8 for submit@debbugs.gnu.org; Sun, 12 Feb 2023 07:00:25 -0500 Received: from mira.cbaines.net ([212.71.252.8]:42210) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pRB1q-0007og-0k for 61214@debbugs.gnu.org; Sun, 12 Feb 2023 07:00:23 -0500 Received: from localhost (unknown [IPv6:2a02:8010:68c1:0:54d1:d5d4:280e:f699]) by mira.cbaines.net (Postfix) with ESMTPSA id 266B916620; Sun, 12 Feb 2023 12:00:21 +0000 (GMT) Received: from felis (localhost [127.0.0.1]) by localhost (OpenSMTPD) with ESMTP id 4cf79a71; Sun, 12 Feb 2023 12:00:20 +0000 (UTC) References: <20230201172821.3072-1-paren@disroot.org> <20230203073624.2338-1-paren@disroot.org> User-agent: mu4e 1.8.11; emacs 28.2 From: Christopher Baines Date: Sun, 12 Feb 2023 10:47:11 +0000 In-reply-to: <20230203073624.2338-1-paren@disroot.org> Message-ID: <875yc73xgu.fsf@cbaines.net> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha512; protocol="application/pgp-signature" X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+larch=yhetil.org@gnu.org Sender: guix-patches-bounces+larch=yhetil.org@gnu.org X-Migadu-Flow: FLOW_IN X-Migadu-Country: US ARC-Seal: i=1; s=key1; d=yhetil.org; t=1676203287; a=rsa-sha256; cv=none; b=bGdkdFyPBvv3d64uPwtnQ5C2KMPioN9WNS90sJ7BotMnJRfcsZ+M8KlGeg7Jdx7jgzS/fi H/7RRRV7wb1AQ3BFKkvruzpPG3wkw/vNEXi+FM57AVje/T51tnhkZhUyWh5JiAir1QdI7P 2Dpdo6Tc4o19xUBsKgHTi9AwFObUMYcL5EtdsqUj+d9jjQI4qBOKavtv4WStp5rNt4izfI d05wkvdxsiiYToe+V1N+GuGd9jQUl34PBbSRTR27kF1NTU5q/Gf7FsqU9vp/YpBLiBKMyj Z0CFxfaiArOTkZjT5v8afmZsz/EIOE1rG79hfeToJXwgFDEr5bjMgKTrzyFRjA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1676203287; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:resent-cc:resent-from:resent-sender: resent-message-id:in-reply-to:in-reply-to:references:references: list-id:list-help:list-unsubscribe:list-subscribe:list-post; bh=2wkfsncs8YJefeWUjdKfluDaUXSfFW6fd/Hm1YTOLcM=; b=ZxNIIas/9cZSKzYGaJZncJEcOUge3HEr+PMWwJ0b/2AbRBEMZ01VCXCWgGUBJnzFpZLeuF 11kEkoEDUSz1TYekzzTpvCmjHxVMON7oBrSzmNZWi3XMaIvAwydPYwpGH0icR5UMJKgZLr WGDulnnfF9PZsxh244h8ovup72i6vfSuH5jwFsG7t/Sqq2t5LCMD2col4u6gPC0+wG82WL SOEaQ4wnjleUaQjX3zPjmIOwFygkukaNxQNJDZv/uCmFwYqgU1NgHnge1JgUUuUuTw3ia4 H5vPdxdoYEjvklIvv9460lDIYPPBr8I5UzaaJa8vcvrV8QtWdEgevmzkhfGizA== X-Migadu-Queue-Id: 3CBB042A10 Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" X-Migadu-Scanner: scn0.migadu.com X-Migadu-Spam-Score: -5.72 X-Spam-Score: -5.72 X-TUID: N0/Si8kBvo+5 --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable "( via Guix-patches" via writes: > +Hello again! > + > +In [the last post](https://guix.gnu.org/en/blog/2023/dissecting-guix-par= t-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`_ v= alues, > +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 ov= er 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 u= p 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 origin= al > +wheel. Guix provides a generic, high-level monads API, along with the t= wo > +generic monads `%identity-monad` and `%state-monad`, and the Guix-specif= ic > +`%store-monad`. Since `maybe` is not one of them, let's integrate our v= ersion > +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. =2D 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. =2D we'll make the API available I'm not too fussed about this. =2D 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 ca= lls > +together, and `return` is the procedure that wraps values in the most ba= sic form > +of the monad. A properly implemented `bind` and `return` must follow th= ese > +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 `>>=3D` and `return`, as w= e will > +see. There's `mbegin`, which evaluates monadic expressions without bind= ing them > +to symbols, returning the last one: > + > +```scheme > +(mbegin %maybe-monad > + (remove-a "abc")) > +;; #< 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 `(>>=3D 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 mo= nad 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)) > +;; #: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 w= e do > +some `>>=3D`ing: > + > +```scheme > +(define state-seq > + (mlet* %state-monad ((number (return 33))) > + (state-push number))) > +result > +;; #: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 i= t onto > +the second value (the state value), which is assumed to be a list, retur= ning 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 `>>=3D` form passes that and `33` = to > +`state-push`. What `%state-monad` allows us to do is thread together so= me > +procedures that require some kind of state, while pretending the state i= sn'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 cl= ear. > +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 numb= er 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 th= is 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=C3=A7ade! 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, rathe= r than > +G-expressions (we'll discuss gexps another time). The best way to creat= e 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))) > +;; # > +``` > + > +You don't have to understand the `#~(...)` form yet, only everything sur= rounding > +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` mona= dic > +value, we use our old friend `run-with-store` when we have a `%store-mon= ad` > +monadic value! > + > +```scheme > +(define symlink-irssi-drv > + (with-store store > + (run-with-store store > + symlink-irssi))) > +;; # /gnu/store/6a94niigx4ii0ldjdy33wx9anhifr25x-link-to-irssi 7fddb7= ef52d0> > +``` > + > +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))) > +=20=20=20=20 > +;; (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 oth= er, > +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 proce= dures > +that perform similar functions (which we will discuss later), but for th= e sake > +of this blog post, here's an example: > + > +```scheme > +(with-store store > + (run-with-store store > + (text-file "unmatched-paren" > + "( "))) > +;; "/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 lac= k 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 t= hrough > + procedures such that you can pretend it doesn't exist. > +4. Guix uses the store monad frequently to thread a store connection thr= ough > + 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 i= t, 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? --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQKlBAEBCgCPFiEEPonu50WOcg2XVOCyXiijOwuE9XcFAmPo1NFfFIAAAAAALgAo aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDNF ODlFRUU3NDU4RTcyMEQ5NzU0RTBCMjVFMjhBMzNCMEI4NEY1NzcRHG1haWxAY2Jh aW5lcy5uZXQACgkQXiijOwuE9XcikQ/+OGv7ppCF0xh/PeowuGkmL83L/GvK3Wl2 Ouk9R7MMJ4a55XkI0gZ9PGIrS4RQ/Y40I8wNjag/5SPd9KWyKzdSKI+Jp/9ASOI2 eyt5dWMgl2h0eNwFWQ5qYQBWlZmbJ28ChwKYlMoOh1eM0xcK7s1yWCQ7QzHx8aID 96a4eBqKeC1KKbpwlmfq1Oa2uqEU4eS2Met/hb7yldapnWuRIxvCXc/IxwQbdRaZ HeOsc929N2aBaiXQUuLWqhvtwq/s6t9tZZJsUokgmqCEIxgBE28EFN0o0ipF9Ez7 QEjQxgaPt3ZMcWhp7ntmDRjlmzun4JVG0GdXr4w9tygPPV0lZl25T8qXphKzlAob FMry4RYypFBK+W7q8F1T+bMFm/z8hgQcXJV2w8iY40IpYvQ/7U31r6En/z8P5W0K KZNR84yUegHTV1wVHM248kBzczMZlUOSv0RPNTeH/ZPKbnCrhWDDX6JnRmGUvZwX kRQFQVPCipdISimk2PSHkbpm1XhIs7vv3+JFkYwkSkravB2kUYEw4MR0XgvXhQUa HBEuZdEklWwK2hzMYCDtqdfdJKj2QBEcZ/TnFtEaDQ/o+UZeiA+rj9iShzZMQUIG 1kX3U2PQq463fig+5q5jNW9+uEC6S97z1C/KfGWZJBsotGhYaV65/YTY9NjxUkhD 05VoOrfT4b4= =SJ4y -----END PGP SIGNATURE----- --=-=-=--