chain

2026-01-01

Chaining/threading macros, one of them `setf`ing its first argument

Upstream URL

gitlab.com/Aksej/chain

Author

Thomas Bartscher <thomas-bartscher@weltraumschlangen.de>

License

BSD-3
README

Chain

Migrated to Codeberg

Table of Contents

[in package CHAIN]

  • [system] "chain"
    • Version: 0.4.0
    • Description: Chaining/threading macros, one of them setfing its first argument
    • Licence: BSD-3
    • Author: Thomas Bartscher thomas-bartscher@weltraumschlangen.de
    • Depends on: metabang-bind, mgl-pax

1 Reference

  • [macro] => ARGUMENT &BODY BODY

    Thread a value through a series of transformations, where <> represents the value of the last transformation.

    • Arguments

      • argument

        Initial value. Supports :let as in the body.

      • body

        Forms to apply sequentially. Each form has <> bound as an anaphoric variable bound to the return value of the previous form in the body. Each form may be:

        • A symbol

          This is taken to be the name of a function, and called on the result of the previous form

          Note that <> as a bare symbol form also will be interpreted in this way, and not as a variable.

        • A regular form

          Evaluated normally. Gets its semantics from <> being bound to the result of the previous form.

        • (:let [var] [form])

          Binds [var] to the result of [form] in the remainder of the chain.

        • (:call [funcallable])

          [funcallable] needs to evaluate to a funcallable object. Calls that object onto the previous value.

          Can be understood as

          (funcall [funcallable] <>)
        • (:req [form])

          If [form] returns nil, the whole macro call will short-circuit and return nil without evaluating further forms. If [form] returns a non-nil value, that value is taken to be the value of the :req form.

          Can be understood as

          (or [form] (bail))
        • (:req [predicate] [form])

          Evaluates [form] as usual, then checks whether [predicate] returns nil on the result. If it does, short circuit and return nil from =>, otherwise continue with the computed value.

          Can be understood as

          (let ((value [form]))
            (if ([predicate] value)
                value
                (bail)))
        • (:cond [form])

          If the [form] returns nil, the value of the :cond form is taken to be the value of the previous form. Otherwise the value of the :cond form is the value of [form].

          Can be understood as

          (or [form] <>)
        • (:cond [predicate] [form])

          Evaluates [form] as usual, then checks whether [predicate] returns nil on the result. If it does, return the value of the previous form, otherwise continue with the computed value.

          Can be understood as

          (let ((value [form]))
            (if ([predicate] value)
                value
                <>))
        • (lambda ([argument]) [body])

          This is called on the result of the previous form.

        :let, :req, and :cond can be nested directly in each other. :call can be nested inside the other forms.

        Inside body the bail and finish macros are defined. They allow to return from the => form early. bail takes an optional and finish a required argument for a return value. finish is supplied for easier switching between => and set=>.

        Note that, unlike other chaining/threading macros, => does not support something like this:

        !(=> '(1 2 3)
        !  (remove-if #'evenp)
        !  (mapcar #'-))

        This would not compile, with remove-if and mapcar not being supplied enough arguments.

    • Return value

      Returns the value of its last form, or, should bail or finish be called, the value of their argument, defaulting to nil for bail.

    • Examples

      ;;; basic usage
      (=> 5
        (* <> 2)  ; => 10
        1+)
      => 11
      
      (defun sigmoid (x)
        (=> x
          - exp 1+ /))
      (sigmoid 0)
      => 0.5
      
      ;;; lambda form usage
      (=> 5
        (lambda (x)  ; => 8
          (+ x 3))
        (* <> 2))
      => 16
      
      ;;; using `:let`
      (defun triangle (n)
        (=> (:let a n)
          1+
          (* <> a)
          (/ <> 2)))
      
      (triangle 10)
      => 55
      
      ;;; using `:call`
      (defun f^2 (f x)
        (=> x
          (:call f)
          (:call f)))
      
      (f^2 (lambda (x)
             (* x x))
           10)
      => 10000
      
      ;;; using `:req`
      (defun negate-first-even (xs)
        (=> xs
          (:req (first (member-if #'evenp
                                  <>)))
          -))
      
      (negate-first-even '(1 2 3))
      => -2
      
      (negate-first-even '(1 3))
      => NIL
      
      (defun maybe-halve (x)
        (=> x
          (:req integerp (/ <> 2))))
      
      (maybe-halve 3)
      => NIL
      
      (maybe-halve 4)
      => 2
      
      ;;; using `:cond`
      (defun negate-first-prefer-even (xs)
        (=> xs
          (:cond (member-if #'evenp
                            <>))
          first -))
      
      (negate-first-prefer-even '(1 2 3))
      => -2
      
      (negate-first-prefer-even '(1 3))
      => -1
      
      (defun halve-even (x)
        (=> x
          (:cond integerp (/ <> 2))))
      
      (halve-even 3)
      => 3
      
      (halve-even 4)
      => 2
      
      ;;; using `bail`
      (defun halve-and-inc-maybe (x)
        (=> x
          (if (evenp <>)
              (/ <> 2)
              (bail))
          1+))
      
      (halve-and-inc-maybe 3)
      => NIL
      
      (halve-and-inc-maybe 4)
      => 3
      ;;; `finish` examples can be derived by replacing `(bail)` with
      ;;; `(finish nil)` in the `bail` examples.

  • [macro] SET=> ARGUMENT &BODY BODY

    Thread the initial value of a place through transformations like in macro =>, and update the place with the final result.

    • Arguments

      • argument

        A setf-able place.

      • body

        Exactly the same as in =>. Supports :let, :call, :req, :cond, lambda(0 1), <>, bail, and finish.

        :req and (bail) will short-circuit such that the place will not be set. (finish [value]) will short-circuit and set the place to [value].

    • Return value

      Returns the value of its last form.

    • Side effects

      Updates PLACE to the calculated result.

    • Examples

      (defstruct box
        value)
      
      ;;; basic usage
      (let ((x 5))
        (set=> x
          (* <> 2)  ; => 10
          1+)  ; => 11
        x)
      => 11
      
      ;;; using `:let`
      (defun triangle! (box)
        (set=> (:let a (box-value box))
          1+
          (* <> a)
          (/ <> 2)))
      
      (let ((box (make-box :value 10)))
        (triangle! box)
        (box-value box))
      => 55
      
      ;;; using `:req`
      (defun maybe-only-even! (box)
        (set=> (box-value box)
          (:req (remove-if-not #'evenp
                               <>))))
      
      (let ((box (make-box :value '(1 2 3 4 5 6))))
        (maybe-only-even! box)
        (box-value box))
      => (2 4 6)
      
      (let ((box (make-box :value '(1 3 5))))
        (maybe-only-even! box)
        (box-value box))
      => (1 3 5)
      
      (defun maybe-halve! (box)
        (set=> (box-value box)
          (:req integerp (/ <> 2))))
      
      (let ((box (make-box :value 3)))
        (maybe-halve! box)
        (box-value box))
      => 3
      
      (let ((box (make-box :value 4)))
        (maybe-halve! box)
        (box-value box))
      => 2
      
      ;;; using `bail`
      (defun double-odd-inc!-or-zero (box)
        (set=> (box-value box)
          (if (oddp <>)
              (* <> 2)
              (bail 0))
          1+))
      
      (let ((box (make-box :value 2)))
        (values (double-odd-inc!-or-zero box)
                (box-value box)))
      => 0
      => 2
      
      (let ((box (make-box :value 3)))
        (values (double-odd-inc!-or-zero box)
                (box-value box)))
      => 7
      => 7
      
      ;;; using `finish`
      (defun double-odd-inc-or-zero! (box)
        (set=> (box-value box)
          (if (oddp <>)
              (* <> 2)
              (finish 0))
          1+))
      
      (let ((box (make-box :value 2)))
        (values (double-odd-inc-or-zero! box)
                (box-value box)))
      => 0
      => 0
      
      (let ((box (make-box :value 3)))
        (values (double-odd-inc-or-zero! box)
                (box-value box)))
      => 7
      => 7
      

  • [macro] LAMBDA=> &BODY BODY

    Returns a lambda taking one argument. The argument is threaded through the body like in =>. Supports all the special forms that => does.

    • Examples

      (funcall (lambda=> - exp 1+ /)
               0)
      => 0.5
      

  • [macro] DEF=> NAME &BODY BODY

    Defines a function named name taking one argument. The argument is threaded through the body like in =>. Supports all the special forms that => does.

    • Examples

      (def=> logistic-curve
        - exp 1+ /)
      
      (logistic-curve 0)
      => 0.5
      

  • [macro] LAMBDA+> (MAIN-ARG &REST ARGS) &BODY BODY

    Version of lambda=> with explicitly supplied arguments. More general since it can accept more than one argument. The first argument is the argument to the => call.

  • [macro] DEF+> NAME (MAIN-ARG &REST ARGS) &BODY BODY

    Version of def=> with explicitly supplied arguments. More general since it can accept more than one argument. The first argument is the argument to the => call.


[generated by MGL-PAX]

Dependencies (2)

  • metabang-bind
  • mgl-pax

Dependents (0)

    • GitHub
    • Quicklisp