{"id":137,"date":"2015-11-27T00:40:09","date_gmt":"2015-11-27T00:40:09","guid":{"rendered":"http:\/\/procyonic.org\/blog\/?p=137"},"modified":"2015-11-27T00:40:09","modified_gmt":"2015-11-27T00:40:09","slug":"aping-js-verb-rank-in-puff","status":"publish","type":"post","link":"https:\/\/procyonic.org\/blog\/aping-js-verb-rank-in-puff\/","title":{"rendered":"Aping J&#8217;s Verb Rank in Puff"},"content":{"rendered":"<p>This blog post will sketch out some thoughts relating to <a href=\"https:\/\/github.com\/VincentToups\/puff\">Puff<\/a>, a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Function-level_programming\">function level programming language<\/a> I am embedding in Javascript and <a href=\"http:\/\/www.jsoftware.com\/\">J<\/a>&#8216;s notion of operator <a href=\"http:\/\/www.jsoftware.com\/help\/jforc\/loopless_code_i_verbs_have_r.htm\">rank<\/a>.<\/p>\n<p>Rank as it pertains to <em>nouns<\/em> in <em>J<\/em> is fairly easy to understand: it is just the number of dimensions of an array. Scalars, like <code>10<\/code>, have rank 0, the empty value (denotable as <code>0 $ 0<\/code>) has rank 0, a simple vector (<code>0 0 0<\/code>) has rank 1, a 2d matrix rank 2, etc.<\/p>\n<p>But <em>J<\/em> also has rank for <em>verbs<\/em>. Consider the verb <code>+<\/code>.<\/p>\n<pre><code>(1 2 3) + (4 5 6)\r\n-&gt; 5 7 9\r\n<\/code><\/pre>\n<p>(For <em>J<\/em> tyros: <code>+<\/code> is called a <em>verb<\/em> in <em>J<\/em> and furthermore we use it in its <em>dyadic<\/em> sense, which is to say we pass it arguments on the left and the right.)<\/p>\n<p>Informally we understand from this that in <em>J<\/em> <code>+<\/code> operates element-wise on both its left and right operands. This means its left and right rank are both <em>zero<\/em> and it operates, then, on the rank <em>zero<\/em> elements of its arguments: the individual scalar values.<\/p>\n<p>But there is more to the story. As a side note, We can denote multi-dimensional arrays in <em>J<\/em> like so:<\/p>\n<pre><code>]example =. 2 3 $ 1 2 3 4 5 6 \r\n1 2 3\r\n4 5 6\r\n<\/code><\/pre>\n<p>(For the curious, that is &#8220;change the shape (<code>$<\/code>) for array <code>1 2 3 4 5 6<\/code> so that it is <code>2 3<\/code>)<\/p>\n<p><em>J<\/em> has a <em>box<\/em> operator which is handy for demonstrating rank issues. It is denoted <code>&lt;<\/code> and wraps any value into a box, which is a special scalar value type which holds something else.<\/p>\n<pre><code>&lt;1\r\n\u250c\u2500\u2510\r\n\u25021\u2502\r\n\u2514\u2500\u2518\r\n<\/code><\/pre>\n<p>Operators in <em>J<\/em> have rank and the rank of <code>&lt;<\/code> is infinite. This means that it always operates on its argument <em>as a whole<\/em>.<\/p>\n<pre><code>&lt;(1 2 3 4 5 6)\r\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n\u25021 2 3 4 5 6\u2502\r\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\r\n<\/code><\/pre>\n<p>But the smart thing about <em>J<\/em> is that you can modify verbs with <em>adverbs<\/em> one of which returns a new verb with a different rank. See if you can guess what all this means:<\/p>\n<pre><code>&lt;\"0(1 2 3 4 5 6)\r\n\u250c\u2500\u252c\u2500\u252c\u2500\u252c\u2500\u252c\u2500\u252c\u2500\u2510\r\n\u25021\u25022\u25023\u25024\u25025\u25026\u2502\r\n\u2514\u2500\u2534\u2500\u2534\u2500\u2534\u2500\u2534\u2500\u2534\u2500\u2518\r\n<\/code><\/pre>\n<p>The array denotation <code>1 2 3 4 5 6<\/code> is the same as before, but now we have written <code>&lt;\"0<\/code> instead of <code>&lt;<\/code>. <code>\"<\/code> is an adverb which modified its right hand arguments&#8217; rank such that it is the left hand value. The result of<code>&lt;\"0<\/code> then is a verb with the same meaning as <code>&lt;<\/code> except that it has rank 0. Verbs with rank 0 operate on the smallest possible cells of the array, so that<\/p>\n<pre><code>&lt;\"0(3 2 $ 1 2 3 4 5 6)\r\n\u250c\u2500\u252c\u2500\u2510\r\n\u25021\u25022\u2502\r\n\u251c\u2500\u253c\u2500\u2524\r\n\u25023\u25024\u2502\r\n\u251c\u2500\u253c\u2500\u2524\r\n\u25025\u25026\u2502\r\n\u2514\u2500\u2534\u2500\u2518\r\n<\/code><\/pre>\n<p>each element of the input is boxed regardless of the incoming arrays shape or rank.<\/p>\n<p>If we use a different rank:<\/p>\n<pre><code>&lt;\"1(3 2 $ 1 2 3 4 5 6)\r\n\u250c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2510\r\n\u25021 2\u25023 4\u25025 6\u2502\r\n\u2514\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2518\r\n<\/code><\/pre>\n<p>We get a different result. One-ranked verbs operate 1-cells (that is, the elements of rank 1) of the incoming array, in this case the arrays <code>1 2<\/code>, <code>3 4<\/code>, and <code>5 6<\/code>.<\/p>\n<p>The rules work for dyadic values too &#8211; each argument of the verb has a rank (a right rank and a left rank) which determines how the underlying logic represented by the verb is used to join elements from the right and left arguments.<\/p>\n<p>By modifying verb rank you can custom tailor your iteration through argument arrays and avoid most explicit looping.<\/p>\n<h2>Puff<\/h2>\n<p><em>Puff<\/em> is mostly aping the <em>function level<\/em> semantics of <em>J<\/em> but we can analogize verb rank too. Consider the Puff function <code>map<\/code>, which has a single argument meaning:<\/p>\n<pre><code>var plus1 = _p(plus,1);\r\nmap(plus1)([1,2,3]) -&gt; [2,3,4]\r\n<\/code><\/pre>\n<p><code>plus1<\/code> above would have, in <em>J<\/em> an <em>infinite<\/em> rank: it always applies to its whole argument. When we say <code>map(plus1)<\/code> we have a new function which applies to the N-cells of its argument (in this case integers). In other words, <code>map<\/code> creates a new function which peels off one layer of its input and applies the original function, collecting the outputs.<\/p>\n<p>What, then, is<\/p>\n<pre><code>var mm_plus1 = map(map(plus1))\r\n<\/code><\/pre>\n<p>?<\/p>\n<p>(NB, we can denote this in Puff via <code>rep(map,2,plus1)<\/code>)<\/p>\n<p>Here is a hint:<\/p>\n<pre><code>mm_plus1([[1,2,3],[4,5,6]]) -&gt; [[2,3,4],[5,6,7]]\r\n<\/code><\/pre>\n<p>Now we have a function operating on the N-2 cells of the input. <em>Rank<\/em> in <em>J<\/em> typically operates <em>bottom up<\/em>: we start at rank 0 operating on the 0 cells, and increasing rank operates on larger and larger chunks of the input array. In contrast, iterative application of <code>map<\/code> in <em>Puff<\/em> moves us from the larger chunks to smaller and smaller chunks, until a number of applications equal to the array rank has us operating on individual items.<\/p>\n<p><em>J<\/em> being <em>J<\/em> we can mimic this behavior using <em>negative<\/em> rank.<\/p>\n<pre><code>&lt;\"_2(3 2 $ 1 2 3 4 5 6)\r\n\u250c\u2500\u252c\u2500\u2510\r\n\u25021\u25022\u2502\r\n\u251c\u2500\u253c\u2500\u2524\r\n\u25023\u25024\u2502\r\n\u251c\u2500\u253c\u2500\u2524\r\n\u25025\u25026\u2502\r\n\u2514\u2500\u2534\u2500\u2518\r\n<\/code><\/pre>\n<p>(<code>_2<\/code> denotes the number <code>-2<\/code> in <em>J<\/em> for possibly obscure reasons to do with making the parser simpler.)<\/p>\n<p>Given that <code>3 2 $ 1 2 3 4 5 6<\/code> has rank 2, the verb <code>&lt;\"_2<\/code> must operate on the <code>2-2=0<\/code> cells.<\/p>\n<p>The <em>J<\/em> approach of, by default, thinking about rank from 0-cells up works well for that language because matrices in <em>J<\/em> are regular and they keep track of their rank. If we represent matrices as nested arrays in Javascript (this is not the only option, but it is the most idiomatic) then the real rank of a matrix cannot be known without a full traversal, which is prohibitive.<\/p>\n<p>I might, one day, integrate a multidimensional matrix implementation into <em>Puff<\/em> and then enable rank modifying functions to work on that representation, but for now I want to focus on the successive use of<em>map<\/em> to simulate ranking down a function from infinite rank.<\/p>\n<h2>Consider Rank<\/h2>\n<p>Consider the following definition:<\/p>\n<pre><code>function rankedCall(f,n,a){\r\n    if(n&lt;0){\r\n        return rep(map, n, f)(a);\r\n    } else {\r\n        throw new Error(\"Positive ranks not yet supported.\");\r\n    }\r\n}\r\n\r\nvar rank = _c(rankedCall);\r\n<\/code><\/pre>\n<p>Such that:<\/p>\n<pre><code>rank(plusOne,1)([1,2,3]) -&gt; [2,3,4]\r\n<\/code><\/pre>\n<p>Cute. This gets us somewhere. But what really makes rank useful is that each argument carries its own rank and the system resolves the looping for you. In <em>J<\/em> operators have at most two arguments (to which rank applies, simulating more arguments with lists of boxes bypasses ranked dispatch).<\/p>\n<p>Dealing with multiple argument functions is tricky. Let&#8217;s start with two.<\/p>\n<p>Consider:<\/p>\n<pre><code>\/\/ Puff provides a plus function\r\nplus(1,3) -&gt; 4\r\n\/\/ but it doesn't work on arrays\r\nplus([1,2,3],[4,5,6]) -&gt; '1,2,34,5,6'\r\n<\/code><\/pre>\n<p>That last line is because Javascript idiotically interprets <code>[1,2,3]+[4,5,6]<\/code> to mean <code>[1,2,3].toString()+[4,5,6].toString()<\/code>.<\/p>\n<p>For these one dimensional arrays, we can get what we want with <code>map<\/code> which applies a function <code>f<\/code> of arity <code>n<\/code> to the items of <code>n<\/code> arrays.<\/p>\n<pre><code>map(plus,[1,2,3],[4,5,6]) -&gt; [5,7,9]\r\n<\/code><\/pre>\n<p>(NB. In <em>Puff<\/em> we can also have said <code>map(plus)([1,2,3],[4,5,6])<\/code>)<\/p>\n<p>What if we have <code>[1,2,3]<\/code> and <code>[[4],[5],[6]]<\/code>, that is, the second argument is rank two?<\/p>\n<p>Put aside questions of efficiency for a moment and consider the function:<\/p>\n<pre><code>function nextCellIndex(a, indexes){\r\n    var indexes = indexes.map(id); \/\/ copy the array\r\n    var delta = indexes.length-1;\r\n    var subIndex = indexes.slice(0,delta);\r\n    var indexedArray = index.apply(null, [a].concat(subIndex));\r\n    var done = indexes[delta]+1 &lt; indexedArray.length;\r\n    while(!done){\r\n      delta = delta -1;\r\n      if(delta&lt;0){\r\n          return null;\r\n      } else {\r\n          indexedArray = index.apply(null, [a].concat(indexes.slice(0,delta)));\r\n          done = indexes[delta]+1 &lt; indexedArray.length;\r\n\r\n      }\r\n    }\r\n    indexes[delta] = indexes[delta]+1;\r\n    for(var i = delta+1; i&lt;indexes.length; i = i + 1){\r\n      indexes[i] = 0;\r\n    }\r\n    return indexes;\r\n}\r\n<\/code><\/pre>\n<p>This function takes an array and an array of indexes and finds the next valid index into that array by incrementing the innermost index, checking whether that is in bounds, stopping if it is, or incrementing the next innermost and so on. If there is no valid next index, then null is returned.<\/p>\n<p>If we want what <em>J<\/em> would call the <code>-2<\/code> cells of an array <code>a<\/code>, we iteratively apply this function to a two element index vector.<\/p>\n<pre><code>var a = [[1],[2,3],[4]]\r\nvar indexes = repeatAccumulate(_p(nextCellIndex,a),3,[0,0])\r\n<\/code><\/pre>\n<p>Evaluating to:<\/p>\n<pre><code>indexes\r\n[ [ 0, 0 ], [ 1, 0 ], [ 1, 1 ], [ 2, 0 ] ]\r\n<\/code><\/pre>\n<p>that is, the indexes of the <code>-2<\/code> cells. We can get these by, for instance,<\/p>\n<pre><code>index.apply(null, a, indexes[0])\r\n<\/code><\/pre>\n<p>Note that <code>a<\/code> is not a regular matrix (the second item of <code>a<\/code> has a different length than the first and third &#8211; it has no obvious rank, but we can talk about its n-cells if we talk about them from outside in. We can write a function to give us these cells:<\/p>\n<pre><code>function cells(n, a){\r\n    if(n&lt;0){\r\n      var nn = -n;\r\n      var out = [];\r\n      var indexes = initArray(nn,0);\r\n      while(indexes){\r\n          out.push(index.apply(null, [a].concat(indexes)));\r\n          indexes = nextCellIndex(a,indexes)\r\n      }\r\n      return out;\r\n    } else if (n===0){\r\n      return a;\r\n    } else {\r\n      throw new Error(\"Positive cells not yet supported.\");\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>We can then just fall back onto <code>map<\/code> with the appropriate applications of <code>cells<\/code>:<\/p>\n<pre><code>map(plus,[1,2,3],cells(-2,[[1,2],[3]]))\r\n-&gt; [ 2, 4, 6 ]\r\n<\/code><\/pre>\n<p>Conceptually we&#8217;ve done well for ourselves: we&#8217;ve reproduced <em>J<\/em>&#8216;s ability to change the way that functions join elements of arrays of arbitrary dimension. On top of that, by virtue of the arity of <code>map<\/code>, which can apply a function of any arity to any number of arrays, we have extended this idea to operators of any number of arguments (<em>J<\/em> is limited to monadic and dyadic verbs.)<\/p>\n<p>In addition, <em>Puff<\/em> allows us to write the above function in a point free fashion:<\/p>\n<pre><code>var ex = f(2,au(map, al(plus), n0, r(n1,_p(cells, 2))));\r\nex([1,2,3],[[1,2],[3]])\r\n-&gt; [2, 4, 6]\r\n<\/code><\/pre>\n<p>(NB. <code>al<\/code> returns a function which always returns the argument to <code>al<\/code>, short for <code>always<\/code>, <code>n0<\/code> returns the first element of a thing, <code>n1<\/code> the second, etc. <code>f<\/code> (short for <code>lambda<\/code>) returns a function which first collects its arguments into an array and then passes them through its subsequent arguments as if via <code>r<\/code> (<code>rCompose<\/code>). Finally, <code>au<\/code> (short for <code>augment<\/code>) takes a set of functions and returns a new function which transforms its inputs via functions 1..n and applies function 0 to the that list.)<\/p>\n<h2>Positive Ranks<\/h2>\n<p>Using negative ranks is much more in-line with idiomatic javascript, since there are no native multidimensional arrays. We can produce a simple implementation of positive ranks if we make a few simple assumptions about usage. First consider:<\/p>\n<pre><code>function guessRank(a){\r\n    var rank = 0;\r\n    var done = false;\r\n    while(!done){\r\n        if(typeof a['length'] === 'number'){\r\n          rank = rank + 1;\r\n          a = a[0];\r\n        } else {\r\n          done = true;\r\n        }\r\n    }\r\n    return rank;\r\n}\r\n<\/code><\/pre>\n<p>Working like:<\/p>\n<pre><code>:Puff:&gt; guessRank(10)\r\n0\r\n:Puff:&gt; guessRank([1,2,3])\r\n1\r\n:Puff:&gt; guessRank([[1,2],[2,3],[3,4]])\r\n2\r\n<\/code><\/pre>\n<p>The assumption we are making here is that the rank of sub-elements is homogeneous (and hence, the first element is a sufficient indicator). Now that we can guess the rank of an array, we can fill in the positive rank branch of our <code>cells<\/code> function:<\/p>\n<pre><code>function cells(n, a){\r\n    if(n&lt;0){\r\n      var nn = -n;\r\n      var out = [];\r\n      var indexes = initArray(nn,0);\r\n      while(indexes){\r\n          out.push(index.apply(null, [a].concat(indexes)));\r\n          indexes = nextCellIndex(a,indexes)\r\n      }\r\n      return out;\r\n    } else {\r\n      var rank = n-guessRank(a);\r\n      return cells(rank, a);\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>Now we can finally write our implementation of <em>J<\/em>&#8216;s conjunction <code>\"<\/code>. Our version of <code>\"<\/code> will be called <code>rank<\/code> and will take a function and a list of ranks and return a new function with the appropriate rank behavior.<\/p>\n<pre><code>function rank(f){\r\n    var ranks = Array.prototype.slice.call(arguments,1,arguments.length);\r\n    return function(){\r\n    return map.apply(null,[f].concat(map(cells, \r\n                         Array.prototype.slice.call(arguments,0,arguments.length),\r\n                         ranks)));\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>We can now say:<\/p>\n<pre><code>rank(plus,0,0)([1,2,3],[[4],[5],[6]])\r\n<\/code><\/pre>\n<p>And get <code>[5,7,9]<\/code>. Just like <em>J<\/em>. Of course, as we&#8217;ve written the code here we won&#8217;t be anywhere near the efficiency of <em>J<\/em> &#8211; in particular we iterate over each argument array separately, where we could combine all those loops into just one. But performance isn&#8217;t everything and we can always optimize the <em>Puff<\/em> implementation as needed. Rewriting the approprite sequence functions (<code>map<\/code>,<code>mapcat<\/code>,<code>crossMap<\/code>) to handle lazy versions of the sequences and introducing a lazy <code>cells<\/code> operator would be the most elegant solution. I&#8217;m sure I&#8217;ll get there eventually.<\/p>\n<p>In the meantime, I hope I&#8217;ve at least helped the reader understand <em>J<\/em>&#8216;s rank concept in greater depth and also showed off some of the nice ways <em>Puff<\/em> can simulate <em>J<\/em> style while staying entirely in Javascript.<\/p>\n<hr \/>\n","protected":false},"excerpt":{"rendered":"<p>This blog post will sketch out some thoughts relating to Puff, a function level programming language I am embedding in Javascript and J&#8216;s notion of operator rank. Rank as it pertains to nouns in J is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_mi_skip_tracking":false,"footnotes":""},"categories":[19,10,18,47],"tags":[],"class_list":["post-137","post","type-post","status-publish","format-standard","hentry","category-data-analysis","category-development","category-j","category-programming"],"_links":{"self":[{"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/posts\/137","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/comments?post=137"}],"version-history":[{"count":1,"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/posts\/137\/revisions"}],"predecessor-version":[{"id":138,"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/posts\/137\/revisions\/138"}],"wp:attachment":[{"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/media?parent=137"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/categories?post=137"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/procyonic.org\/blog\/wp-json\/wp\/v2\/tags?post=137"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}