Представьте себе функциональный язык программирования, единственными типами данных которого являются числовые скаляры и произвольные вложения массивов. В языке отсутствуют какие-либо средства неограниченной итерации, поэтому запрещено следующее:
- явные циклы (во всяком случае, без побочных эффектов)
- рекурсия
- произвольные функции первого класса (без y-комбинатора)
Язык, однако, имеет:
- функции верхнего уровня
- лексически ограниченные пусть привязки
- ветвление потока управления
- общие скалярные математические и логические функции
- некоторый простой конструктор массива, такой как fill (n, x), который создает массив из n элементов с одинаковыми значениями x
- самое главное: ограниченный набор операторов высшего порядка, которые выполняют параллельные структурированные итерации (такие как отображение, уменьшение, сканирование, все пары).
Чтобы быть более конкретным о параллельных операторах данных:
- y = map (f, x) => y [i] = f [i]
- y = уменьшить (f, a, x) => y = f (a, f (y [p [0]], f (y [p [1]], ...))) для некоторой перестановки p
- y = scan (f, a, x) => y [i] = уменьшить (f, a, y [0 ... i-1])
- y = allpairs (f, x, y) => y [i, j] = f (x [i], y [j])
У нас могут быть и другие операторы, но для квалификации они должны иметь полиномиальное время выполнения, быть реализуемыми в рамках некоторой разумной модели параллельных вычислений данных и использовать не более чем полиномиальное пространство.
Очевидно, есть некоторые конструкции, которые не могут быть выражены на этом языке, такие как:
while f(x) > tol:
x <- update(x)
Что мы можем выразить в этой системе? Мы ограничены только проблемами поиска в FP? Можем ли мы охватить все алгоритмы полиномиального времени? Кроме того, есть ли минимальный набор операторов для этого класса?
источник
Мой другой ответ указал на недостатки языка в его нынешнем виде. В этом ответе я более подробно расскажу о проблемах сосуществования складок и разворотов на общем языке. Затем я предложу решение и покажу, что все проблемы в P (и многие другие) могут быть решены на этом расширенном языке.
Складывание на общем языке потребляет списки:
Развертывание на общем языке генерирует потоки, которые потенциально не ограничены:
К сожалению, списки и потоки живут в разных мирах, поэтому эти операторы не могут быть составлены. Нам нужна частичная переписка:
Оператор потока встраивает список в ограниченный поток. Функция list извлекает первые n элементов (или меньше, если поток заканчивается раньше) в список. Таким образом, мы имеем следующее уравнение:
В качестве оптимизации эффективности мы можем полностью вырезать потоки в качестве промежуточной структуры данных:
Сейчас я приведу набросок доказательства того, что это (с учетом других операторов, уже подразумеваемых в исходном вопросе) позволяет нам моделировать любой алгоритм за полиномиальное время.
По определению, язык L находится в P, когда есть машина Тьюринга M и такой многочлен p, что членство в x в L можно определить, выполнив M не более p (n) итераций, где n = | x |. С помощью стандартного аргумента состояние ленты машины в итерации k может быть закодировано с помощью списка длиной не более 2k + 1, даже если лента машины бесконечна.
Идея теперь состоит в том, чтобы представить переход M как функцию от списков к спискам. Выполнение машины будет выполнено путем развертывания исходного состояния с помощью функции перехода. Это создает поток списков. Предположение, что L находится в P, означает, что нам нужно смотреть не дальше, чем p (n) элементов в поток. Таким образом, мы можем составить развертывание,
list p(n)
чтобы получить конечный список. Наконец, мы сворачиваем его, чтобы проверить, был ли ответ на решение проблемы «да» или «нет».В более общем смысле это показывает, что всякий раз, когда у нас есть алгоритм, время завершения которого может быть ограничено функцией, вычисляемой в языке, мы можем его смоделировать. Это также наводит на мысль, почему что-то вроде функции Аккермана не может быть смоделировано: это ее собственная граница, поэтому возникает проблема курицы и яйца.
источник
Это очень приукрашенный язык. Попробуйте запрограммировать факториальную функцию:
Проблема в том, что ваш язык имеет только сгибы, но не разворачивается. Естественный способ выражения факториала состоит в том, чтобы складывать n в список [1, 2, ..., n] со складыванием, которое разрывает его при умножении.
Вы действительно интересуетесь этим конкретным языком или общим функциональным программированием в целом? Очевидно, что ваш язык может максимально выражать алгоритмы полиномиального времени. Система F (полиморфное лямбда-исчисление) может с легкостью выражать таких монстров, как функция Аккермана. Даже если вы заинтересованы в алгоритмах полиномиального времени, вам часто требуется пространство для суперполиномиального локтя, чтобы выразить их естественным образом.
Редактирование: Как указывает Дейв, NESL является одним из основополагающих функциональных языков параллельного программирования данных, но он очень далек от общего (даже не пытается). Семейство APL имело параллельный путь эволюции и имеет полное алгебраическое подмножество (избегайте операторов с фиксированной точкой). Если ваш вопрос сфокусирован на целостности, Дэвид Тернер написал несколько хороших статей в этой области, хотя не специально для параллельного программирования данных.
источник