J может быть читаемым

  • Tutorial
Жесть! Это как программирование через регулярные выражения…
Нет, вы меня не заставите! Больше никогда!
Смотрю на код и чувствую себя идиотом. Это правда не эзотерический язык типа brainfuck? Им кто-то реально пользуется? И эти программы потом читают?
quicksort=: (($:@(<#[), (=#[), $:@(>#[)) ({~ ?@#)) ^: (1<#)
Perl нервно курит в сторонке.
Хабрахабр о J

imageJ — корейский среди языков программирования. Взрыв на фабрике спецсимволов. Куча скобочек, точек, слэшей, и всё это ещё и работает. Не иначе как по велению чёрной магии, а то и самого Сатаны.

Некоторые из тех, кто пишет на J, забывают простые правила написания любого кода в погоне за краткостью или просто увлекшись. Эти правила не новые, но они приобретают критическое значение применительно к APL-подобным языкам, потому как при чтении конструкций вроде ((]i.~[:{.[){([:{:[),]`([:<[$:[:>])@.([:32&=[:3!:0[:>]))"2 0 даже тренированный мозг сворачивается в трубочку.

Итак, простые правила написания читаемого кода на J под катом.

Без словарика под кат лучше не соваться. Вас предупреждали.
И напомню, список полезных ссылок здесь.


Пользуйтесь мнемониками


Если неудобно или просто не хочется часто лезть в словарь, чтобы подсмотреть, какими же всё-таки символами обозначается та или иная функция или что значит вон та конструкция — оберните её в глагол:
  head =: {.
  tail =: {:
  head 9 8 3 0 6 1 2 5 4 7
9
  tail 9 8 3 0 6 1 2 5 4 7
7

Разделяй и властвуй


Иногда лучше разбить сложную, запутанную конструкцию на несколько более простых.
  logistic =: dyad : '(^ - y % x) % x * *: >: ^ - y % x'

  exp =: dyad : '^ - y % x'
  logistic =: dyad : '(x exp y) % x * *: >: x exp y

Комментарии


Комментарии в J начинаются с NB., и это как раз тот случай, когда их не может быть много! Если залезть в стандартные библиотеки или промышленный код, можно наткнуться на тонны комментариев; зачастую их в несколько раз больше, чем самого кода: писать самодокументирующийся код на J сложно, и усилия обычно себя не оправдывают.

Больше пробелов, хороших и разных


Средний однострочник на J и так состоит из двадцати слабо на первый взгляд связанных символов, незачем лишний раз усложнять прочтение. Правильно расставленные пробелы и скобки помогут разобраться в структуре даже самого запутанного глагола.
  (]%[**:@>:@])[:^[:-%~
  (] % [ * *:@>:@]) [: ^ [: - %~

Явное лучше скрытого


Для сложных выражений часто лучше использовать эксплицитную запись вместо тацитной.
  (] % [ * *:@>:@]) [: ^ [: - %~
  dyad : '(] % (x * *:@>:@])) ^ -y%x'

Хуки и форки — ваши друзья


Для тех, кто не знал или забыл:
  (f g) y ⇔ y f (g y)
  x (f g) y ⇔ x f (g y)
  (f g h) y ⇔ (f y) g (h y)
  x (f g h) y ⇔ (x f y) g (x h y)

Эти простые конструкции часто помогают сократить количество кода вдвое-втрое, практически не ухудшая его читаемость.

Управляющие структуры


В J есть все привычные управляющие структуры, но использовать их можно только внутри эксплицитного глагола.

Ветвления работают так же, как в любом другом языке: если условие T вернуло 1, переходим к блоку B, иначе переходим к следующему блоку else./elseif., если он есть.
if. T do. B end.
if. T do. B else. B1 end.
if. T do. B elseif. T1 do. B1 elseif. T2 do. B2 end.

while. и whilst. исполняют блок B, пока T возвращает 1, с тем отличием, что whilst. пропускает проверку для первого прохода, так что B всегда выполняется как минимум один раз.
while. T do. B end.
whilst. T do. B end.

for. просто выполняет B столько раз, сколько элементов в T; for_i. создаёт переменные i и i_index — элемент и его индекс соответственно.
for. T do. B end.
for_i. T do. B end.

select. переходит к первому Ti, совпавшему с T, выполняя соответствующий блок. fcase.case. с «проваливанием».
select. T
 case. T0 do. B0
 case. T1 do. B1
 fcase.T2 do. B2
 case. T3 do. B3
end.

Если блок B выполнился с ошибкой — выполняем блок B1, иначе просто игнорируем его.
try. B catch. B1 end.



Попробуем применить эти правила к решателю судоку из этого поста.
Исходный исходный код:
  i =: ,((,|:)i.9 9),,./,./i.4$3
  c =: (#=[:#~.)@-.&0
  t =: [:(([:*/_9:c\])"1#])i&{+"1 1(>:i.9)*/[:i&=i.&0
  r =: [:,`$:@.(0:e.,)[:;(<@t)"1
  s =: 9 9&$@r@,

Итог преобразований:
  cells =: ,./^:2 i. 4 # 3
  rows_cols =: ((, |:) i. 9 9)
  indices =: , rows_cols, cells

  no_errors =: verb : '(-: ~.) y -. 0'

  substitutions =: verb : '(indices { y) +"1 1 ((>:i.9) */ indices = y i. 0)'
  remove_wrong =: verb : 'y #~ */"(1) _9 no_errors\"1 y'

  try =: remove_wrong @ substitutions

  solve =: verb define
    variants =: y
    whilst.
      0 e. ,variants
    do.
      variants =: ; (<@try)"1 variants
    end.
    ,variants
  )

  sudoku =: verb : '9 9 $ solve , y'

Результат работы
   m
2 0 0 3 7 0 0 0 9
0 0 9 2 0 0 0 0 7
0 0 1 0 0 4 0 0 2
0 5 0 0 0 0 8 0 0
0 0 8 0 0 0 9 0 0
0 0 6 0 0 0 0 4 0
9 0 0 1 0 0 5 0 0
8 0 0 0 0 7 6 0 0
4 0 0 0 8 9 0 0 1
   s m
2 8 4 3 7 5 1 6 9
6 3 9 2 1 8 4 5 7
5 7 1 9 6 4 3 8 2
1 5 2 4 9 6 8 7 3
3 4 8 7 5 2 9 1 6
7 9 6 8 3 1 2 4 5
9 6 7 1 4 3 5 2 8
8 1 3 5 2 7 6 9 4
4 2 5 6 8 9 7 3 1
   sudoku m
2 8 4 3 7 5 1 6 9
6 3 9 2 1 8 4 5 7
5 7 1 9 6 4 3 8 2
1 5 2 4 9 6 8 7 3
3 4 8 7 5 2 9 1 6
7 9 6 8 3 1 2 4 5
9 6 7 1 4 3 5 2 8
8 1 3 5 2 7 6 9 4
4 2 5 6 8 9 7 3 1

Теперь по коду видно, что он делает! Я считаю, это успех.
Поделиться публикацией

Комментарии 24

    +15
    Спасибо за статью, многое стало понятным. И все-же… Это эзотерический язык программирования, по крайней мере для меня.ццц
      +26
      Простите за сарказм, но

      (]%[**:@>:@])[:^[:-%~
      (] % [ * *:@>:@]) [: ^ [: - %~

      О да. Стало ГОРАЗДО понятнее :)

      А если серьезно, какие есть причины вообще писать на J, а не на чем-нибудь другом?
        +3
        О да. Стало ГОРАЗДО понятнее :)
        Да, было довольно трудно найти подходящий пример :) Вообще, правила хорошо работают все скопом, а не по отдельности.

        А если серьезно, какие есть причины вообще писать на J, а не на чем-нибудь другом?
        Да хоть бы и for fun. На брейнфаке же пишут.
          +1
          Ну разве что for fun :)
            +2
            Я писал не just for fun. Тут дело далеко не только в синтаксисе, а в высоком уровне операций над векторами. А краткость конструкций позвоняет не отвлекаться от самого написания.
              0
              А прочесть потом смогли? :)
                0
                Легко!… если конечно не через 6 месяцев, на протяжении которых не трогал J. Но в основном по причине, что словарь забывается быстрее чем в других языках, но думаю он просто не успел достаточно прочно осесть в голове/на пальцах. После восстановления словаря — читается хорошо.

                В J, как и в любом языке, большинство конструкций довольно простые, просто люди удивляются типа как же это так записано без слов, но на самом деле ничего такого нет. Потом открывается масса других достоинств, но к синтаксису они имеют мало отношения.
                  0
                  Лично меня просто пугают слишком краткие записи. Что J, что Хаскель, что Перл — их как-то очень тяжело проговаривать про себя, слишком мало слов.
                    +1
                    Не понимаю. Неужели вам удобнее воспринять:
                    sum=0; foreach i in L { sum+=i }; return sum ?

                    чем

                    +/ L NB. / это over или foldr

                    ?
                      0
                      sum(L)

                      Люблю Питон :)
                        0
                        Тем более.

                        sum =: +/
                        sum(L)

                        Просто через какое-то время нужда в мнемониках отпадает, и +/ воспринимается ничуть не хуже.
                          0
                          Ни в коем случае не отрицаю :) Вопрос в поддержке кода кем-то другим…
                            0
                            Через неделю-две чтения JforC по вечерам вполне можно читать и понимать код на J не хуже, чем код на любом другом языке, хоть и не без периодического подглядывания в словарик.
                            0
                            Более корректное сравнение с Python:

                            from functools import reduce
                            from operator import add
                            
                            reduce(add, L, 0)
                            

                            Исходя из опыта разработки DSL могу сказать, что нужда в мнемониках не отпадает. Наоборот, на реальном оборудовании выясняется, что сложение может быть знаковым/беззнаковым, с разным округлением и разным контролем переполнения, деление может быть целочисленным и обычным и т. д. И вообще есть много бинарных операторов, попытка обозначить которые приведёт к неоднозначным результатам. Например, при перемножении векторов идёт покомпонентное перемножение или традиционное векторное произведение? А при перемножении множеств?
                              0
                              Исходя из опыта разработки DSL могу сказать, что нужда в мнемониках не отпадает. Наоборот, на реальном оборудовании выясняется, что сложение может быть знаковым/беззнаковым, с разным округлением и разным контролем переполнения, деление может быть целочисленным и обычным и т. д.
                              Я, может, не совсем понял, но это уже будут не мнемоники, а вполне себе собственные глаголы вроде <.@%. Под мнемониками я имею в виду именно оборачивание примитива в глагол (+/, конечно, не примитив)
                          0
                          Не знаю, как вы, но я код проговариваю про себя, когда читаю. Соответственно, «плюс слеш эл энбэ точка» звучит как клинопись какая-то.
                          Может, дело привычки, но порог вхождения в такой язык сильно повышается.
                            0
                            NB. — это начало комментария.

                            так что просто +/L
                            но проговаривать можно + over L. так как для APL'а,K и думаю J есть переводчики синтаксиса в слова — только они не особо нужны тем кто начал писать.
              +3
              Для Code golf, например.
              0
              Да, Perl и правда курит в сторонке, он позволяет писать какой угодно код, хоть запутанный, хоть самодокументированный, в отличии от. Тем не менне, с автором статьи согласен, эти правила никому не мешали.
                +1
                И всё-таки, писать самодокументирующийся код можно, но усилий это обычно не стоит:
                logistic =: verb define
                  s =: x
                  x =: y
                
                  (exp (- x % s)) % (s * sqr (1 + exp (- x % s))) return.
                )
                
                normal =: verb define
                  x =: y
                
                  (exp (- (sqr (x)) % 2)) % (sqrt (2 * pi)) return.
                )
                
                0
                Я первый раз вижу этот язык, будто программирование смайликами
                  +2
                  будто программирование смайликами

                  Это вы ещё Lisp не видели, наверное.
                    0
                    Lisp — это программирование безглазыми смайликами ))))
                  –1
                  Мне одному кажется, что данный язык программирования серьезно увеличивает энтропию ?!

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое