Quais algoritmos podem ser expressos usando uma linguagem funcional total com operadores paralelos de dados?

11

Imagine uma linguagem de programação funcional cujos únicos tipos de dados são escalares numéricos e aninhamentos arbitrários de matrizes. O idioma não possui meios de iteração ilimitada, portanto, o seguinte não é permitido:

  • loops explícitos (pouco uso sem efeitos colaterais)
  • recursão
  • funções arbitrárias de primeira classe (sem combinador y)

A linguagem, no entanto, possui:

  • funções de nível superior
  • lexicamente, deixe ligações
  • fluxo de controle de ramificação
  • funções matemáticas e lógicas escalares comuns
  • algum construtor de matriz simples como fill (n, x), que cria uma matriz de n elementos com valores idênticos x
  • o mais importante: um conjunto restrito de operadores de ordem superior que executam iterações estruturadas paralelas (como mapear, reduzir, varrer, todos os pares).

Para ser mais específico sobre os operadores paralelos de dados:

  • y = mapa (f, x) => y [i] = f [i]
  • y = reduzir (f, a, x) => y = f (a, f (y [p [0]], f (y [p [1]], ...))) para alguma permutação p
  • y = varredura (f, a, x) => y [i] = redução (f, a, y [0 ... i-1])
  • y = allpairs (f, x, y) => y [i, j] = f (x [i], y [j])

Também poderíamos ter outros operadores, mas para qualificá-los, eles deveriam ter um tempo de execução polinomial, ser implementável sob algum modelo razoável de computação paralela de dados e usar no máximo espaço polinomial.

Obviamente, existem algumas construções que não podem ser expressas nessa linguagem, como:

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

O que podemos expressar neste sistema? Estamos limitados apenas a procurar problemas no FP? Podemos capturar todos os algoritmos de tempo polinomial? Além disso, existe algum conjunto mínimo de operadores para essa classe?

Alex Rubinsteyn
fonte

Respostas:

7

Você pode querer olhar para a antiga linguagem de programação NESL, que levou essas idéias a sério. A linguagem é baseada em operações em coleções, essencialmente listas e listas de listas e assim por diante, mas acho que árvores e gráficos também foram considerados, por meio de codificações complicadas. Algumas das pesquisas realizadas nesse projeto (em meados da década de 90) podem ajudar a responder sua pergunta. A tese de doutorado (disponível em livro) Guy E. Blelloch. Modelos vetoriais para computação paralela a dados. MIT Press, 1990 pode fornecer algumas respostas. Faz algum tempo desde que eu olhei.

O trabalho realizado no formalismo Bird-Meertens (BMF) também se enquadra nessa categoria, assim como o idioma Charity . Na página da Charity, na Wikipedia, diz que o idioma não é Turing completo, mas pode expressar a função de Ackermann, o que significa que é mais que recursivo primitivo. Tanto o BMF quanto o Charity envolvem operadores como dobras e varreduras (catamorfismos, anamorfismos etc.) e têm suas raízes na Teoria das Categorias.

A resposta curta e imprecisa é que você pode expressar bastante.

Dave Clarke
fonte
1
NESL não é um idioma total.
Por Vognsen 30/09/10
Estou superficialmente ciente do NESL há um tempo, mas acabei de ler pela primeira vez um dos artigos de Blelloch. Obrigado pela dica. O NESL é bem parecido com o idioma que descrevi acima, exceto que, como Per Vognsen notou, ele permite recursão.
Alex Rubinsteyn
Também estou interessado na escolha de operadores primitivos de Blelloch: map, dist (acredito que chamei de 'preenchimento'), comprimento, leitura de matriz, gravação de matriz, varredura, partição, nivelamento. As primitivas do NESL estão "completas" ou há alguma outra operação com uma implementação paralela de dados que não pode ser expressa com eficiência usando essas?
Alex Rubinsteyn
2
Remova a recursão, e você terá uma linguagem bastante expressiva, especialmente se considerar dobras e assim por diante. Observar o BMF e o trabalho a seguir pode ser mais interessante, então. Sinto muito, mas não estou atualizado nesta área.
Dave Clarke
7

Minha outra resposta apontou falhas no idioma como está. Nesta resposta, darei mais detalhes sobre os problemas com a coexistência de dobras e desdobramentos em um idioma total. Então, proponho uma resolução e mostrarei que todos os problemas em P (e de fato muitos mais) podem ser resolvidos nessa linguagem estendida.

Dobrar em um idioma total consome listas:

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

O desdobramento em um idioma total gera fluxos, que são potencialmente ilimitados:

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

Infelizmente, listas e fluxos vivem em mundos diferentes, portanto, esses operadores não podem ser compostos. Precisamos de uma correspondência parcial:

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

O operador de fluxo incorpora uma lista em um fluxo limitado. A função list extrai os primeiros n elementos (ou menos se o fluxo terminar anteriormente) em uma lista. Portanto, temos a seguinte equação:

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

Como uma otimização de eficiência, podemos cortar completamente os fluxos como uma estrutura de dados intermediária:

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

Agora esboçarei uma prova de que isso (com os outros operadores já implícitos na pergunta original) nos permite simular qualquer algoritmo de tempo polinomial.

Por definição, uma linguagem L está em P quando há uma máquina de Turing M e um polinômio p, de modo que a associação de x em L possa ser decidida executando M no máximo p (n) iterações em que n = | x |. Por um argumento padrão, o estado da fita da máquina na iteração k pode ser codificado com uma lista de comprimento no máximo 2k + 1, mesmo que a fita da máquina seja infinita.

A idéia agora é representar a transição de M como uma função de listas para listas. A execução da máquina será feita desdobrando o estado inicial com a função de transição. Isso gera um fluxo de listas. A suposição de que L está em P significa que não precisamos procurar mais do que p (n) elementos no fluxo. Assim, podemos compor o desdobramento list p(n)para obter uma lista finita. Finalmente, dobramos para verificar se a resposta para o problema de decisão foi sim ou não.

De maneira mais geral, isso mostra que sempre que temos um algoritmo cujo tempo de término pode ser limitado por uma função computável na linguagem, podemos simulá-lo. Isso também sugere por que algo como a função de Ackermann não pode ser simulado: é o seu próprio limite, então há um problema de galinha e ovo.

Per Vognsen
fonte
4

É uma linguagem muito limitada. Tente programar a função fatorial:

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

O problema é que seu idioma possui apenas dobras, mas não se desdobra. A maneira natural de expressar fatorial é compor um desdobramento de n na lista [1, 2, ..., n] com a dobra que o rasga enquanto se multiplica.

Você está realmente interessado nessa linguagem específica ou na programação funcional total em geral? É óbvio que sua linguagem pode, no máximo, expressar algoritmos de tempo polinomial. O sistema F (cálculo lambda polimórfico) pode expressar monstros como a função de Ackermann com facilidade. Mesmo que seu interesse seja por algoritmos de tempo polinomial, você frequentemente precisa da sala de cotovelo super-polinomial para expressá-los naturalmente.

Edit: Como Dave ressalta, o NESL é uma das linguagens de programação paralela de dados funcionais seminais, mas está muito longe do total (nem sequer tenta). A família APL tinha uma faixa paralela de evolução e possui um subconjunto algébrico total (evite os operadores de ponto fixo). Se o foco da sua pergunta é a totalidade, David Turner escreveu alguns bons artigos nessa área, embora não especificamente na programação paralela a dados.

Per Vognsen
fonte
Desculpe, a falta de operadores que se desdobram é uma supervisão da minha parte ... eu pretendia adicionar um, mas esqueci de colocá-lo no correio. Não estou necessariamente interessado nessa linguagem específica, mas na expressividade e nos limites da computação paralela de dados.
Alex Rubinsteyn
O desdobramento é naturalmente com valor de fluxo, não com valor de matriz, que é um problema básico com a correção em idiomas estritos totais.
Por Vognsen