Quels algorithmes peuvent être exprimés en utilisant un langage fonctionnel total avec des opérateurs de données parallèles?


11

Imaginez un langage de programmation fonctionnel dont les seuls types de données sont des scalaires numériques et des imbrications arbitraires de tableaux. La langue ne dispose d'aucun moyen d'itération illimitée, les éléments suivants sont donc interdits:

  • boucles explicites (pas très utiles sans effets secondaires de toute façon)
  • récursivité
  • fonctions arbitraires de première classe (pas de combinateur y)

La langue a cependant:

  • fonctions de niveau supérieur
  • reliures let à portée lexicale
  • flux de contrôle de branchement
  • fonctions mathématiques et logiques scalaires communes
  • un constructeur de tableau simple comme fill (n, x) qui crée un tableau à n éléments avec des valeurs identiques x
  • le plus important: un ensemble restreint d'opérateurs d'ordre supérieur qui effectuent une itération structurée parallèle (comme la carte, la réduction, le balayage, toutes les paires).

Pour être plus précis sur les opérateurs parallèles de données:

  • y = carte (f, x) => y [i] = f [i]
  • y = réduire (f, a, x) => y = f (a, f (y [p [0]], f (y [p [1]], ...))) pour une certaine permutation p
  • y = scan (f, a, x) => y [i] = réduire (f, a, y [0 ... i-1])
  • y = toutes les paires (f, x, y) => y [i, j] = f (x [i], y [j])

Nous pourrions également avoir d'autres opérateurs, mais pour les qualifier, ils devraient avoir un temps d'exécution polynomial, être implémentables sous un modèle raisonnable de calcul parallèle de données et utiliser au maximum l'espace polynomial.

Il y a évidemment des constructions qui ne peuvent pas être exprimées dans ce langage, telles que:

while f(x) > tol:
    x <- update(x)   

Que pouvons-nous exprimer dans ce système? Sommes-nous limités uniquement aux problèmes de recherche dans FP? Pouvons-nous capturer tous les algorithmes de temps polynomiaux? Existe-t-il également un ensemble minimal d'opérateurs pour cette classe?

Réponses:


7

Vous voudrez peut-être regarder l'ancien langage de programmation NESL qui prenait ces idées au sérieux. Le langage est basé sur des opérations sur des collections, essentiellement des listes et des listes de listes et ainsi de suite, mais je pense que les arbres et les graphiques ont également été pris en compte, via des encodages délicats. Certaines des recherches effectuées dans le cadre de ce projet (au milieu des années 1990) pourraient aider à répondre à votre question. La thèse de doctorat (disponible sous forme de livre) Guy E. Blelloch. Modèles vectoriels pour l'informatique parallèle aux données. MIT Press, 1990 peut apporter quelques réponses. C'était il y a quelque temps depuis que je l'ai regardé.

Les travaux sur le formalisme Bird-Meertens (BMF) entrent également dans cette catégorie, tout comme le langage Charity . À partir de la page wikipedia de Charity, il est dit que le langage n'est pas Turing complet, mais peut exprimer la fonction d'Ackermann, ce qui signifie qu'il est plus que récursif primitif. BMF et Charity impliquent des opérateurs comme les plis et les scans (catamorphismes, anamorphismes, etc.), et ils ont leurs racines dans la théorie des catégories.

Ma réponse courte et imprécise est que vous pouvez exprimer beaucoup.


1
NESL n'est pas une langue totale.
Per Vognsen

Je connais superficiellement NESL depuis un certain temps, mais je viens de lire un des articles de Blelloch en détail pour la première fois. Merci pour le conseil. NESL est assez similaire au langage que j'ai décrit ci-dessus, sauf que, comme Per Vognsen l'a remarqué, il permet la récursivité.
Alex Rubinsteyn

Je suis également intéressé par le choix d'opérateurs primitifs de Blelloch: carte, dist (je crois la même chose que ce que j'ai appelé `` remplir ''), longueur, tableau lu, tableau écrit, scan, partition, aplatir. Les primitives de NESL sont-elles "complètes" ou existe-t-il une autre opération avec une implémentation parallèle de données qui ne peut pas être efficacement exprimée à l'aide de celles-ci?
Alex Rubinsteyn

2
Supprimez la récursivité, alors vous avez un langage assez expressif, surtout si vous considérez les plis et ainsi de suite. Regarder BMF et le travail qui en découle pourrait alors être plus intéressant. Je suis désolé, mais je ne suis pas à jour dans ce domaine.
Dave Clarke

7

Mon autre réponse a souligné des défauts dans la langue actuelle. Dans cette réponse, je donnerai plus de détails sur les problèmes de coexistence des plis et des dépliages dans une langue totale. Ensuite, je proposerai une résolution et montrerai que tous les problèmes en P (et bien d'autres encore) peuvent être résolus dans ce langage étendu.

Plier dans une langue totale consomme des listes:

fold :: (a -> b -> b) -> b -> List a -> b

Le déploiement dans une langue totale génère des flux potentiellement illimités:

unfold :: (b -> Maybe (a, b)) -> b -> Stream a

Malheureusement, les listes et les flux vivent dans des mondes différents, de sorte que ces opérateurs ne peuvent pas être composés. Nous avons besoin d'une correspondance partielle:

stream :: List a -> Stream a
list :: Int -> Stream a -> List a

L'opérateur de flux intègre une liste dans un flux borné. La fonction de liste extrait les n premiers éléments (ou moins si le flux se termine plus tôt) dans une liste. On a donc l'équation suivante:

for all xs :: List a, xs == list (length xs) (stream xs)

En tant qu'optimisation de l'efficacité, nous pouvons entièrement supprimer les flux en tant que structure de données intermédiaire:

unfoldList :: Int -> (b -> Maybe (a, b)) -> b -> List a

Je vais maintenant esquisser une preuve que cela (avec les autres opérateurs déjà impliqués dans la question d'origine) nous permet de simuler n'importe quel algorithme polynomial.

Par définition, un langage L est dans P quand il y a une machine de Turing M et un polynôme p de telle sorte que l'appartenance de x à L peut être décidée en exécutant M au plus p (n) itérations où n = | x |. Par un argument standard, l'état de la bande de la machine dans l'itération k peut être codé avec une liste de longueur au plus 2k + 1, même si la bande de la machine est infinie.

L'idée est maintenant de représenter la transition de M en fonction de listes en listes. L'exécution de la machine se fera en dépliant l'état initial avec la fonction de transition. Cela génère un flux de listes. L'hypothèse que L est dans P signifie que nous n'avons pas besoin de regarder plus loin que p (n) éléments dans le flux. Ainsi nous pouvons composer le déroulement avec list p(n)pour obtenir une liste finie. Enfin, nous le replions pour vérifier si la réponse au problème de décision était oui ou non.

Plus généralement, cela montre que chaque fois que nous avons un algorithme dont le temps de terminaison peut être limité par une fonction calculable dans le langage, nous pouvons le simuler. Cela suggère également pourquoi quelque chose comme la fonction d'Ackermann ne peut pas être simulé: c'est sa propre limite, donc il y a un problème de poulet et d'oeuf.


4

C'est une langue très guipée. Essayez de programmer la fonction factorielle:

fact 0 = 1
fact n = n * fact (n-1)

Le problème est que votre langue n'a que des plis mais ne se déplie pas. La manière naturelle d'exprimer la factorielle est de composer un dépliement de n dans la liste [1, 2, ..., n] avec le repliement qui le déchire en le multipliant.

Êtes-vous vraiment intéressé par ce langage spécifique ou par la programmation fonctionnelle totale en général? Il est évident que votre langue peut tout au plus exprimer des algorithmes en temps polynomial. Le système F (calcul lambda polymorphe) peut exprimer facilement des monstres comme la fonction d'Ackermann. Même si vous vous intéressez aux algorithmes à temps polynomial, vous avez souvent besoin de l'espace du coude super-polynomial pour les exprimer naturellement.

Edit: Comme le souligne Dave, NESL est l'un des langages de programmation fonctionnels parallèles aux données, mais il est très loin d'être total (il n'essaie même pas). La famille APL avait une trajectoire d'évolution parallèle et possède un sous-ensemble algébrique total (éviter les opérateurs à virgule fixe). Si l'objectif de votre question est la totalité, David Turner a écrit de bons articles dans ce domaine, mais pas spécifiquement sur la programmation parallèle aux données.


Désolé, le manque d'opérateurs de déroulement est un oubli de ma part ... Je voulais en ajouter un mais j'ai oublié de le mettre dans le message. Je ne m'intéresse pas nécessairement à ce langage spécifique mais plutôt à l'expressivité et aux limites du calcul parallèle de données.
Alex Rubinsteyn

Le dépliage est naturellement une valeur de flux, pas une valeur de tableau, ce qui est un problème de base avec la corecursion dans des langues strictes totales.
Per Vognsen
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.