Skip to content

myguidingstar-zz/synthread

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

98 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Syntax Threading Library

Extensions of Clojure's standard -> macro used to build code blocks that operate on a threaded topic.

Installation

SynThread is available from clojars. Add it to your project dependencies:

   [synthread "1.3.0"]

Usage

See unit tests for specific examples of each macro.

Some general guidelines:

  1. Require SynThread with the alias -> like this in Clojure:
   (ns your.ns.here
     (:require [lonocloud.synthread :as ->]))

or like this if you use Clojurescript:

   (ns your.ns.here
     (:require [lonocloud.synthread :as -> :include-macros true]))
  1. Always start a threaded block with Clojure's standard -> macro or ->/do (see next point). This clearly identifies the topic to be threaded.
   ;; good
   (-> {:first "John" :last "Smith"}
       (->/update :last first)) ;; replace last name with last initial

   ;; bad
   (->/update {:first "John" :last "Smith"}
             :last first) ;; replace last name with last initial
  1. Don't change the type (or shape) of the topic as it flows through. In our experience, the threading macros are used to either dig into a deep data structure massaging the topic the deeper it goes or are used to build a result by describing a pipeline of operations. The main difference between digging and building is that the type and shape of the threaded topic is changing or is constant respectively. We use synthread macros for building as a general rule. The ->/do macro will behave like -> but will also check that the topic is maintained through a threaded block.
   ;; good
   (-> {:a 1 :b 2}
     (->/update :a inc)
     (->/in [:b]
       (->/for [n (range 3)]
         (inc n))))
   ;; returns {:a 2 :b 5}

   ;; bad
   (-> {:a 1 :b 2}
     (->/update :a inc)
     :b ;; type changed from map to number
     inc)
   ;; returns 3
  1. Use ->/as to put the threaded value (or "topic") into a named local variable. This is useful when you need to call a function or macro that needs access to the topic in some parameter slot other than the first:
   (-> {:a 1 :b 2}
     (->/update :a inc)
     (->/as topic                             ;; label topic
       (->/when (> (:b topic) 10)
         (assoc :large-b true))))

Standard destructuring is supported by ->/as:

   (-> {:a 1 :b 2}
     (->/update :a inc)
     (->/as {:keys [b]}                       ;; destructure topic
       (->/when (> b 10)
         (assoc :large-b true))))

Additionally, a special destructuring form is supported allowing the use of functions. Passing a threaded form will implicity insert the topic at the front and use the last argument as the binding label. For example:

   (-> {:a 1 :b 2}
     (->/as (-> vals (->/apply max) max-val)  ;; use functions on topic
       (->/when (> max-val 10)
         (assoc :large-val true))))
  1. Clojure's do and doto macros are useful in these threading contexts, so don't be afraid to use them. do lets you stop threading, and yet pass a result to the next threaded step:
   (-> {:a 1 :b 2}
     (->/update :a inc)
     (->/when we-should-reset?
       (do {:a 0 :b 0}))  ;; see also ->/reset function
     (->/update :b inc))

This can be particularly useful in conjunction with ->/as:

   (-> {10 :orig, 20 :orig}
     (->/as topic
       (do
         (reduce #(assoc %1 %2 :default) topic (range 5)))))

On the other hand, doto is nice when you do not want to pass a result to the next step:

   (-> {:a 1 :b 2}
     (doto prn)
     (->/update :a inc))

The debugging prn above works, but the patten rapidly becomes awkward if you want to provide a label to the prn. That would actually require a combination of ->/as to label it and do to prevent the topic from being printed before the label because of threading. This is exactly the purpose of ->/aside:

   (-> {:a 1 :b 2}
     (->/aside topic        ;; note the body is entirely unthreaded
       (prn :topic topic)
       (println "Note: b is currently" (:b topic))) ;; return value is ignored
     (->/update :a inc))
  1. In addition to the threading macros, two helper functions are also available: ->/reset and ->/apply. Use ->/reset to set the value of the threaded topic (similar to Clojure's reset!) and use ->/apply to call a function with the threaded topic as its first argument (similar to Clojure's apply).
   ;; example of ->/reset
   (-> false
     (->/reset true))
   ;; returns true

   ;; example of ->/apply
   (-> [0]
     (->/apply conj [1 2 3])  ;; => (conj [0] 1 2 3)
     (->/apply [conj 4 5 6])) ;; also works!
   ;; returns [0 1 2 3 4 5 6]
  1. Some of the macros are marked EXPERIMENTAL to reflect the fact that they have seen little or no use in our live code.

Related work

This is not the first library to have sailed these waters. Some that we were aware of during the design of SynThread include:

Example walkthrough

http://www.infoq.com/presentations/Macros-Monads

Copyright

© 2013-2014 LonoCloud. © 2015 Hoang Minh Thang.

All rights reserved. The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in the file epl-v10.html at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.

About

Syntax threading macros for Clojure

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Clojure 68.9%
  • HTML 31.1%