Product Promotion
0x5a.live
for different kinds of informations and explorations.
GitHub - noprompt/meander: Tools for transparent data transformation
Tools for transparent data transformation. Contribute to noprompt/meander development by creating an account on GitHub.
Visit SiteGitHub - noprompt/meander: Tools for transparent data transformation
Tools for transparent data transformation. Contribute to noprompt/meander development by creating an account on GitHub.
Powered by 0x5a.live ๐
Meanderฮต
Meander is a Clojure/ClojureScript library that empowers you to write transparent data transformation code that allows you to plainly see the input and output of these transformations. The latest version of the library can be found at the following link.
What can Meander do?
(require '[meander.epsilon :as m])
(defn favorite-food-info [foods-by-name user]
(m/match {:user user
:foods-by-name foods-by-name}
{:user
{:name ?name
:favorite-food {:name ?food}}
:foods-by-name {?food {:popularity ?popularity
:calories ?calories}}}
{:name ?name
:favorite {:food ?food
:popularity ?popularity
:calories ?calories}}))
Meander's match
macro allows us to pattern match on a data structure and return the answer that matches our pattern.
We use logic variables (symbols that start with ?
) to extract values from our input and return them in our output.
Logic variables also let us join across values.
In this example, we do that using the ?food
variable to lookup our users' favorite foods in the foods-by-name
collection.
Here is an example of running this match with some test data foods-by-name
.
(def foods-by-name
{:nachos {:popularity :high
:calories :lots}
:smoothie {:popularity :high
:calories :less}})
(favorite-food-info foods-by-name
{:name :alice
:favorite-food {:name :nachos}})
;; =>
{:name :alice
:favorite {:food :nachos
:popularity :high
:calories :lots}}
Finding More than One Answer
What if instead of a user having one favorite food, they had many?
And suppose we wanted to return the information for all of them.
The search
macro performs exactly this job.
(defn favorite-foods-info [foods-by-name user]
(m/search {:user user
:foods-by-name foods-by-name}
{:user
{:name ?name
:favorite-foods (m/scan {:name ?food})}
:foods-by-name {?food {:popularity ?popularity
:calories ?calories}}}
{:name ?name
:favorite {:food ?food
:popularity ?popularity
:calories ?calories}}))
There is actually very little that is different here.
Some names have been pluralized, line 2 changed to use search
instead of match
, and on line 6 added we're using the scan
pattern matching operator.
That is all we need to find all of a users favorite foods and look up the information about them.
Lets try it with our test data from the previous example.
(favorite-foods-info foods-by-name
{:name :alice
:favorite-foods [{:name :nachos}
{:name :smoothie}]})
;; =>
({:name :alice
:favorite {:food :nachos
:popularity :high
:calories :lots}}
{:name :alice
:favorite {:food :smoothie
:popularity :high
:calories :less}})
Remembering Values
Let's shift gears. What if a user has all sorts of different foods scattered through out their information and we want to collect them all? Here we can use memory variables.
(defn grab-all-foods [user]
(m/find user
{:favorite-foods [{:name !foods} ...]
:special-food !foods
:recipes [{:title !foods} ...]
:meal-plan {:breakfast [{:food !foods} ...]
:lunch [{:food !foods} ...]
:dinner [{:food !foods} ...]}}
!foods))
This code example is a little contrived, but it does immediately show you how you can grab values from all sorts of places in your data structure.
This combination of a !memory-variable
and the zero or more postfix operator ...
is a fairly common one.
Using them together allows you to gather up many values.
If you want to ensure that a certain number of elements exist you can also use the n or more postfix operator i.e. ..2
.
Conditional Matches
Stepping away from food examples, we can see a few of Meanders more traditional pattern matching abilities.
Imagine that we have coordinates that can either include be [x y]
or [x y z]
and we want a pattern that extracts y
.
(def point [1 2])
(m/find point
[?x ?y] ?y
[?x ?y ?z] ?y)
;; => 2
Here we used find
to check against multiple patterns.
Meander checks this in a top to bottom ordering.
One thing to note is that since we didn't use ?x
or ?z
we could have replaced them with a wildcard match, a simple symbol which starts with _
.
The above pattern accomplishes the task, but imagine that for some reason people keep passing things that aren't numbers into our match, so we want to restrict our matches to only numbers.
(m/find point
[(m/pred number?) (m/pred number? ?y)]
?y
[(m/pred number?) (m/pred number? ?y) (m/pred number?)]
?y)
This ensures things that aren't number fail to match, but is a little verbose. Honestly, that isn't a problem. Length is not the measure of good code, clarity is. But just to see how Meander allows us to build our own extensions let's look at how we can shorten things up.
(m/defsyntax number
([] `(number _#))
([pattern]
(if (m/match-syntax? &env)
`(m/pred number? ~pattern)
&form)))
(m/find point
[(number) (number ?y)]
?y
[(number) (number ?y) (number)]
?y)
Here we use defsyntax
to essentially build our own macros for Meander.
To learn more check out the Syntax Extensions article.
Gaining Control
Sometimes we have a multistep process where we want to transform nested data in place. This is an area that Meander continues to explore, but one powerful way of solving this problem is using Meander's strategies.
(require '[meander.strategy.epsilon :as m*])
(def eliminate-zeros
(m*/rewrite
(+ ?x 0) ?x
(+ 0 ?x) ?x))
(def eliminate-all-zeros
(m*/bottom-up
(m*/attempt eliminate-zeros)))
(eliminate-all-zeros '(+ (+ 0 (+ 0 (+ 3 (+ 2 0)))) 0))
;; => (+ 3 2)
Using our strategies we can make rewrite rules and then say how they ought to be applied. Here we use the attempt strategy, which just says if the match fails, then return whatever was passed in. And the bottom up strategies which applies our match to the most deep value, and replaces values that match all the way up the tree. To learn more checkout how to apply meander strategies
Documentation
- Concepts and API documentation covers the concepts, operators and primitives of Meander.
- Cookbook has commonly encountered situations and solution snippets. Contributions welcome.
- Examples contains larger examples.
- Tests are a good way to see the detailed behaviors of primitives.
Blog Posts
- Meander: The answer to map fatigue
- Meander for Practical Data Transformation
- Introduction to Term Rewriting with Meander
- Building Meander in Meander
Talks
Community
If you have any questions don't hesitate to ask them in Clojurians Slack #meander or file an issue for this project. We are happy to help.
Project Status
Meander is a young, active, and ambitious project. Unless there is a reason to surrender, the project will continue to be regularly improved.
Release schedule
Prompt releases can be expected when bugs are fixed or there are significant performance enchancements.
Versioning semantics
This project uses an unorthodox method of versioning in that any change that could break compatibility with the current meander/artifactID
must occur at a new meander/artifactID
.
For instance, a change in syntax could cause matches to now fail.
In this case we do not want a new version of the software i.e. we do not wish to go from 0.0.N
to 1.N.N
etc.
Instead we create a new meander/artifactID
update the namespaces accordingly and continue to progress from there.
This might sound strange at first but this method allows the project to progress in a way that is much more free than a traditional approach with semantic versioning with the following advantages:
- A version of a
meander/artifactID
will always be safe to upgrade. - When a new
meander/artifactID
is created, you can depend on both the new and the old libraries without conflict when or if you decide to transition. - It promotes project growth because it gives the library the ability grow and change without the fear of breaking users.
Like anything, there are drawbacks to this approach, however, as the project matures it should stabilize and be more like a "regular" project.
Made with โค๏ธ
to provide different kinds of informations and resources.