J is great! It is a wonderful little language for data analysis tasks. However, to programmers used to working in modern dynamic languages, it sometimes feels a little restrictive. In particular, a ubiquitous feature in the post-Python (let’s call it) era is hash-maps. Even in older languages, like Common Lisp, the association list – allowing abritrary mappings between keys and values, is a very common idiom.
J exposes no native functionality that exactly meets this use case, one common application of which is named, optional arguments to functions.
In developing an interactive gnuplot interface for J, I wanted to pass optional keyword arguments to functions so that plots can be easily customized. So I developed a simple representation of key/val pairs which is efficient enough for small collections of named values.
Consider:
nil =: (0$0)
rankOne =: 1: -: (#@:$)
rankTwo =: 2: -: (#@:$)
talliesEven =: 0: -: (2: | #)
twoColumns =: 2: -: 1&{@:$
opts =: monad define
keysandvals =. y
assert. rankOne keysandvals
assert. talliesEven keysandvals
ii =. 2 | (i. #y)
keys =. (-. ii) # y
vals =. ii # y
keys (>@:[ ; ])"(0 0) vals
)
Opts is a monadic verb which takes a flat boxed array of even length and returns a rank two boxed array whose first column is keys and whose second column is values:
opts 'key1';10;'key2';11
+----+--+
|key1|10|
+----+--+
|key2|11|
+----+--+
Look up is simple: find the key’s index in the first column and index the second column with it. Return nil if the key isn’t found.
getopt =: dyad define
options =. x
key =. y
assert. rankTwo options
assert. twoColumns options
if. 0 -: #options do.
nil
else.
ii =. ((i.&1)@:(((key&-:@:>)"0)@:(((0&{)@:|:)))) options
if. ii < #options do.
(>@:(ii&{)@:(1&{)@:|:) options
else.
nil
end.
end.
)
Eg:
(opts 'key1';10;'key2';11) getopt 'key1'
-> 10
We can now define a handy conjunction to allow the specification of a default value:
dft =: conjunction define
:
r =. x u y
if. r -: nil do.
n
else.
r
end.
)
Which we use this way:
(opts 'key1';10;'key2';11) getopt dft '___' 'key1'
-> 10
(opts 'key1';10;'key2';11) getopt dft '___' 'key3'
'---'
This allows us to pass optional arguments to verbs and specify default values relatively easily, as in this example from my gnuplot library:
histogram =: verb define
(ensureGnuPlot'') histogram y
:
data =. y
if. boxedP x do.
options =. x
gph =. options getopt dft (ensureGnuPlot'') 'gnuplot'
else.
gph =. x
options =. (opt '')
end.
mn =. <./ data
mx =. >./ data
bw =. options getopt dft (0.1&* mx-mn) 'binwidth'
pttl =. options getopt dft '' 'plot-title'
'binwidth=%f\n' gph gpfmt <bw
gph send 'set boxwidth binwidth'
gph send 'bin(x,width)=width*floor(x/width) + binwidth/2.0'
s =. 'plot "%s" using (bin($1,binwidth)):(1.0) smooth freq title "%s" with boxes'
s gph gpfmt ((asFile (asVector data));pttl)
)
Where, in the dyadic case, we detect whether x
is boxed, and if so, treat it as a list of options. We extract each option by name, providing a reasonable default.
This seems like a common problem, so I am wondering if anyone else in the J community has solved it before in a different way?
Having developed in APL in the 1980’s, before J, and also before the hashmap was even a concept, we had ‘packed objects’ which some APL’s had implemented , and for others, were implemented much like the blogger did here. This is such an old concept that time was available to implement an ‘is’ function, (which I privately call the Clinton function,) so that is’aPackedObject.aComponent’ is meaningful.
It’s nice to see ‘mainstream’ languages catching up.