Предыдущая статья цикла Язык программирования J. Взгляд любителя. Часть 1. Введение
В J используется идея тацитного (от слова «tacit», неявный) программирования, не требующего явного упоминания аргументов определяемой функции (программы). Работа в тацитном подходе происходит, как правило, с массивами данных, а не с отдельными их элементами.
Интересно заметить, что тацитное программирование было открыто Бэкусом еще до APL и реализовано им в языке FP. Среди современных языков, поддерживающих такой подход, (кроме, естественно, J) можно назвать Форт и другие конкатенативные языки, а также Haskell (за счет point-free подхода).
Определим наш первый глагол. Для удобства, можно считать, что глагол – это функция с аргументами, заданными по умолчанию. Открываем интерпретатор J, вводим
где «neg» – имя нашего глагола, «=:» – оператор присваивания, «-» – собственно тело глагола, которое состоит из вызова встроенного глагола «-». Попробуем теперь применить наш глагол к значению (т.е. вызвать его). Например, к единице:
Как видно из примера, в результате получилось число «-1» (в J для записи отрицательного числа используется знак подчеркивания, т.к. минус занят под встроенный глагол).
Теперь определим переменную (в J это называется «существительное») «x» и присвоим ей значение -1:
Мы ожидаем, что конструкция «neg x» вернет нам единицу. Это действительно так:
Этот вызов аналогичен простому
Другой пример:
Т.е. значением переменной «x» является «-1», а значением глагола — его тело. Изменить способ вывода значений на экран можно с помощью глагола «9!:3».
Определим еще один глагол, который также будет простым синонимом для встроенного глагола:
Этот глагол удваивает значение своего аргумента. Тут можно проследить закономерность в именовании глаголов – так, например, если глагол «+:» удваивает свой аргумент, то глагол «-:» свой аргумент уменьшает вдвое.
И даже так:
И так
Глаголы можно вызывать как с одним, так и с 2 аргументами. Глагол с одним аргументом (операндом) в терминах J называется монадой, а с двумя аргументами — диадой. В последнем случае аргументы записываются слева и справа (первый и второй аргумент соответственно) от вызываемого глагола. Вспомним нашу первую монаду:
Но определенный нами глагол можно вызвать и с двумя аргументами:
В таком случае глагол minus используется как диада и в результате подстановки вычисляется выражение «4 — 2» и «12 -5» соответственно.
Два аргумента — это максимум, который можно использовать при определении и вызове глагола.
Можно представить себе, что, если бы язык J поддерживал двумерный синтаксис (как, например, эзотерический язык Befunge), то аргументы можно было бы записывать не только слева и справа, но сверху-снизу. К счастью, в J поддерживается только линейный синтаксис.
Приведем еще один пример — определим глагол «div» как синоним встроенному глаголу «%». Причем «%» — это оператор деления, более привычный нам символ «/» используется для других задач (позже мы покажем для чего).
В данном примере глагол вызван как диада и делит 1 на 2 и 4 на 3 соответственно. Причем обратите внимание, что хотя операнды — целые числа, результат деления — число с плавающей точкой.
Будучи вызванным с одним операндом глагол «%» по умолчанию делит единицу на этот операнд.
Приведенные выше выражение, можно было бы записать и как «1 % 2» и «1 % 4» соответственно.
В приведенном выше примере используется достаточно важная особенность языка — монадный и диадный вызовы одного и того же глагола могут совершать достаточно разные по смыслу действия. Так, например, глагол «*:» в монадном случае вычисляет квадрат своего аргумента, а в диадном — выполняет логическую операцию «Не-И»:
Для того, чтобы глагол явно использовал свой левый или правый аргумент предназначены глаголы «[» и «]». Первый из них возвращает свой левый аргумент, второй — правый. Например:
Дополним наш багаж константными глаголами «0:», «1:», «2:», …, «9:». Константными их называют потому, что с какими бы аргументами мы бы их не вызывали, они всегда вернут одно и тоже значение. «0:» вернет нуль, «1:» — единицу и т.д. Например:
Предположим далее, что для одного и того же глагола надо определить различное поведение для монадного (с одним аргументом) и для диадного (с двумя аргументами) вызовов. Это вполне обоснованное желание, т.к. J — язык динамический, и наш код не застрахован от некорректного использования. Для того, чтобы разделить монадное и диадное поведение глагола, есть специальный союз «:».
Если мы говорим, что глагол – это функция с одним или двумя аргументами по умолчанию, а значением аргументов может быть как переменная, так и непосредственное значение, то, в таком случае, «союз» — это такая функция, которая в качестве двух (и только двух) аргументов принимает глаголы. Союзы, также как и глаголы, пользователь может определять самостоятельно.
Тут можно было бы провести аналогию с частями речи (в J, как мы увидим позже, есть еще и наречия), но автору кажется, что это приведет к лишней путанице.
Вернемся к нашим монадам. По левую сторону от союза «:» записывается монадный вызов глагола, по правую – диадный. Пример:
Если вызвать глагол «v» как диаду (в примере — с аргументами «11» и «22»), то произойдет обращение к выражению «2:». Если как монаду (в примере — с аргументом «11»), то к «1:». А это константные глаголы, которые всегда возвращают свое значение: «2» — в первом случае и «1» — во втором.
До сих пор мы оперировали только численными значениями. На сей раз воспользуемся строками, которые записываются в одинарных кавычках (двойные кавычки в J – это союз). Переопределим описанный ранее глагол «v»:
Как мы видим, в нашем определении ошибка. Строка – это значение, а не глагол, и она, следовательно, не производит никакого действия. А при тацитном определении глагола требуется указать только порядок применения глаголов и их комбинаций.
Работать со строками в J можно также, как и с массивами (с которыми мы познакомимся несколько позже). Например, глагол «#» в монадном вызове возвращает длину массива:
Ранее мы определяли глагол neg, который меняет знак у своего аргумента. Однако, если вызвать этот глагол в диадном варианте, то случится следующее:
Т.е. после подстановки наш глагол сработал как вычитание «1 – 2». Целесообразно было бы, ограничить область применения глагола только монадным вызовом. Как уже говорилось ранее, динамическая природа J принуждает нас проводить подобные проверки во время исполнения программы. Как, пожалуй, и все остальное в J, такая проверка записывается максимально коротко:
Мы воспользовались новым для нас глаголом «[:». В книге «J for C programmers» этот глагол назван «глаголом-самоубийцей» за то, что будучи вызванным, приводит к аварийному останову программы:
Для того, чтобы отлавливать подобные ситуации можно использовать т.н. «ловушки». В J нет исключений (exceptions) в привычном понимании этого слова, вместо этого используется союз «::».
Союз «::» выполняет выражение слева от себя, а если оно завершилось с ошибкой, то исполняет выражение справа. Пример:
Этот глагол всегда будет возвращать 3, потому что, как мы сказали, сначала выполнится выражение слева. А слева мы поставили глагол-самоубийцу. Следовательно, союз «::» при каждом вызове глагола «v» отлавливает самоубийцу «[:» и передает все аргументы глаголу, который мы записали справа, т.е. глаголу-константе «3:».
До сих пор мы использовали только одиночные глаголы. Для того, чтобы проводить последовательные вычисления используется союз «@» либо специальный глагол «[:», который, будучи первым элементом в композиции глаголов, пропускается и позволяет выполниться последовательному вычислению:
Следующий союз в нашу коллекцию «&.», принцип работы которого следующий.
где «v_инверсия» — значит глагол, обратный «v». В выражении «+:&.*: » глагол, от которого берется инверсия — это «*:» (т.к. он стоит справа от союза «&.»). А глагол, обратный «*:» — это «%:». Т.е. операция, обратная возведению в квадрат — это взятие квадратного корня.
Другой полезный для нас союз «&», который делает из диадного вызова монадный, присоединяя к глаголу существительное (операнд-значение). С определенным допущением можно сказать, что этот союз осуществляет каррирование. Например:
Определим инкремент только для монадного случая:
Ранее мы уже упомянули о наречиях. В терминах языка J наречие – это выражение, которое принимает в качестве аргумента глагол и формирует их него новый глагол с другим поведением.
Приведем наречие «~», которое меняет местами аргументы в своем диадном вызове («x u~ y» подставляется на «y u x») и заменяет монадный вызов глагола — диадным, копируя операнд («u~ y»подставляется на «y u y»).
Одним из самых часто используемых наречий является наречие / («между»), рассмотрение которого мы отложим до раздела о массивах.
В J весьма своеобразный способ композиции функций — если указаны 2 подряд идущих глагола, то это не означает, что они будут применяться последовательно. У них будет свой специальный порядок вызова отдельно для диадного и для монадного вызовов. Такая композиция для 2 глаголов называется хуком(крюком). Определим глагол v, который является хук-вызовом глаголов f и g:
Если вызвать v как монаду на некотором существительном y:
то этот вызов будет эквивалентен:
Необходимо отметить, что, несмотря на монадный вызов глагола v, один из элементов хука (а именно f) вызывается как диада с дублированием слева единственного операнда.
Если же вызвать v как диаду на некоторых существительных x и y:
то этот вызов будет эквивалентен:
Вспомним глагол решетку
Разложим диадный вызов глагола v по приведенной выше схеме:
Как видно, v копирует число, равное длине операнда y, х раз. Например:
Можно было бы предположить, что 3 подряд идущих глагола являются хуком от хука. Однако это не так – вместо этого реализуется форк-композиция (т.н. вилка). Так, монадный вызов форка
равнозначен следующему вызову:
Примером такого выражения можно привести известный уже нам глагол, высчитывающий среднее от массива чисел:
Такое выражение равнозначно следующему:
Рассмотрим еще один пример форка:
Как видно, это выражение копирует число, равное длине операнда, столько раз, какая длина операнда.
Предыдущей глагол можно было бы переписать и без использования форков:
Определим на диадном вызове форк на существительных x и y:
который будет равнозначен следующему выражению:
Проиллюстрируем диадный форк с помощью нашего старого знакомого – глагола mean:
Раскроем данное выражение:
Получим, что правая часть выражения копирует 2 раза тройку. А левая вычисляет сумму левого и правого операндов. Т.о. получим:
Особенно важно понимать, что в J если указаны 4 глагола, то это выражение будет хуком от форка, если 5 – то форком от форка, 6 — хуком от форка от форка и т.д. Т.е. выражения «(# # # # # #)» и «(# (# # (# # #)))» равнозначны.
Покажем, как с помощью круглых скобок можно изменять порядок вычислений. Например:
Но
Последнее выражение равнозначно следующим:
Покажем более сложный пример с композицией функций
Это выражение равнозначно следующему:
Комбинация из 5 глаголов может выглядеть так:
Последнее выражение равнозначно следующим:
Следующая статья цикла Язык программирования J. Взгляд любителя. Часть 3. Массивы
Вопрос: Если функции изменяют данные, а операторы изменяют функции, тогда кто изменяет операторы?
Ответ: Кен Айверсон
Chirag Pathak
В J используется идея тацитного (от слова «tacit», неявный) программирования, не требующего явного упоминания аргументов определяемой функции (программы). Работа в тацитном подходе происходит, как правило, с массивами данных, а не с отдельными их элементами.
Интересно заметить, что тацитное программирование было открыто Бэкусом еще до APL и реализовано им в языке FP. Среди современных языков, поддерживающих такой подход, (кроме, естественно, J) можно назвать Форт и другие конкатенативные языки, а также Haskell (за счет point-free подхода).
1. Глаголы
Определим наш первый глагол. Для удобства, можно считать, что глагол – это функция с аргументами, заданными по умолчанию. Открываем интерпретатор J, вводим
neg =: -
где «neg» – имя нашего глагола, «=:» – оператор присваивания, «-» – собственно тело глагола, которое состоит из вызова встроенного глагола «-». Попробуем теперь применить наш глагол к значению (т.е. вызвать его). Например, к единице:
neg 1
_1
Как видно из примера, в результате получилось число «-1» (в J для записи отрицательного числа используется знак подчеркивания, т.к. минус занят под встроенный глагол).
Теперь определим переменную (в J это называется «существительное») «x» и присвоим ей значение -1:
x =: _1
Мы ожидаем, что конструкция «neg x» вернет нам единицу. Это действительно так:
neg x
1
Этот вызов аналогичен простому
- x
1
Другой пример:
x
_1
neg
-
Т.е. значением переменной «x» является «-1», а значением глагола — его тело. Изменить способ вывода значений на экран можно с помощью глагола «9!:3».
Определим еще один глагол, который также будет простым синонимом для встроенного глагола:
twice =: +:
Этот глагол удваивает значение своего аргумента. Тут можно проследить закономерность в именовании глаголов – так, например, если глагол «+:» удваивает свой аргумент, то глагол «-:» свой аргумент уменьшает вдвое.
NB. Ранее мы определили x =: _1
NB. Кстати, после «NB.» следует однострочный комментарий.
NB. Это сокращение от латинского «Nota Bene», что в переводе «обрати внимание».
twice x
_2
twice (neg x)
2
И даже так:
twice (-: 2)
2
И так
+: (+: (- 2))
_8
2. Монады и диады
Этот раздел не имеет отношения к Haskell
Глаголы можно вызывать как с одним, так и с 2 аргументами. Глагол с одним аргументом (операндом) в терминах J называется монадой, а с двумя аргументами — диадой. В последнем случае аргументы записываются слева и справа (первый и второй аргумент соответственно) от вызываемого глагола. Вспомним нашу первую монаду:
minus =: -
minus 1
_1
minus 7
_7
Но определенный нами глагол можно вызвать и с двумя аргументами:
4 minus 2
2
12 minus 5
7
В таком случае глагол minus используется как диада и в результате подстановки вычисляется выражение «4 — 2» и «12 -5» соответственно.
Два аргумента — это максимум, который можно использовать при определении и вызове глагола.
Можно представить себе, что, если бы язык J поддерживал двумерный синтаксис (как, например, эзотерический язык Befunge), то аргументы можно было бы записывать не только слева и справа, но сверху-снизу. К счастью, в J поддерживается только линейный синтаксис.
Приведем еще один пример — определим глагол «div» как синоним встроенному глаголу «%». Причем «%» — это оператор деления, более привычный нам символ «/» используется для других задач (позже мы покажем для чего).
div =: %
1 div 2
0.5
4 div 3
1.3333
В данном примере глагол вызван как диада и делит 1 на 2 и 4 на 3 соответственно. Причем обратите внимание, что хотя операнды — целые числа, результат деления — число с плавающей точкой.
Будучи вызванным с одним операндом глагол «%» по умолчанию делит единицу на этот операнд.
div 2
0.5
div 4
0.25
Приведенные выше выражение, можно было бы записать и как «1 % 2» и «1 % 4» соответственно.
В приведенном выше примере используется достаточно важная особенность языка — монадный и диадный вызовы одного и того же глагола могут совершать достаточно разные по смыслу действия. Так, например, глагол «*:» в монадном случае вычисляет квадрат своего аргумента, а в диадном — выполняет логическую операцию «Не-И»:
*: 3
9
*: 4
16
0 *: 0
1
1 *: 1
0
0 *: 1
1
1 *: 0
1
Для того, чтобы глагол явно использовал свой левый или правый аргумент предназначены глаголы «[» и «]». Первый из них возвращает свой левый аргумент, второй — правый. Например:
11 [ 22
11
11 ] 22
22
] 33
33
[ 44 NB. как видим, если аргумент один, то [ возвращает правый аргумент.
44
Дополним наш багаж константными глаголами «0:», «1:», «2:», …, «9:». Константными их называют потому, что с какими бы аргументами мы бы их не вызывали, они всегда вернут одно и тоже значение. «0:» вернет нуль, «1:» — единицу и т.д. Например:
7 3: 9
3
3: 7
3
3. Союзы
Предположим далее, что для одного и того же глагола надо определить различное поведение для монадного (с одним аргументом) и для диадного (с двумя аргументами) вызовов. Это вполне обоснованное желание, т.к. J — язык динамический, и наш код не застрахован от некорректного использования. Для того, чтобы разделить монадное и диадное поведение глагола, есть специальный союз «:».
Если мы говорим, что глагол – это функция с одним или двумя аргументами по умолчанию, а значением аргументов может быть как переменная, так и непосредственное значение, то, в таком случае, «союз» — это такая функция, которая в качестве двух (и только двух) аргументов принимает глаголы. Союзы, также как и глаголы, пользователь может определять самостоятельно.
Тут можно было бы провести аналогию с частями речи (в J, как мы увидим позже, есть еще и наречия), но автору кажется, что это приведет к лишней путанице.
Вернемся к нашим монадам. По левую сторону от союза «:» записывается монадный вызов глагола, по правую – диадный. Пример:
v =: 1: : 2:
11 v 22
2
v 11
1
Если вызвать глагол «v» как диаду (в примере — с аргументами «11» и «22»), то произойдет обращение к выражению «2:». Если как монаду (в примере — с аргументом «11»), то к «1:». А это константные глаголы, которые всегда возвращают свое значение: «2» — в первом случае и «1» — во втором.
До сих пор мы оперировали только численными значениями. На сей раз воспользуемся строками, которые записываются в одинарных кавычках (двойные кавычки в J – это союз). Переопределим описанный ранее глагол «v»:
v =: 'monadic call' : 'dyadic call'
v 11
|domain error
11 v 22
|domain error
Как мы видим, в нашем определении ошибка. Строка – это значение, а не глагол, и она, следовательно, не производит никакого действия. А при тацитном определении глагола требуется указать только порядок применения глаголов и их комбинаций.
Работать со строками в J можно также, как и с массивами (с которыми мы познакомимся несколько позже). Например, глагол «#» в монадном вызове возвращает длину массива:
# 23
1
# '12ab'
4
# '012345'
6
# ''
0
Ранее мы определяли глагол neg, который меняет знак у своего аргумента. Однако, если вызвать этот глагол в диадном варианте, то случится следующее:
neg =: -
1 neg 2
_1
Т.е. после подстановки наш глагол сработал как вычитание «1 – 2». Целесообразно было бы, ограничить область применения глагола только монадным вызовом. Как уже говорилось ранее, динамическая природа J принуждает нас проводить подобные проверки во время исполнения программы. Как, пожалуй, и все остальное в J, такая проверка записывается максимально коротко:
neg =: - : [:
Мы воспользовались новым для нас глаголом «[:». В книге «J for C programmers» этот глагол назван «глаголом-самоубийцей» за то, что будучи вызванным, приводит к аварийному останову программы:
neg 1
_1
1 neg 2 NB. Здесь срабатывает правая часть определения - глагол-самоубийца
|domain error
Для того, чтобы отлавливать подобные ситуации можно использовать т.н. «ловушки». В J нет исключений (exceptions) в привычном понимании этого слова, вместо этого используется союз «::».
Союз «::» выполняет выражение слева от себя, а если оно завершилось с ошибкой, то исполняет выражение справа. Пример:
v =: [: :: 3:
1 v 2
3
v 7
3
Этот глагол всегда будет возвращать 3, потому что, как мы сказали, сначала выполнится выражение слева. А слева мы поставили глагол-самоубийцу. Следовательно, союз «::» при каждом вызове глагола «v» отлавливает самоубийцу «[:» и передает все аргументы глаголу, который мы записали справа, т.е. глаголу-константе «3:».
3.1. Союзы для последовательных вычислений
До сих пор мы использовали только одиночные глаголы. Для того, чтобы проводить последовательные вычисления используется союз «@» либо специальный глагол «[:», который, будучи первым элементом в композиции глаголов, пропускается и позволяет выполниться последовательному вычислению:
([: +: +:) 10 NB. эквивалентно (+: (+: 10))
40
(+: @ +:) 10 NB. эквивалентно (+: (+: 10))
40
Следующий союз в нашу коллекцию «&.», принцип работы которого следующий.
u&.v y NB. эквивалентно v_инверсия u v y
x u&.v y NB. эквивалентно v_инверсия (v x) u (v y).
+:&.*: 10
14.1421
%: @: +: (*: 10) NB. аналогично примеру выше
14.1421
%: (+: (*: 10)) NB. или так
14.1421
где «v_инверсия» — значит глагол, обратный «v». В выражении «+:&.*: » глагол, от которого берется инверсия — это «*:» (т.к. он стоит справа от союза «&.»). А глагол, обратный «*:» — это «%:». Т.е. операция, обратная возведению в квадрат — это взятие квадратного корня.
Другой полезный для нас союз «&», который делает из диадного вызова монадный, присоединяя к глаголу существительное (операнд-значение). С определенным допущением можно сказать, что этот союз осуществляет каррирование. Например:
inc =: 1&+ NB. инкремент
inc 41
42
inc _3
_2
div3 =: %&3
div3 9
3
div3 12
4
Определим инкремент только для монадного случая:
inc =: 1&+ : [:
inc 1
2
1 inc 2
|domain error: inc
| 1 inc 2
4. Наречия
Ранее мы уже упомянули о наречиях. В терминах языка J наречие – это выражение, которое принимает в качестве аргумента глагол и формирует их него новый глагол с другим поведением.
Приведем наречие «~», которое меняет местами аргументы в своем диадном вызове («x u~ y» подставляется на «y u x») и заменяет монадный вызов глагола — диадным, копируя операнд («u~ y»подставляется на «y u y»).
% 2 NB. Как мы помним, это аналогично вызову «1 % 2»
0.5
%~ 2 NB. аналогично «2 % 2»
1
2 % 3
0.666667
2 %~ 3 NB. аналогично «3 % 2»
1.5
Одним из самых часто используемых наречий является наречие / («между»), рассмотрение которого мы отложим до раздела о массивах.
5. Хуки и форки
В J весьма своеобразный способ композиции функций — если указаны 2 подряд идущих глагола, то это не означает, что они будут применяться последовательно. У них будет свой специальный порядок вызова отдельно для диадного и для монадного вызовов. Такая композиция для 2 глаголов называется хуком(крюком). Определим глагол v, который является хук-вызовом глаголов f и g:
v =: f g
Если вызвать v как монаду на некотором существительном y:
v y
то этот вызов будет эквивалентен:
y f (g y)
Необходимо отметить, что, несмотря на монадный вызов глагола v, один из элементов хука (а именно f) вызывается как диада с дублированием слева единственного операнда.
Если же вызвать v как диаду на некоторых существительных x и y:
x v y
то этот вызов будет эквивалентен:
x f (g y)
Вспомним глагол решетку
«#»
, который в монадном вызове возвращает длину строки/массива, а в диадном копирует правый операнд столько раз, сколько указано в левом операнде. Определим диаду: v =: # #
Разложим диадный вызов глагола v по приведенной выше схеме:
х # (# y)
Как видно, v копирует число, равное длине операнда y, х раз. Например:
2 v '1234'
4 4
Можно было бы предположить, что 3 подряд идущих глагола являются хуком от хука. Однако это не так – вместо этого реализуется форк-композиция (т.н. вилка). Так, монадный вызов форка
(f g h) y
равнозначен следующему вызову:
(f y) g (h y)
Примером такого выражения можно привести известный уже нам глагол, высчитывающий среднее от массива чисел:
mean =: +/%#
mean 0 1 2 3 4 5
2.5
Такое выражение равнозначно следующему:
((+/ @ [ )%(# @ ])) 0 1 2 3 4 5
2.5
Рассмотрим еще один пример форка:
v =: # # #
v '12345'
5 5 5 5 5
Как видно, это выражение копирует число, равное длине операнда, столько раз, какая длина операнда.
Предыдущей глагол можно было бы переписать и без использования форков:
((#@]) # (#@])) '12345'
5 5 5 5 5
Определим на диадном вызове форк на существительных x и y:
x (f g h) y
который будет равнозначен следующему выражению:
(x f y) g (x h y)
Проиллюстрируем диадный форк с помощью нашего старого знакомого – глагола mean:
2 mean 3
1.66667 1.66667
Раскроем данное выражение:
(2 +/ 3) % (2 # 3)
Получим, что правая часть выражения копирует 2 раза тройку. А левая вычисляет сумму левого и правого операндов. Т.о. получим:
5 % 3 3
1.66667 1.66667
Особенно важно понимать, что в J если указаны 4 глагола, то это выражение будет хуком от форка, если 5 – то форком от форка, 6 — хуком от форка от форка и т.д. Т.е. выражения «(# # # # # #)» и «(# (# # (# # #)))» равнозначны.
Покажем, как с помощью круглых скобок можно изменять порядок вычислений. Например:
(# # #) 3
1
Но
(# (# #)) 3
1 1 1 1 1 1 1 1 1
Последнее выражение равнозначно следующим:
3 # (3 # (# 3)
3 # 1 1 1 NB. Повторить 3 раза правый операнд.
Покажем более сложный пример с композицией функций
(* + - %) 3
8
Это выражение равнозначно следующему:
3 * ((+ 3) - (% 3))
Комбинация из 5 глаголов может выглядеть так:
(>: * + - %) 3
10.6667
Последнее выражение равнозначно следующим:
(>: 3) * ((+ 3) - (% 3))
4 * (3 – 0.333)
Следующая статья цикла Язык программирования J. Взгляд любителя. Часть 3. Массивы