📜 ⬆️ ⬇️

Programming language J. Amateur look. Part 2. Tacit Programming

Previous article in the Cycle Programming Language J. Amateur Look. Part 1. Introduction

Question: If functions change data, and operators change functions, then who changes the operators?
Answer: Ken Iverson
Chirag pathak


J uses the idea of ​​tacit (from the word "tacit", implicit) programming, which does not require explicit mention of the arguments of the function (program) being defined. Work in the tacit approach occurs, as a rule, with arrays of data, and not with their individual elements.
It is interesting to note that tacit programming was discovered by Backus before APL and implemented in the FP language. Among the modern languages ​​that support this approach (except, of course, J), we can name the Fort and other concatenative languages, as well as Haskell (due to the point-free approach).
')

1. Verbs


Define our first verb. For convenience, we can assume that the verb is a function with default arguments. Open the interpreter J, enter

neg =: - 


where “neg” is the name of our verb, “=:” is the assignment operator, “-” is the actual body of the verb, which consists of calling the embedded verb “-”. Now let's try to apply our verb to the value (i.e. call it). For example, to the unit:

  neg 1 _1 


As you can see from the example, the result is the number “-1” (in J, an underscore is used to write a negative number, since the minus is occupied by the embedded verb).

Now we define a variable (in J this is called a “noun”) “x” and assign the value -1 to it:

  x =: _1 


We expect the “neg x” construction to return the unit to us. It really is:

  neg x 1 


This call is similar to the simple

  - x 1 


Another example:

  x _1 neg - 


Those. the value of the variable "x" is "-1", and the value of the verb is its body. You can change the way the values ​​are displayed on the screen using the verb "9!: 3".
We define another verb, which will also be a simple synonym for the embedded verb:

  twice =: +: 


This verb doubles the value of its argument. Here you can trace the pattern in the naming of verbs - for example, if the verb “+:” doubles its argument, then the verb “-:” halves its argument.

  NB.    x =: _1 NB. ,  «NB.»   . NB.     «Nota Bene»,    « ». twice x _2 twice (neg x) 2 


And even so:

  twice (-: 2) 2 


So

  +: (+: (- 2)) _8 


2. Monads and dyads


This section is not related to Haskell


Verbs can be called with one or two arguments. A verb with one argument (operand) in terms of J is called a monad, and with two arguments it is called a dyad. In the latter case, the arguments are written left and right (the first and second arguments, respectively) from the verb being called. Recall our first monad:

  minus =: - minus 1 _1 minus 7 _7 


But the verb we have defined can be invoked with two arguments:

  4 minus 2 2 12 minus 5 7 


In this case, the verb minus is used as a dyad and, as a result of the substitution, the expression "4-2" and "12-5" is calculated, respectively.

Two arguments are a maximum that can be used when defining and invoking a verb.
One can imagine that if the language J supported a two-dimensional syntax (such as the esoteric language Befunge), then the arguments could be written not only on the left and on the right, but on the top-bottom. Fortunately, J only supports linear syntax.

Let us give one more example - we define the verb “div” as a synonym for the embedded verb “%”. Moreover, "%" is a division operator, the more familiar to us symbol "/" is used for other tasks (later we will show why).

  div =: % 1 div 2 0.5 4 div 3 1.3333 


In this example, the verb is called as a dyad and divides 1 into 2 and 4 into 3, respectively. And note that although the operands are integers, the result of the division is a floating point number.

When called with one operand, the verb “%” by default divides the unit by this operand.

  div 2 0.5 div 4 0.25 


The above expression could also be written as "1% 2" and "1% 4", respectively.

In the example above, a rather important feature of the language is used - the monadic and dyadic calls of the same verb can perform actions that are quite different in meaning. For example, the verb "*:" in the monad case calculates the square of its argument, and in the dyadic it performs the logical operation "Not-And":

  *: 3 9 *: 4 16 0 *: 0 1 1 *: 1 0 0 *: 1 1 1 *: 0 1 


Verbs "[" and "]" are intended for the verb to explicitly use its left or right argument. The first one returns its left argument, the second one - the right one. For example:

  11 [ 22 11 11 ] 22 22 ] 33 33 [ 44 NB.  ,   ,  [   . 44 


Let's add our luggage with the constant verbs "0:", "1:", "2:", ..., "9:". They are called constant because, with whatever arguments we would not call them, they always return the same value. "0:" returns zero, "1:" - one, etc. For example:

  7 3: 9 3 3: 7 3 


3. Unions


Suppose further that for the same verb it is necessary to define different behavior for monadic (with one argument) and dyadic (with two arguments) calls. This is a well-grounded desire, since J is a dynamic language, and our code is not immune from incorrect use. In order to separate the monadic and dyadic behavior of the verb, there is a special union ":".

If we say that a verb is a function with one or two arguments by default, and the value of the arguments can be both a variable and an immediate value, then, in this case, “union” is a function that as two (and only two) arguments accept verbs. Unions, as well as verbs, the user can define on their own.

Here one could draw an analogy with parts of speech (in J, as we shall see later, there are also adverbs), but it seems to the author that this will lead to unnecessary confusion.

Let's return to our monads. On the left side of the union ":" the monadic call of the verb is recorded, on the right side - dyadic. Example:

  v =: 1: : 2: 11 v 22 2 v 11 1 


If you call the verb "v" as a dyad (in the example with the arguments "11" and "22"), then the expression "2:" will be called. If as a monad (in the example with the argument "11"), then to "1:". And these are constant verbs that always return their meaning: “2” in the first case and “1” in the second.

So far, we have operated only with numerical values. This time, we use strings that are written in single quotes (double quotes in J is a union). Redefine the previously described verb "v":

  v =: 'monadic call' : 'dyadic call' v 11 |domain error 11 v 22 |domain error 


As we can see, there is an error in our definition. A string is a value, not a verb, and it therefore does not produce any action. And with the tacit definition of a verb, you only need to specify the order in which the verbs and their combinations are used.

You can work with strings in J as well as with arrays (which we will get to know a little later). For example, the verb “#” in a monadic call returns the length of the array:

  # 23 1 # '12ab' 4 # '012345' 6 # '' 0 


Earlier we defined the verb neg, which changes the sign of its argument. However, if you call this verb in the dyadic version, the following will happen:

  neg =: - 1 neg 2 _1 


Those. after substitution, our verb worked as a subtraction "1 - 2". It would be advisable to limit the scope of the verb to only monadic call. As mentioned earlier, the dynamic nature of J forces us to conduct similar checks during program execution. As, perhaps, everything else in J, such a test is recorded as short as possible:

  neg =: - : [: 


We used the new verb "[:". In the book “J for C programmers”, this verb is called “suicidal verb” for being called, results in the program crash:

  neg 1 _1 1 neg 2 NB.      - - |domain error 


In order to catch such situations, you can use the so-called. "Traps". In J, there are no exceptions in the conventional sense of the word, instead the “::” union is used.

The union "::" executes the expression to the left of itself, and if it ended with an error, it executes the expression to the right. Example:

  v =: [: :: 3: 1 v 2 3 v 7 3 


This verb will always return 3, because, as we said, the expression on the left will be executed first. And on the left we put a suicidal verb. Consequently, the union "::" with each call of the verb "v" catches the suicide "[:" and passes all the arguments to the verb, which we wrote down on the right, i.e. the verb-constant "3:".

3.1. Unions for sequential computing



So far, we have used only single verbs. In order to carry out sequential calculations, the conjunction “@” or the special verb “[:” ”is used, which, being the first element in the composition of verbs, is skipped and allows you to perform a sequential calculation:

  ([: +: +:) 10 NB.  (+: (+: 10)) 40 (+: @ +:) 10 NB.  (+: (+: 10)) 40 


The next union in our collection is “&.”, The principle of which is the following.

  u&.vy NB.  v_ uvy x u&.vy NB.  v_ (vx) u (vy). +:&.*: 10 14.1421 %: @: +: (*: 10) NB.    14.1421 %: (+: (*: 10)) NB.   14.1421 

where “v_inversion” means a verb, reverse “v”. In the expression “+: &. *:” The verb from which the inversion is taken is “*:” (because it stands to the right of the conjunction “&.”). And the verb, the reverse "*:" is "%:". Those. the operation inverse to squaring is taking a square root.

Another useful for us is the conjunction "&", which makes a monadic from a dyadic call, attaching a noun (operand-value) to the verb. With a certain assumption, it can be said that this union performs currying. For example:
  inc =: 1&+ NB.  inc 41 42 inc _3 _2 div3 =: %&3 div3 9 3 div3 12 4 


Define the increment only for the monad case:

  inc =: 1&+ : [: inc 1 2 1 inc 2 |domain error: inc | 1 inc 2 


4. Adverbs



We have already mentioned adverbs. In terms of the J language, an adverb is an expression that takes a verb as an argument and forms a new verb with a different behavior.

We give the adverb "~", which swaps the arguments in its dyadic call ("xu ~ y" is substituted for "yux") and replaces the monadic call of the verb - with the dyadic, copying the operand ("u ~ y" is substituted for "yuy").

  % 2 NB.   ,    «1 % 2» 0.5 %~ 2 NB.  «2 % 2» 1 2 % 3 0.666667 2 %~ 3 NB.  «3 % 2» 1.5 


One of the most commonly used adverbs is the adverb / (“between”), the consideration of which we will defer to the section on arrays.

5. Hooks and forks



In J, there is a very peculiar way of composition of functions - if 2 consecutive verbs are indicated, this does not mean that they will be applied sequentially. They will have their own special calling procedure for dyadic and monadic calls. Such a composition for 2 verbs is called a hook (hook). Define the verb v, which is the hook calling of the verbs f and g:

  v =: fg 


If you call v as a monad on some noun y:

  vy 


then this call will be equivalent to:

  yf (gy) 


It should be noted that, despite the monadic call of the verb v, one of the elements of the hook (namely, f) is called as a dyad with duplication on the left of a single operand.

If you call v as a dyad on some nouns x and y:

  xvy 


then this call will be equivalent to:

  xf (gy) 


Let us recall the verb «#» , which in the monadic call returns the length of a string / array, and in the dyadic it copies the right operand as many times as indicated in the left operand. Define the dyad:

  v =: # # 


Let's decompose the dyadic call of the verb v according to the above scheme:

   # (# y) 


As you can see, v copies a number equal to the length of the operand y, x times. For example:

  2 v '1234' 4 4 


One would assume that 3 consecutive verbs are a hook from a hook. However, this is not the case - instead, a fork composition (the so-called fork) is implemented. So, monad call fork

  (fgh) y 


equivalent to the following call:

  (fy) g (hy) 


An example of such an expression is the already known verb, which calculates the average of an array of numbers:

  mean =: +/%# mean 0 1 2 3 4 5 2.5 


This expression is equivalent to the following:

  ((+/ @ [ )%(# @ ])) 0 1 2 3 4 5 2.5 


Consider another example of a fork:

  v =: # # # v '12345' 5 5 5 5 5 


As you can see, this expression copies the number equal to the length of the operand, as many times as the length of the operand.

The previous verb could be rewritten without using forks:

  ((#@]) # (#@])) '12345' 5 5 5 5 5 


Define on the dyadic call forks on nouns x and y:

  x (fgh) y 


which will be equivalent to the following expression:

  (xfy) g (xhy) 


We illustrate the dyadic fork with the help of our old friend - the verb mean:

  2 mean 3 1.66667 1.66667 


Expand this expression:

  (2 +/ 3) % (2 # 3) 


We get that the right side of the expression copies 2 times the top three. And the left one calculates the sum of the left and right operands. So we will receive:

  5 % 3 3 1.66667 1.66667 


It is especially important to understand that in J if 4 verbs are indicated, then this expression will be a hook from a fork, if 5 - then a fork from a fork, 6 - a hook from a fork from a fork, etc. Those. the expressions "(# # # # # #)" and "(# (# # (# # #)))" are equivalent.

We show how to use the round brackets to change the order of calculations. For example:

  (# # #) 3 1 


But

  (# (# #)) 3 1 1 1 1 1 1 1 1 1 


The last expression is equivalent to the following:

  3 # (3 # (# 3) 3 # 1 1 1 NB.  3   . 


Let us show a more complex example with the composition of functions.

  (* + - %) 3 8 


This expression is equivalent to:

  3 * ((+ 3) - (% 3)) 


A combination of 5 verbs might look like this:

  (>: * + - %) 3 10.6667 


The last expression is equivalent to the following:

  (>: 3) * ((+ 3) - (% 3)) 4 * (3 – 0.333) 


Next article in the Cycle of Programming Language J. Amateur Look. Part 3. Arrays

Source: https://habr.com/ru/post/198066/


All Articles