Неявное программирование , также называемое стилем без точек , представляет собой парадигму программирования , в которой определения функций не идентифицируют аргументы (или «точки»), на которых они работают. Вместо этого определения просто составляют другие функции, среди которых есть комбинаторы , которые манипулируют аргументами. Неявное программирование представляет теоретический интерес, поскольку строгое использование композиции приводит к программам, которые хорошо адаптированы для эквациональных рассуждений. [1] Это также естественный стиль некоторых языков программирования , включая APL и его производные, [2] и конкатенативные языки, такие как Forth . Отсутствие именования аргументов дает стилю без точек репутацию излишне непонятного, отсюда и эпитет «стиль без точек». [1]
Скрипты Unix используют парадигму с каналами .
Неявное программирование можно проиллюстрировать следующим кодом Python . Последовательность операций, например, следующая:
пример определения ( x ): return baz ( bar ( foo ( x )))
... может быть записано в бесточечном стиле как композиция последовательности функций без параметров: [3]
из functools import partial , reduce def compose ( * fns ): return partial ( reduce , lambda v , fn : fn ( v ), fns )пример = составить ( foo , bar , baz )
Для более сложного примера код Haskell p = ((.) f) . g
можно перевести как:
p = частичный ( составить , частичный ( составить , f ), g )
Простой пример (на Haskell ) — это программа, которая вычисляет сумму списка чисел. Мы можем определить функцию суммы рекурсивно, используя указанный стиль (ср. программирование на уровне значений ) как:
сумма ( x : xs ) = x + сумма xs сумма [] = 0
Однако, используя складку, мы можем заменить это на:
сумма xs = фолдr ( + ) 0 xs
И тогда аргумент не нужен, так что это упрощается до
сумма = фолд ( + ) 0
который является бесточечным.
В другом примере используется композиция функций :
п х у z = ф ( г х у ) z
Следующий псевдокод в стиле Haskell демонстрирует, как свести определение функции к ее эквиваленту без точек:
p = \ x -> \ y -> \ z -> f ( g x y ) z = \ x -> \ y -> f ( g x y ) = \ x -> \ y -> ( f . ( g x )) y = \ x -> f . ( g x ) ( * Здесь инфиксный оператор компоновки "." используется как каррированная функция . * ) = \ x -> (( . ) f ) ( g x ) = \ x - > ( ( ( . ) f ) . g ) x р = (( . ) ф ) . г
Наконец, чтобы увидеть сложный пример, представьте себе программу фильтра карты, которая берет список, применяет к нему функцию, а затем фильтрует элементы на основе критерия.
список операторов критериев mf = критерии фильтра ( список операторов карты )
Это можно выразить без точек [4] как
mf = ( . map ) . ( . ) . filter
Обратите внимание, что, как уже было сказано ранее, точки в «точечно-бесточечном» относятся к аргументам, а не к использованию точек; это распространенное заблуждение. [5]
Было написано несколько программ для автоматического преобразования выражения Haskell в бесточечную форму.
В J такой же тип бесточечного кода встречается в функции, созданной для вычисления среднего значения списка (массива) чисел:
средн. =: +/ % #
+/
суммирует элементы массива путем сопоставления ( /
) суммирования ( +
) с массивом. %
делит сумму на количество элементов ( #
) в массиве.
Формула Эйлера, выраженная молчаливо:
соз =: 2 о . ] грех =: 1 о . ] Эйлер =: ^@ j . = потому что j . грех
( j.
— примитивная функция, монадическое определение которой — 0j1
умножить на x, а диадическое определение — x+0j1×y
.) Те же самые неявные вычисления, выраженные в Dyalog APL :
средн. ← + ⌿ ÷ ≢ cos ← 2 ○ ⊢ sin ← 1 ○ ⊢ EulerCalc ← cos + 0j1 × sin ⍝ 0j1 обычно записывается как i EulerDirect ← * 0J1 ×⊢ ⍝ То же, что и ¯12○⊢ ⍝ Дают ли эти 2 метода одинаковый результат? EulerCheck ← EulerDirect = EulerCalc EulerCheck ¯1 1 2 3 1 1 1 1 ⍝ Да, пока все хорошо!
В языках программирования, ориентированных на стек (и конкатенационных , большинство из которых основаны на стеке [ требуется ссылка ] ), обычно используются методы без точек. Например, процедура вычисления чисел Фибоначчи может выглядеть следующим образом в PostScript :
/fib { dup dup 1 eq exch 0 eq или нет { dup 1 sub fib exch 2 sub fib add } if } def
В скриптах Unix функции — это компьютерные программы, которые получают данные из стандартного ввода и отправляют результаты в стандартный вывод . Например,
сортировка | uniq -c | сортировка -rn
является неявной или бесточечной композицией, которая возвращает количество своих аргументов и аргументов в порядке убывания количества. 'sort' и 'uniq' являются функциями, '-c' и '-rn' управляют функциями, но аргументы не упоминаются. Канал '|' является оператором композиции.
Из-за особенностей работы конвейеров обычно возможно передавать только один "аргумент" за раз в виде пары стандартных потоков ввода/вывода. Хотя дополнительные дескрипторы файлов могут быть открыты из именованных каналов , это больше не является стилем без точек.
jq — это язык программирования, ориентированный на JSON, в котором символ '|' используется для соединения фильтров с целью формирования конвейера привычным способом. Например:
[1,2] | добавить
оценивается как 3. (Да, массив JSON — это фильтр jq, который оценивается как массив.)
Хотя конвейеры jq похожи на конвейеры Unix, они позволяют отправлять входящие данные более чем одному получателю в правой части '|' как будто параллельно. Например, программа `add/length` вычислит среднее значение чисел в массиве, так что:
[1,2] | добавить/длину
оценивается как 1,5
Сходным образом:
[1,2] | [длина, добавить, добавить/длина]
оценивается как [2,3,1.5]
Точку ('.') можно использовать для определения точки присоединения на правой стороне, например:
1 | [., .]
оценивается как [1,1]
и аналогично:
2 | pow(.; .)
оценивается как 4, поскольку pow(x;y) — это x в степени y.
Программа молчаливого jq для генерации последовательности Фибоначчи будет выглядеть так:
[0,1] | рекурсия( [последний, добавить] ) | первый
Здесь [0,1] — начальная пара, которая будет взята в качестве первых двух элементов в последовательности Фибоначчи. (Пара [1,1] также может быть использована для определения варианта.)
Буквенные токены являются встроенными фильтрами: `first` и `last` выдают первый и последний элементы своих входных массивов соответственно; а `recurse(f)` рекурсивно применяет фильтр f к своим входным данным.
jq также позволяет определять новые фильтры в неявном виде, например:
def fib: [0,1] | recurse( [last, add] ) | first;
В разделе, посвященном Python в этой статье, рассматривается следующее определение Python:
пример определения ( x ): return baz ( bar ( foo ( x )))
В бесточечном стиле это можно записать на Python так:
пример = составить ( foo , bar , baz )
В jq эквивалентное определение без точек будет иметь вид:
пример определения: foo | bar | baz;