her.esy.fun/src/Scratch/en/blog/Higher-order-function-in-zsh/index.html
Yann Esposito (Yogsototh) 03610908ce
Old site match new style
2021-05-25 22:25:47 +02:00

255 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>YBlog - Higher order function in zsh</title>
<meta name="keywords" content="zsh, map, foldr, filter, functional, programming, higher order functions" />
<link rel="shortcut icon" type="image/x-icon" href="../../../../Scratch/img/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/css/y.css" />
<link rel="stylesheet" type="text/css" href="/css/legacy.css" />
<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="apple-touch-icon" href="../../../../Scratch/img/about/FlatAvatar@2x.png" />
<!--[if lt IE 9]>
<script src="http://ie7-js.googlecode.com/svn/version/2.1(beta4)/IE9.js"></script>
<![endif]-->
<!-- IndieAuth -->
<link href="https://twitter.com/yogsototh" rel="me">
<link href="https://github.com/yogsototh" rel="me">
<link href="mailto:yann.esposito@gmail.com" rel="me">
<link rel="pgpkey" href="../../../../pubkey.txt">
</head>
<body lang="en" class="article">
<div id="content">
<div id="header">
<div id="choix">
<span id="choixlang">
<a href="../../../../Scratch/fr/blog/Higher-order-function-in-zsh/">French</a>
</span>
<span class="tomenu"><a href="#navigation">↓ Menu ↓</a></span>
<span class="flush"></span>
</div>
</div>
<div id="titre">
<h1>Higher order function in zsh</h1>
</div>
<div class="flush"></div>
<div id="afterheader" class="article">
<div class="corps">
<div>
<img src="../../../../Scratch/img/blog/Higher-order-function-in-zsh/main.jpg" alt="Title image" />
</div>
<div class="intro">
<p>UPDATE: <a href="http://nicholassterling.wordpress.com/2012/03/30/a-zsh-map-function/">Nicholas Sterling had discovered a way to implement anonymous functions</a> Thanks!</p>
<p>With this last version you should use <code>map</code> if you use external function. <code>mapl</code> to use lambda function. And <code>mapa</code> for arithmetic operations.</p>
<p>Example:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode zsh"><code class="sourceCode zsh"><a class="sourceLine" id="cb1-1" title="1">$ filterl <span class="st">'echo $1|grep a &gt;/dev/null'</span> ab <span class="kw">cd</span> ef ada</a>
<a class="sourceLine" id="cb1-2" title="2">ab</a>
<a class="sourceLine" id="cb1-3" title="3">ada</a>
<a class="sourceLine" id="cb1-4" title="4"></a>
<a class="sourceLine" id="cb1-5" title="5">$ folda <span class="st">'$1+$2'</span> <span class="dt">{1..5}</span></a>
<a class="sourceLine" id="cb1-6" title="6">15</a>
<a class="sourceLine" id="cb1-7" title="7"></a>
<a class="sourceLine" id="cb1-8" title="8">$ folda <span class="st">'$1*$2'</span> <span class="dt">{1..20}</span></a>
<a class="sourceLine" id="cb1-9" title="9">2432902008176640000</a>
<a class="sourceLine" id="cb1-10" title="10"></a>
<a class="sourceLine" id="cb1-11" title="11">$ mapl <span class="st">'echo X $1:t Y'</span> ~/.zsh/functional/src/*</a>
<a class="sourceLine" id="cb1-12" title="12">X each Y</a>
<a class="sourceLine" id="cb1-13" title="13">X filter Y</a>
<a class="sourceLine" id="cb1-14" title="14">X fold Y</a>
<a class="sourceLine" id="cb1-15" title="15">X map Y</a>
<a class="sourceLine" id="cb1-16" title="16"></a>
<a class="sourceLine" id="cb1-17" title="17">$ mapa <span class="st">'$1*2'</span> <span class="dt">{1..3}</span></a>
<a class="sourceLine" id="cb1-18" title="18">2</a>
<a class="sourceLine" id="cb1-19" title="19">4</a>
<a class="sourceLine" id="cb1-20" title="20">6</a>
<a class="sourceLine" id="cb1-21" title="21"></a>
<a class="sourceLine" id="cb1-22" title="22">$ mapl <span class="st">'echo result $1'</span> <span class="ot">$(</span>mapa <span class="st">'$1+5'</span> <span class="ot">$(</span>mapa <span class="st">'$1*2'</span> <span class="dt">{1..3}</span><span class="ot">))</span></a>
<a class="sourceLine" id="cb1-23" title="23">result 7</a>
<a class="sourceLine" id="cb1-24" title="24">result 9</a>
<a class="sourceLine" id="cb1-25" title="25">result 11</a></code></pre></div>
<p><span class="sc"><abbr title="Too long; didn't read">tl;dr</abbr>: </span> some simple implementation of higher order function for zsh.</p>
</div>
<p>Why is it important to have these functions? Simply because, the more I programmed with zsh the more I tended to work using functional programming style.</p>
<p>The minimal to have better code are the functions <code>map</code>, <code>filter</code> and <code>fold</code>.</p>
<p>Lets compare. First a program which convert all gif to png in many different directories of different projects.</p>
<p>Before ⇒</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode zsh"><code class="sourceCode zsh"><a class="sourceLine" id="cb2-1" title="1"><span class="co"># for each directory in projects dir</span></a>
<a class="sourceLine" id="cb2-2" title="2"><span class="kw">for</span> toProject <span class="kw">in</span> /path/to/projects/*<span class="kw">(</span>/N<span class="kw">)</span>; <span class="kw">do</span></a>
<a class="sourceLine" id="cb2-3" title="3"> <span class="co"># toProject is /path/to/projects/foo</span></a>
<a class="sourceLine" id="cb2-4" title="4"> <span class="co"># project become foo (:t for tail)</span></a>
<a class="sourceLine" id="cb2-5" title="5"> <span class="ot">project=${toProject:t}</span></a>
<a class="sourceLine" id="cb2-6" title="6"> <span class="kw">for</span> toResource <span class="kw">in</span> <span class="ot">$toProject</span>/resources/*.gif<span class="kw">(</span>.N<span class="kw">)</span>; <span class="kw">do</span></a>
<a class="sourceLine" id="cb2-7" title="7"> convert <span class="ot">$toResource</span> <span class="ot">${toResource:r}</span>.png <span class="kw">&amp;&amp;</span> <span class="kw">\</span></a>
<a class="sourceLine" id="cb2-8" title="8"> \<span class="kw">rm</span> -f <span class="ot">$toResource</span></a>
<a class="sourceLine" id="cb2-9" title="9"> <span class="kw">done</span></a>
<a class="sourceLine" id="cb2-10" title="10"><span class="kw">done</span></a></code></pre></div>
<ul>
<li>The <code>(/N)</code> means to select only directory and not to crash if there isnt any.</li>
<li>The <code>(.N)</code> means to select only files and not to crash if there isnt any.</li>
<li>The <code>:t</code> means tail; if <code>toto=/path/to/file.ext</code> then <code>${toto:t}=file.ext</code>.</li>
</ul>
<p>After ⇒</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><a class="sourceLine" id="cb3-1" title="1"><span class="fu">gif_to_png()</span> <span class="kw">{</span> <span class="ex">convert</span> <span class="va">$1</span> <span class="va">${1:r}</span>.png <span class="kw">&amp;&amp;</span> \<span class="fu">rm</span> -f <span class="va">$1</span> <span class="kw">}</span></a>
<a class="sourceLine" id="cb3-2" title="2"></a>
<a class="sourceLine" id="cb3-3" title="3"><span class="fu">handle_resources()</span> <span class="kw">{</span> <span class="ex">map</span> gif_to_png <span class="va">$1</span>/resources/*.gif(.N) <span class="kw">}</span></a>
<a class="sourceLine" id="cb3-4" title="4"></a>
<a class="sourceLine" id="cb3-5" title="5"><span class="ex">map</span> handle_resources /path/to/projects/*(/N)</a></code></pre></div>
<p>No more bloc! It might be a little bit harder to read if youre not used to functional programming notation. But it is more concise and robusts.</p>
<p>Another example with some tests.</p>
<p>Find all files in project not containing an <code>s</code> which their name contains their project name:</p>
<p>Before ⇒</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode zsh"><code class="sourceCode zsh"><a class="sourceLine" id="cb4-1" title="1"><span class="kw">for</span> toProject <span class="kw">in</span> Projects/*; <span class="kw">do</span></a>
<a class="sourceLine" id="cb4-2" title="2"> <span class="ot">project=$toProject</span>:t</a>
<a class="sourceLine" id="cb4-3" title="3"> <span class="kw">if</span> <span class="kw">print</span> -- project <span class="kw">|</span> <span class="kw">grep</span> -v s <span class="kw">&gt;</span>/dev/null</a>
<a class="sourceLine" id="cb4-4" title="4"> <span class="kw">then</span></a>
<a class="sourceLine" id="cb4-5" title="5"> <span class="kw">print</span> <span class="ot">$project</span></a>
<a class="sourceLine" id="cb4-6" title="6"> <span class="kw">for</span> toResource <span class="kw">in</span> <span class="ot">$toProject</span>/*<span class="kw">(</span>.N<span class="kw">)</span>; <span class="kw">do</span></a>
<a class="sourceLine" id="cb4-7" title="7"> <span class="kw">if</span> <span class="kw">print</span> -- <span class="ot">${toResource:t}</span> <span class="kw">|</span> <span class="kw">grep</span> <span class="ot">$project</span> <span class="kw">&gt;</span>/dev/null; <span class="kw">then</span></a>
<a class="sourceLine" id="cb4-8" title="8"> <span class="kw">print</span> -- <span class="st">&quot;X </span><span class="ot">$toResource</span><span class="st">&quot;</span></a>
<a class="sourceLine" id="cb4-9" title="9"> <span class="kw">fi</span></a>
<a class="sourceLine" id="cb4-10" title="10"> <span class="kw">done</span></a>
<a class="sourceLine" id="cb4-11" title="11"> <span class="kw">fi</span></a>
<a class="sourceLine" id="cb4-12" title="12"><span class="kw">done</span></a></code></pre></div>
<p>After ⇒</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode zsh"><code class="sourceCode zsh"><a class="sourceLine" id="cb5-1" title="1"><span class="fu">contain_no_s()</span> <span class="kw">{</span> <span class="kw">print</span> <span class="ot">$1</span> <span class="kw">|</span> <span class="kw">grep</span> -v s <span class="kw">}</span></a>
<a class="sourceLine" id="cb5-2" title="2"></a>
<a class="sourceLine" id="cb5-3" title="3"><span class="kw">function</span><span class="fu"> verify_file_name</span> <span class="kw">{</span> </a>
<a class="sourceLine" id="cb5-4" title="4"> <span class="kw">local</span> <span class="ot">project=$1</span>:t</a>
<a class="sourceLine" id="cb5-5" title="5"> <span class="fu">contains_project_name()</span> <span class="kw">{</span> <span class="kw">print</span> <span class="ot">$1</span>:t <span class="kw">|</span> <span class="kw">grep</span> <span class="ot">$project</span> <span class="kw">}</span></a>
<a class="sourceLine" id="cb5-6" title="6"> map <span class="st">&quot;print -- X&quot;</span> <span class="ot">$(</span>filter contains_project_name <span class="ot">$1</span>/*<span class="kw">(</span>.N<span class="kw">)</span><span class="ot">)</span></a>
<a class="sourceLine" id="cb5-7" title="7"><span class="kw">}</span></a>
<a class="sourceLine" id="cb5-8" title="8"></a>
<a class="sourceLine" id="cb5-9" title="9">map verify_file_name <span class="ot">$(</span> filter contain_no_s Projects/* <span class="ot">)</span></a></code></pre></div>
<p>Also, the first verstion is a bit easier to read. But the second one is clearly far superior in architecture. I dont want to argue why here. Just believe me that the functional programming approach is superior.</p>
<p>You can find an <a href="https://github.com/Tarrasch/zsh-functional">updated version of the code (thanks to Arash Rouhani)</a>. An older version is <a href="https://github.com/yogsototh/zsh_functional">here thought</a>. Here is the (first version) source code:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode zsh"><code class="sourceCode zsh"><a class="sourceLine" id="cb6-1" title="1"><span class="co">#!/usr/bin/env zsh</span></a>
<a class="sourceLine" id="cb6-2" title="2"></a>
<a class="sourceLine" id="cb6-3" title="3"><span class="co"># Provide higer-order functions </span></a>
<a class="sourceLine" id="cb6-4" title="4"></a>
<a class="sourceLine" id="cb6-5" title="5"><span class="co"># usage:</span></a>
<a class="sourceLine" id="cb6-6" title="6"><span class="co">#</span></a>
<a class="sourceLine" id="cb6-7" title="7"><span class="co"># $ foo(){print &quot;x: $1&quot;}</span></a>
<a class="sourceLine" id="cb6-8" title="8"><span class="co"># $ map foo a b c d</span></a>
<a class="sourceLine" id="cb6-9" title="9"><span class="co"># x: a</span></a>
<a class="sourceLine" id="cb6-10" title="10"><span class="co"># x: b</span></a>
<a class="sourceLine" id="cb6-11" title="11"><span class="co"># x: c</span></a>
<a class="sourceLine" id="cb6-12" title="12"><span class="co"># x: d</span></a>
<a class="sourceLine" id="cb6-13" title="13"><span class="kw">function</span><span class="fu"> map</span> <span class="kw">{</span></a>
<a class="sourceLine" id="cb6-14" title="14"> <span class="kw">local</span> <span class="ot">func_name=$1</span></a>
<a class="sourceLine" id="cb6-15" title="15"> <span class="kw">shift</span></a>
<a class="sourceLine" id="cb6-16" title="16"> <span class="kw">for</span> elem <span class="kw">in</span> <span class="ot">$@</span>; <span class="kw">print</span> -- <span class="ot">$(</span><span class="kw">eval</span> <span class="ot">$func_name</span> <span class="ot">$elem)</span></a>
<a class="sourceLine" id="cb6-17" title="17"><span class="kw">}</span></a>
<a class="sourceLine" id="cb6-18" title="18"></a>
<a class="sourceLine" id="cb6-19" title="19"><span class="co"># $ bar() { print $(($1 + $2)) }</span></a>
<a class="sourceLine" id="cb6-20" title="20"><span class="co"># $ fold bar 0 1 2 3 4 5</span></a>
<a class="sourceLine" id="cb6-21" title="21"><span class="co"># 15</span></a>
<a class="sourceLine" id="cb6-22" title="22"><span class="co"># -- but also</span></a>
<a class="sourceLine" id="cb6-23" title="23"><span class="co"># $ fold bar 0 $( seq 1 100 )</span></a>
<a class="sourceLine" id="cb6-24" title="24"><span class="kw">function</span><span class="fu"> fold</span> <span class="kw">{</span></a>
<a class="sourceLine" id="cb6-25" title="25"> <span class="kw">if</span> <span class="kw">((</span><span class="ot">$#</span>&lt;2<span class="kw">))</span> <span class="kw">{</span></a>
<a class="sourceLine" id="cb6-26" title="26"> <span class="kw">print</span> -- <span class="st">&quot;ERROR fold use at least 2 arguments&quot;</span> <span class="kw">&gt;&amp;2</span></a>
<a class="sourceLine" id="cb6-27" title="27"> <span class="kw">return</span> 1</a>
<a class="sourceLine" id="cb6-28" title="28"> <span class="kw">}</span></a>
<a class="sourceLine" id="cb6-29" title="29"> <span class="kw">if</span> <span class="kw">((</span><span class="ot">$#</span>&lt;3<span class="kw">))</span> <span class="kw">{</span></a>
<a class="sourceLine" id="cb6-30" title="30"> <span class="kw">print</span> -- <span class="ot">$2</span></a>
<a class="sourceLine" id="cb6-31" title="31"> <span class="kw">return</span> 0</a>
<a class="sourceLine" id="cb6-32" title="32"> <span class="kw">}</span> <span class="kw">else</span> <span class="kw">{</span></a>
<a class="sourceLine" id="cb6-33" title="33"> <span class="kw">local</span> <span class="ot">acc</span></a>
<a class="sourceLine" id="cb6-34" title="34"> <span class="kw">local</span> <span class="ot">right</span></a>
<a class="sourceLine" id="cb6-35" title="35"> <span class="kw">local</span> <span class="ot">func_name=$1</span></a>
<a class="sourceLine" id="cb6-36" title="36"> <span class="kw">local</span> <span class="ot">init_value=$2</span></a>
<a class="sourceLine" id="cb6-37" title="37"> <span class="kw">local</span> <span class="ot">first_value=$3</span></a>
<a class="sourceLine" id="cb6-38" title="38"> <span class="kw">shift</span> 3</a>
<a class="sourceLine" id="cb6-39" title="39"> <span class="ot">right=$(</span> fold <span class="ot">$func_name</span> <span class="ot">$init_value</span> <span class="ot">$@</span> <span class="ot">)</span></a>
<a class="sourceLine" id="cb6-40" title="40"> <span class="ot">acc=$(</span> <span class="kw">eval</span> <span class="st">&quot;</span><span class="ot">$func_name</span><span class="st"> </span><span class="ot">$first_value</span><span class="st"> </span><span class="ot">$right</span><span class="st">&quot;</span> <span class="ot">)</span></a>
<a class="sourceLine" id="cb6-41" title="41"> <span class="kw">print</span> -- <span class="ot">$acc</span></a>
<a class="sourceLine" id="cb6-42" title="42"> <span class="kw">return</span> 0</a>
<a class="sourceLine" id="cb6-43" title="43"> <span class="kw">}</span></a>
<a class="sourceLine" id="cb6-44" title="44"><span class="kw">}</span></a>
<a class="sourceLine" id="cb6-45" title="45"></a>
<a class="sourceLine" id="cb6-46" title="46"><span class="co"># usage:</span></a>
<a class="sourceLine" id="cb6-47" title="47"><span class="co">#</span></a>
<a class="sourceLine" id="cb6-48" title="48"><span class="co"># $ baz() { print $1 | grep baz }</span></a>
<a class="sourceLine" id="cb6-49" title="49"><span class="co"># $ filter baz titi bazaar biz</span></a>
<a class="sourceLine" id="cb6-50" title="50"><span class="co"># bazaar</span></a>
<a class="sourceLine" id="cb6-51" title="51"><span class="kw">function</span><span class="fu"> filter</span> <span class="kw">{</span></a>
<a class="sourceLine" id="cb6-52" title="52"> <span class="kw">local</span> <span class="ot">predicate=$1</span></a>
<a class="sourceLine" id="cb6-53" title="53"> <span class="kw">local</span> <span class="ot">result</span></a>
<a class="sourceLine" id="cb6-54" title="54"> <span class="kw">typeset</span> -a <span class="ot">result</span></a>
<a class="sourceLine" id="cb6-55" title="55"> <span class="kw">shift</span></a>
<a class="sourceLine" id="cb6-56" title="56"> <span class="kw">for</span> elem <span class="kw">in</span> <span class="ot">$@</span>; <span class="kw">do</span></a>
<a class="sourceLine" id="cb6-57" title="57"> <span class="kw">if</span> <span class="kw">eval</span> <span class="ot">$predicate</span> <span class="ot">$elem</span> <span class="kw">&gt;</span>/dev/null; <span class="kw">then</span></a>
<a class="sourceLine" id="cb6-58" title="58"> <span class="ot">result=(</span> <span class="ot">$result</span> <span class="ot">$elem</span> <span class="ot">)</span></a>
<a class="sourceLine" id="cb6-59" title="59"> <span class="kw">fi</span></a>
<a class="sourceLine" id="cb6-60" title="60"> <span class="kw">done</span></a>
<a class="sourceLine" id="cb6-61" title="61"> <span class="kw">print</span> <span class="ot">$result</span></a>
<a class="sourceLine" id="cb6-62" title="62"><span class="kw">}</span></a></code></pre></div>
</div>
<div id="afterarticle">
<div id="social">
<a href="/rss.xml" target="_blank" rel="noopener noreferrer nofollow" class="social">RSS</a>
·
<a href="https://twitter.com/home?status=http%3A%2F%2Fyannesposito.com/Scratch/en/blog/Higher-order-function-in-zsh/%20via%20@yogsototh" target="_blank" rel="noopener noreferrer nofollow" class="social">Tweet</a>
·
<a href="http://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fyannesposito.com/Scratch/en/blog/Higher-order-function-in-zsh/" target="_blank" rel="noopener noreferrer nofollow" class="social">FB</a>
<br />
<a class="message" href="../../../../Scratch/en/blog/Social-link-the-right-way/">These social sharing links preserve your privacy</a>
</div>
<div id="navigation">
<a href="../../../../">Home</a>
<span class="sep">¦</span>
<a href="../../../../Scratch/en/blog">Blog</a>
<span class="sep">¦</span>
<a href="../../../../Scratch/en/softwares">Softwares</a>
<span class="sep">¦</span>
<a href="../../../../Scratch/en/about">About</a>
</div>
<div id="totop"><a href="#header">↑ Top ↑</a></div>
<div id="bottom">
<div>
Published on 2011-09-28
</div>
<div>
<a href="https://twitter.com/yogsototh">Follow @yogsototh</a>
</div>
<div>
<a rel="license" href="http://creativecommons.org/licenses/by/3.0/deed.en_US">Yann Esposito©</a>
</div>
<div>
Done with
<a href="http://www.vim.org" target="_blank" rel="noopener noreferrer nofollow"><strike>Vim</strike></a>
<a href="http://spacemacs.org" target="_blank" rel="noopener noreferrer nofollow">spacemacs</a>
<span class="pala">&amp;</span>
<a href="http://nanoc.ws" target="_blank" rel="noopener noreferrer nofollow"><strike>nanoc</strike></a>
<a href="http://jaspervdj.be/hakyll" target="_blank" rel="noopener noreferrer nofollow">Hakyll</a>
</div>
<hr />
<div style="max-width: 100%">
<a href="https://cardanohub.org">
<img src="../../../../Scratch/img/ada-logo.png" class="simple" style="height: 16px;
border-radius: 50%;
vertical-align:middle;
display:inline-block;" />
ADA:
</a>
<code style="display:inline-block;
word-wrap:break-word;
text-align: left;
vertical-align: top;
max-width: 85%;">
DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp
</code>
</div>
</div>
</div>
</div>
</div>
</body>
</html>