Прямая функция - Direct function

Прямая функция ( д.ф.н. , произносится как «Ди весело») является альтернативным способом определить функцию и оператор (а функция высшего порядка ) в языке программирования APL . Прямого оператора также можно назвать доп (произносится как «ди оп»). Они были изобретены Джоном Скоулзом в 1996 году. Они представляют собой уникальную комбинацию программирования массивов , функций высшего порядка и функционального программирования и являются важным отличительным признаком APL начала 21 века по сравнению с предыдущими версиями.

Dfn - это последовательность, возможно, защищенных выражений (или просто защитных выражений ) между {и }, разделенных символом новой строки или новой строкой, где обозначает левый аргумент и правый, а также обозначает рекурсию (ссылку на функцию). Например, функция PTпроверяет , является ли каждая строка представляет собой Пифагор триплет (путем проверки , равна ли сумма квадратов дважды квадрат максимума).

   PT {(+/*2)=2×(/)*2}
   PT 3 4 5
1
   x
 4  5  3
 3 11  6
 5 13 12
17 16  8
11 12  4
17 15  8
   PT x
1 0 1 0 0 1

Факторный функция как DFN:

   fact {0=⍵:1  × -1}
   fact 5
120
   fact¨ 10    ⍝ fact applied to each element of 0 to 9
1 1 2 6 24 120 720 5040 40320 362880

Описание

Правила для dfns кратко изложены в следующей «справочной карточке»:

{ function } {⍺⍺ operator ⍵⍵} :   сторожить
  левый аргумент ⍺⍺  левый операнд ::  стражник ошибок
  правильный аргумент ⍵⍵  правый операнд   левый аргумент по умолчанию 
  ссылка на себя   ∇∇  ссылка на себя   s  застенчивый результат

Dfn - это последовательность, возможно, защищенных выражений (или просто охранников) между {и }, разделенных символом новой строки или новой строкой.

expression
guard: expression
guard:

Выражения лица и / или охранники оцениваются последовательно. Охранник должен дать оценку 0 или 1; связанное с ним выражение оценивается, если значение равно 1. dfn завершается после первого незащищенного выражения, которое не заканчивается присваиванием , или после первого защищенного выражения, защита которого оценивается как 1, или если больше нет выражений. Результат dfn - результат последнего вычисленного выражения. Если последнее вычисленное выражение заканчивается присваиванием, результат будет «застенчивым» - не будет автоматически отображаться в сеансе.

Имена, присвоенные в dfn, по умолчанию являются локальными с лексической областью видимости .

обозначает левый аргумент функции и правый; ⍺⍺обозначает левый операнд и ⍵⍵правый. Если ⍵⍵встречается в определении, то dfn является диадическим оператором ; если только ⍺⍺встречается, но нет ⍵⍵, то это монадический оператор; если ни один из них ⍺⍺или не ⍵⍵происходит, то dfn является функцией.

Специальный синтаксис используется для присвоения значения по умолчанию левому аргументу, если dfn вызывается монадически, то есть вызывается без левого аргумента. В противном случае не оценивается. expressionexpression

обозначает рекурсию или ссылку на себя функцией и ∇∇обозначает ссылку на себя оператором. Такое обозначение допускает анонимную рекурсию .

Отлов ошибок обеспечивается средствами защиты от ошибок . Когда генерируется ошибка, система динамически ищет через вызывающие функции средство защиты от ошибок, которое соответствует ошибке. Если он найден, среда выполнения возвращается в свое состояние непосредственно перед выполнением защиты от ошибок, и соответствующее выражение защиты от ошибок оценивается как результат dfn. errnums::expression

Дополнительные описания, объяснения и руководства по dfns доступны в цитируемых статьях.

Примеры

Примеры здесь иллюстрируют различные аспекты dfns. Дополнительные примеры можно найти в цитируемых статьях.

Левый аргумент по умолчанию

Функция добавляет к ( я или -1 ) раз . {+0j1×}0j1

   3 {+0j1×} 4
3J4
   ∘.{+0j1×} ¯2+⍳5
¯2J¯2 ¯2J¯1 ¯2 ¯2J1 ¯2J2
¯1J¯2 ¯1J¯1 ¯1 ¯1J1 ¯1J2
 0J¯2  0J¯1  0  0J1  0J2
 1J¯2  1J¯1  1  1J1  1J2
 2J¯2  2J¯1  2  2J1  2J2

Значение этой функции можно увидеть в следующем:

Комплексные числа могут быть построены как упорядоченные пары действительных чисел, аналогично тому, как целые числа могут быть построены как упорядоченные пары натуральных чисел и рациональные числа как упорядоченные пары целых чисел. Для комплексных чисел играет ту же роль, что и для целых и рациональных чисел.{+0j1×}-÷

Кроме того, аналогично это монадической ⇔ ( запись отрицания ) и монадическая ⇔ ( обратный ), монадическое определение функции полезно, осуществляется путем указания значения по умолчанию , равным 0 для : если , то ⇔ ⇔ . -0-÷1÷j{0 +0j1×}j 0 j 0+0j1×

   j{0  +0j1×}

   3 j 4 ¯5.6 7.89
3J4 3J¯5.6 3J7.89

   j 4 ¯5.6 7.89
0J4 0J¯5.6 0J7.89

   sin 1
   cos 2
   Euler {(*j ) = (cos ) j (sin )}

   Euler (¯0.5+?100) j (¯0.5+?100)
1 1 1 1 1 1 1 1 1 1

Последнее выражение иллюстрирует формулу Эйлера для десяти случайных чисел с действительной и мнимой частями в интервале .

Одиночная рекурсия

Тернарное построение множества Кантора начинается с интервала [0,1] и на каждом этапе удаляет среднюю треть из каждого оставшегося подынтервала:

Набор порядка Кантора, определенный как dfn:

   Cantor {0=⍵:,1  ,1 0 1 ∘.  -1}

   Cantor 0
1
   Cantor 1
1 0 1
   Cantor 2
1 0 1 0 0 0 1 0 1
   Cantor 3
1 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 1

Cantor 0 - Cantor 6 изображены черными полосами:

Набор Кантора за семь итераций.svg

Функция вычисляет битовый вектор длины, так что бит (для и ) равен 1 тогда и только тогда, когда является простым числом . sieve i0ii<i

sieve{
  4⍵:⍵0 0 1 1
  r0.5*n
  p2 3 5 7 11 13 17 19 23 29 31 37 41 43
  p(1+(n≤×p)1)p
  b 0@1  {(m)>m1  mn×≢} 1,p
  {r<qb1:bb[]1  b[q,q×⍸bn÷q]0   ,q}p
}

   10 10  sieve 100
0 0 1 1 0 1 0 1 0 0
0 1 0 1 0 0 0 1 0 1
0 0 0 1 0 0 0 0 0 1
0 1 0 0 0 0 0 1 0 0
0 1 0 1 0 0 0 1 0 0
0 0 0 1 0 0 0 0 0 1
0 1 0 0 0 0 0 1 0 0
0 1 0 1 0 0 0 0 0 1
0 0 0 1 0 0 0 0 0 1
0 0 0 0 0 0 0 1 0 0

   bsieve 1e9
   b
1000000000
   (10*⍳10) (+)0 1 b
0 4 25 168 1229 9592 78498 664579 5761455 50847534

Последняя последовательность, число простых чисел меньше 10, является начальным сегментом OEISA006880 . Последнее число, 50847534, - это количество простых чисел меньше, чем . Это число называется числом Бертельсена, которое MathWorld запоминает как «ошибочное имя, которому ошибочно присвоено ошибочное значение ».

sieveиспользует два разных метода для маркировки композитов нулями, оба осуществляются с использованием локальных анонимных dfns: первый использует решето Эратосфена на начальной маске 1 и префиксе простых чисел 2 3 ... 43, используя оператор вставки ( правый сгиб ). (Длина префикса получается путем сравнения с примитивной функцией .) Второй находит наименьшее новое простое число, оставшееся в ( ), и сам устанавливает в 0 бит , а иногда биты числа в оставшихся 1 битах в начальном сегменте ( ) . Этот второй dfn использует хвостовую рекурсию. ×pqbqb1qqbbn÷q

Хвостовая рекурсия

Как правило, факториальная функция определяется рекурсивно (как указано выше ), но ее можно закодировать так, чтобы использовать хвостовую рекурсию , используя левый аргумент аккумулятора:

fac{1  =0:⍺  (×)  -1}

Точно так же определитель квадратной комплексной матрицы с использованием исключения Гаусса может быть вычислен с помощью хвостовой рекурсии:

det{                ⍝ determinant of a square complex matrix
  1                ⍝ product of co-factor coefficients so far
  0=≢⍵:⍺             ⍝ result for 0-by-0
  (i j)()⊤⊃⍒|,   ⍝ row and column index of the maximal element
  k⍳≢
  (×[i;j]ׯ1*i+j)  [k~i;k~j] - [k~i;j] ∘.× [i;k~j]÷[i;j]
}

Множественная рекурсия

Перегородка из неотрицательного целого числа является вектором положительных целых чисел , таких , что , где порядок не является существенным. Например, и являются разделами по 4, а и и считаются одним и тем же разделом. n = +v2 22 1 12 1 11 2 11 1 2

Функция разделения подсчитывает количество разделов. Функция представляет интерес в теории чисел , ее изучали Эйлер , Харди , Рамануджан , Эрдеш и другие. Рекуррентное отношение

выводится из теоремы Эйлера о пятиугольных числах . Написано как dfn:

   pn   {1⍵:0  -+¨rec }
   rec  { - (÷2 (×1) ¯1 1 ∘.+ 3×) 1+⍳⌈0.5*×2÷3}

   pn 10
42
   pn¨ 13    ⍝ OEIS A000041
1 1 2 3 5 7 11 15 22 30 42 56 77

Базовый шаг утверждает, что для , результатом функции будет 1, если ⍵ равно 0 или 1, и 0 в противном случае. Рекурсивный шаг является многократно рекурсивным. Например, это приведет к применению функции к каждому элементу : 1⍵:010pn 200rec 200

   rec 200
199 195 188 178 165 149 130 108 83 55 24 ¯10
198 193 185 174 160 143 123 100 74 45 13 ¯22

и для вычисления требуется больше возраста вселенной ( функция вызывает сама себя). Время вычислений может быть уменьшено с помощью мемоизации , здесь реализованной как прямой оператор (функция высшего порядка) : pn 200M

M{
  f⍺⍺
  i2+'⋄'t2↓,⎕cr 'f'
  '{T←(1+⍵)⍴¯1 ⋄ ',(it),'¯1≢T[⍵]:⊃T[⍵] ⋄ ⊃T[⍵]←⊂',(it),'⍵}⍵'
}

   pn M 200
3.973E12
   0  pn M 200  ⍝ format to 0 decimal places
 3972999029388

Это значение согласуется с вычисленным Харди и Рамануджаном в 1918 году. pn M 200

Оператор memo Mопределяет вариант своей функции операнда ⍺⍺для использования кеша, T а затем оценивает его. pnВариант с операндом :

{T(1+)¯1  {1⍵:0  ¯1T[]:T[]  T[]⊂-+¨rec }}

Прямой оператор (доп)

Быстрая сортировка в массиве работает, выбирая случайным образом «опорную точку » среди ее основных ячеек, затем объединяя отсортированные основные ячейки, которые строго предшествуют опорной точке, основные ячейки, равные опорной точке, и отсортированные основные ячейки, которые строго следуют за опорной точкой, как определяется функцией сравнения ⍺⍺. Определяется как прямой оператор (доп) Q:

   Q{1≥≢⍵:⍵  ( ⌿⍨0>s)(⌿⍨0=s) ⌿⍨0<s ⍺⍺ ?≢}

   ⍝ precedes            ⍝ follows            ⍝ equals
   2 (×-) 8              8 (×-) 2             8 (×-) 8
¯1                    1                    0

   x 2 19 3 8 3 6 9 4 19 7 0 10 15 14

   (×-) Q x
0 2 3 3 4 6 7 8 9 10 14 15 19 19

Q3- это вариант, который объединяет три части, заключенные в функцию, вместо частей как таковых . Три части, генерируемые на каждом рекурсивном шаге, видны в структуре конечного результата. Применение функции, полученной из Q3одного и того же аргумента, несколько раз дает разные результаты, поскольку точки поворота выбираются случайным образом. Обход результатов по порядку дает тот же отсортированный массив.

   Q3{1≥≢⍵:⍵  ( ⌿⍨0>s)(⌿⍨0=s)⍪⊂ ⌿⍨0<s ⍺⍺ ?≢}

   (×-) Q3 x
┌────────────────────────────────────────────┬─────┬┐
│┌──────────────┬─┬─────────────────────────┐│19 19││
││┌──────┬───┬─┐│6│┌──────┬─┬──────────────┐││     ││
│││┌┬─┬─┐│3 34││ ││┌┬─┬─┐│9│┌┬──┬────────┐│││     ││
│││││02││    ││ ││││78││ │││10│┌──┬──┬┐││││     ││
│││└┴─┴─┘│    ││ ││└┴─┴─┘│ │││  ││1415││││││     ││
││└──────┴───┴─┘│ ││       │││  │└──┴──┴┘││││     ││
││               ││       │└┴──┴────────┘│││     ││
││               │└──────┴─┴──────────────┘││     ││
│└──────────────┴─┴─────────────────────────┘│     ││
└────────────────────────────────────────────┴─────┴┘
   (×-) Q3 x
┌───────────────────────────┬─┬─────────────────────────────┐
│┌┬─┬──────────────────────┐│7│┌────────────────────┬─────┬┐│
│││0│┌┬─┬─────────────────┐││ ││┌──────┬──┬────────┐│19 19│││
│││ │││2│┌────────────┬─┬┐│││ │││┌┬─┬─┐│10│┌──┬──┬┐││     │││
│││ │││ ││┌───────┬─┬┐│6│││││ │││││89││  ││1415││││     │││
│││ │││ │││┌┬───┬┐│4│││ │││││ │││└┴─┴─┘│  │└──┴──┴┘││     │││
│││ │││ │││││3 3│││ │││ │││││ ││└──────┴──┴────────┘│     │││
│││ │││ │││└┴───┴┘│ │││ │││││ │└────────────────────┴─────┴┘│
│││ │││ ││└───────┴─┴┘│ │││││                              
│││ │││ │└────────────┴─┴┘│││                              
│││ │└┴─┴─────────────────┘││                              
│└┴─┴──────────────────────┘│                              
└───────────────────────────┴─┴─────────────────────────────┘

Приведенная выше формулировка не нова; см., например, рисунок 3.7 классической книги «Проектирование и анализ компьютерных алгоритмов» . Однако, в отличие от программы pidgin ALGOL на рис. 3.7, она Qявляется исполняемой, а частичный порядок, используемый при сортировке, является операндом, как в приведенных выше примерах. (×-)

Дфнс с операторами и поездами

Dfns, особенно анонимные, хорошо работают с операторами и поездами. Следующий фрагмент решает загадку "Programming Pearls": учитывая словарь английских слов, представленный здесь в виде матрицы символов a, найдите все наборы анаграмм.

   a            {[]}1 a        ({[]}1 {} ) a
pats         apst                ┌────┬────┬────┐
spat         apst                patsteasstar
teas         aest                spatsate    
sate         aest                tapsetas    
taps         apst                pastseat    
etas         aest                    eats    
past         apst                    tase    
seat         aest                    east    
eats         aest                    seta    
tase         aest                └────┴────┴────┘
star         arst
east         aest
seta         aest

Алгоритм работает путем сортировки строк по отдельности ( ), и эти отсортированные строки используются как ключи («подпись» в описании Programming Pearls) к ключевому оператору для группировки строк матрицы. Выражение справа - это поезд , синтаксическая форма, используемая APL для достижения неявного программирования . Здесь это изолированная последовательность из трех функций таких, что ⇔ , поэтому выражение справа эквивалентно . {[]}1 a(f g h) (f ) g (h )({[]}1 a) {} a

Лексический объем

Когда внутренний (вложенный) dfn ссылается на имя, он ищется, глядя вовне через включающие dfns, а не вниз по стеку вызовов . Говорят, что этот режим использует лексическую область видимости вместо обычной динамической области видимости APL . Различие становится очевидным, только если выполняется вызов функции, определенной на внешнем уровне. Для более обычных входящих вызовов эти два режима неотличимы.

Например, в следующей функции whichпеременная tyопределяется как whichсама по себе, так и во внутренней функции f1. Когда f1внешние вызовы f2и f2ссылаются на ty, он находит внешний (со значением 'lexical'), а не тот, который определен в f1(со значением 'dynamic'):

which{
  ty'lexical'
  f1{ty'dynamic'  f2 }
  f2{ty,}
  f1 
}

   which ' scope'
lexical scope

Охранник ошибок

Следующая функция иллюстрирует использование средств защиты от ошибок:

plus{
  tx'catch all'   0::tx
  tx'domain'     11::tx
  tx'length'      5::tx
  +
}      
   2 plus 3              ⍝ no errors
5
   2 3 4 5 plus 'three'  ⍝ argument lengths don't match
length
   2 3 4 5 plus 'four'   ⍝ can't add characters
domain
   2 3 plus 3 45        ⍝ can't add vector to matrix
catch all

В APL номер ошибки 5 - «ошибка длины»; номер ошибки 11 - «ошибка домена»; а номер ошибки 0 - это «уловка всех» для ошибок с номерами от 1 до 999.

В примере показано разворачивание локальной среды перед вычислением выражения защиты от ошибок. Локальное имя txустанавливается для описания области действия его следующей защиты от ошибок. При возникновении ошибки среда разворачивается, чтобы отобразить txстатически правильное значение.

Dfns против tradfns

Поскольку прямые функции - это dfns, функции APL, определенные традиционным способом, называются tradfns, произносится как «trad funs». Здесь dfns и tradfns сравниваются с учетом функции sieve: слева - dfn (как определено выше ); посередине - tradfn с использованием управляющих структур ; справа - tradfn с использованием gotos ( ) и меток строк .

sieve←{
  4≥⍵:⍵⍴0 0 1 1
  r←⌊0.5*⍨n←⍵
  p←2 3 5 7 11 13 17 19 23 29 31 37 41 43
  p←(1+(n≤×⍀p)⍳1)↑p
  b← 0@1 ⊃ {(m⍴⍵)>m⍴⍺↑1 ⊣ m←n⌊⍺×≢⍵}⌿ ⊖1,p
  {r<q←b⍳1:b⊣b[⍵]←1 ⋄ b[q,q×⍸b↑⍨⌈n÷q]←0 ⋄ ∇ ⍵,q}p
}

∇ b←sieve1 n;i;m;p;q;r
  :If 4≥n ⋄ b←n⍴0 0 1 1 ⋄ :Return ⋄ :EndIf
  r←⌊0.5*⍨n
  p←2 3 5 7 11 13 17 19 23 29 31 37 41 43
  p←(1+(n≤×⍀p)⍳1)↑p
  b←1
  :For q :In p ⋄ b←(m⍴b)>m⍴q↑1 ⊣ m←n⌊q×≢b ⋄ :EndFor
  b[1]←0
  :While r≥q←b⍳1 ⋄ b[q,q×⍸b↑⍨⌈n÷q]←0 ⋄ p⍪←q ⋄ :EndWhile
  b[p]←1
∇

∇ b←sieve2 n;i;m;p;q;r
  →L10 ⍴⍨ 4<n ⋄ b←n⍴0 0 1 1 ⋄ →0
 L10:
  r←⌊0.5*⍨n
  p←2 3 5 7 11 13 17 19 23 29 31 37 41 43
  p←(1+(n≤×\p)⍳1)↑p
  i←0 ⋄ b←1
 L20:
  b←(m⍴b)>m⍴p[i]↑1 ⊣ m←n⌊p[i]×≢b
  →L20 ⍴⍨ (≢p)>i←1+i
  b[1]←0
 L30:
  →L40 ⍴⍨ r<q←b⍳1 ⋄ b[q,q×⍸b↑⍨⌈n÷q]←0 ⋄ p⍪←q ⋄ →L30
 L40:
  b[p]←1
∇
  • Dfn может быть анонимным ; необходимо указать tradfn.
  • Имя dfn присваивается функцией assignment ( ); tradfn называется путем встраивания имени в представление функции и применения ⎕fx(системной функции) к этому представлению.
  • В качестве операнда dfn удобнее, чем tradfn (см. Предыдущие пункты: tradfn должен иметь имя; tradfn именуется путем встраивания ...).
  • Имена, присвоенные в dfn, по умолчанию являются локальными ; имена, присвоенные в tradfn, являются глобальными, если они не указаны в списке локальных .
  • Локальные переменные в dfn имеют лексическую область видимости ; Местные жители в tradfn имеют динамическую сферу , видимую в вызываемой функции , если не затененные от их списка местных жителей.
  • Аргументы dfn именуются, а операнды dop именуются ⍺⍺и ⍵⍵; аргументы и операнды tradfn могут иметь любое имя, указанное в его ведущей строке.
  • Результат (если есть) dfn безымянный; результат (если есть) tradfn назван в его заголовке.
  • Значение по умолчанию для ⍺ указано более точно, чем для левого аргумента tradfn.
  • Рекурсия в dfn осуществляется вызовом или ∇∇или его имени; рекурсия в tradfn осуществляется путем вызова его имени.
  • Управление потоком в dfn осуществляется охранниками и вызовами функций; что в tradfn - это управляющие структуры и (goto) и метки строк.
  • Оценка выражения в dfn, не заканчивающегося присваиванием, вызывает возврат из dfn; оценка строки в tradfn, не оканчивающейся на присваивание или goto, отображает результат строки.
  • Функция dfn возвращается при вычислении выражения, не заканчивающемся присваиванием, при вычислении защищенного выражения или после последнего выражения; tradfn возвращается на (goto) строке 0 или несуществующей строке, или при оценке структуры управления, или после последней строки.:Return
  • Более простое управление потоком в dfn упрощает обнаружение и реализацию хвостовой рекурсии, чем в tradfn.
  • Dfn может вызывать tradfn и наоборот ; dfn может быть определен в tradfn, и наоборот .

История

Кеннет Э. Айверсон , изобретатель APL, был недоволен способом определения пользовательских функций (tradfns). В 1974 году он разработал «формальное определение функции» или «прямое определение» для использования в экспозиции. Прямое определение состоит из двух или четырех частей, разделенных двоеточиями:

name : expression
name : expression0 : proposition : expression1

В прямом определении обозначает левый аргумент и правый аргумент. В первом случае результат expression- это результат функции; во втором случае результатом функции будет то, что expression0if propositionоценивается как 0 или expression1если оно оценивается как 1. Присваивания в пределах прямого определения являются динамически локальными . Примеры использования прямого определения можно найти в лекции премии Тьюринга 1979 года, а также в книгах и прикладных документах.

Прямое определение было слишком ограниченным для использования в более крупных системах. Идеи были развиты несколькими авторами в нескольких работах, но результаты были громоздкими. Из них «альтернативное определение функции APL» Бунда в 1987 году наиболее близко подошло к существующим возможностям, но имеет изъяны из-за конфликтов с существующими символами и обработки ошибок, которые вызвали бы практические трудности, и никогда не было реализовано. Основные преимущества различных предложений заключались в том, что (а) определяемая функция является анонимной, с последующим присвоением имен (если требуется); (b) функция обозначается символом и, таким образом, обеспечивает анонимную рекурсию .

В 1996 году Джон Скоулз из Dyalog Limited изобрел прямые функции (dfns). Идеи возникли в 1989 году, когда он прочитал специальный выпуск The Computer Journal о функциональном программировании. Затем он приступил к изучению функционального программирования и стал сильно мотивирован («болен желанием», как Йейтс ), чтобы донести эти идеи до APL. Первоначально он действовал скрытно, потому что опасался, что изменения могут быть сочтены слишком радикальными и ненужным усложнением языка; другие наблюдатели говорят, что он действовал незаметно, потому что коллеги по Дьялогу не были так очарованы и думали, что он зря тратит свое время и причиняет людям неприятности. Dfns были впервые представлены на форуме поставщиков Dyalog на конференции APL '96 и выпущены в Dyalog APL в начале 1997 года. Принятие и признание шло медленно. Уже в 2008 год в Dyalog на 25 , издание отмечает 25 - летие Dyalog Limited, dfns были почти не упоминались (дважды упоминаются как «динамические функции» и без разработки). С 2019 года dfns реализованы в Dyalog APL, NARS2000 и ngn / apl. Они также играют ключевую роль в усилиях по использованию вычислительных возможностей графического процессора (GPU).

использованная литература

внешние ссылки