É possível ter função de currying e variadic ao mesmo tempo?

13

Estou pensando em disponibilizar funções variadas e currying em uma linguagem de programação funcional de tipo dinâmico, mas me pergunto se é possível ou não.

Aqui estão alguns pseudocódigo:

sum = if @args.empty then 0 else @args.head + sum @args.tail

que supostamente soma todos os seus argumentos. Então, se sumfor tratado um número, o resultado será 0. por exemplo,

sum + 1

é igual a 1, assumindo que +só possa funcionar em números. No entanto, mesmo sum == 0é verdade, sumainda manterá seu valor e propriedade funcional, independentemente de quantos argumentos sejam fornecidos (portanto, "parcialmente aplicado" e "variável" ao mesmo tempo), por exemplo, se eu declarar

g = sum 1 2 3

então gé igual a 6, no entanto, ainda podemos aplicar g. Por exemplo, g 4 5 == 15é verdade. Nesse caso, não podemos substituir o objeto gpor um literal 6, porque, embora eles produzam o mesmo valor quando tratados como um número inteiro, eles contêm códigos diferentes.

Se esse design for usado em uma linguagem de programação real, causará alguma confusão ou ambiguidade?

Michael Tsang
fonte
1
Estritamente falando, usar o curry como fundamento de uma linguagem significa que todas as funções são unárias - não apenas não existem funções variadicas, nem mesmo binárias! No entanto, os programas nessa linguagem ainda parecerão ter recebido vários argumentos, e isso vale tanto para as funções variadas quanto para as normais.
Kilian Foth
Então minha pergunta é simplificada para "um objeto pode ser uma função e um valor não funcional ao mesmo tempo?" No exemplo acima, sumestá 0sem argumento e se chama recursivamente com um argumento.
Michael Tsang
esse não é o trabalho de reduce?
catraca aberração
1
Dê uma olhada nas funções que você está usando em args: empty, heade tail. Essas são todas as funções de lista, sugerindo que talvez a coisa mais fácil e direta a ser feita seria usar uma lista onde as coisas variadas seriam. (Então, sum [1, 2, 3]em vez de sum 1 2 3)
Michael Shaw

Respostas:

6

Como as varargs podem ser implementadas? Precisamos de algum mecanismo para sinalizar o fim da lista de argumentos. Isso pode ser

  • um valor especial de terminador ou
  • o comprimento da lista vararg passou como um parâmetro extra.

Ambos os mecanismos podem ser usados ​​no contexto de currying para implementar varargs, mas a digitação adequada se torna um problema importante. Vamos supor que estamos lidando com uma função sum: ...int -> int, exceto que essa função usa currying (então, na verdade, temos um tipo mais parecido sum: int -> ... -> int -> int, exceto que não sabemos o número de argumentos).

Caso: valor do terminador: Seja endo terminador especial e Tseja o tipo de sum. Sabemos agora que a aplicada endà função retorna: sum: end -> inte que aplicado a um int temos outra soma-como função: sum: int -> T. Portanto Té a união destes tipos: T = (end -> int) | (int -> T). Substituindo T, temos vários tipos possíveis, tais como end -> int, int -> end -> int, int -> int -> end -> int, etc. No entanto, a maioria dos sistemas do tipo não acomodam esses tipos.

Caso: comprimento explícito: O primeiro argumento para uma função vararg é o número de varargs. Assim sum 0 : int, sum 1 : int -> int, sum 3 : int -> int -> int -> intetc. Isto é suportado em alguns sistemas do tipo e é um exemplo de tipagem dependente . Na verdade, o número de argumentos seria um parâmetro de tipo e não um parâmetro regular - não faria sentido para o arity da função de depender de um valor de tempo de execução, s = ((sum (floor (rand 3))) 1) 2é, obviamente, mal-digitou: Isto avalia como quer s = ((sum 0) 1) 2 = (0 1) 2, s = ((sum 1) 1) 2 = 1 2ou s = ((sum 2) 1) 2 = 3.

Na prática, nenhuma dessas técnicas deve ser usada, pois são propensas a erros e não têm um tipo (significativo) em sistemas de tipos comuns. Em vez disso, apenas passar uma lista de valores como um paramter: sum: [int] -> int.

Sim, é possível que um objeto apareça como uma função e um valor, por exemplo, em um sistema de tipos com coerção. Let sumbe a SumObj, que tem duas coerções:

  • coerce: SumObj -> int -> SumObjpermite sumser usado como uma função e
  • coerce: SumObj -> int nos permite extrair o resultado.

Tecnicamente, essa é uma variação do caso do valor do terminador acima, com T = SumObje coercesendo um desempacotador para o tipo. Em muitas linguagens orientadas a objetos, isso é trivialmente implementável com sobrecarga do operador, por exemplo, C ++:

#include <iostream>
using namespace std;

class sum {
  int value;
public:
  explicit sum() : sum(0) {}
  explicit sum(int x) : value(x) {}
  sum operator()(int x) const { return sum(value + x); }  // function call overload
  operator int() const { return value; } // integer cast overload
};

int main() {
  int zero = sum();
  cout << "zero sum as int: " << zero << '\n';
  int someSum = sum(1)(2)(4);
  cout << "some sum as int: " << someSum << '\n';
}
amon
fonte
Resposta incrível! A desvantagem com as variedades de embalagem em uma lista é que você perde a aplicação parcial do curry. Eu estava brincando com uma versão Python da sua abordagem de terminador, usando um argumento de palavra-chave ..., force=False)para forçar a aplicação da função inicial.
ThomasH
Você pode criar sua própria função de ordem superior que aplica parcialmente uma função que recebe uma lista, como curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys).
Jack
2

Você pode examinar esta implementação do printf em Haskell , juntamente com esta descrição de como ele funciona . Na última página, há um link para o artigo de Oleg Kiselyov sobre esse tipo de coisa, que também vale a pena ler. De fato, se você está criando uma linguagem funcional, o site de Oleg provavelmente deve ser uma leitura obrigatória.

Na minha opinião, essas abordagens são um pouco complicadas, mas mostram que é possível. Se seu idioma possui digitação totalmente dependente, no entanto, é muito mais simples. Uma função variadica para somar seus argumentos inteiros pode ser algo parecido com isto:

type SumType = (t : union{Int,Null}) -> {SumType, if t is Int|
                                         Int,     if t is Null}
sum :: SumType
sum (v : Int) = v + sum
sum (v : Null) = 0

Uma abstração para definir o tipo recursivo sem precisar fornecer um nome explícito pode facilitar a gravação dessas funções.

Edit: é claro, acabei de ler a pergunta novamente e você disse uma linguagem de tipo dinâmico , quando obviamente a mecânica do tipo não é realmente relevante e, portanto, a resposta da @ amon provavelmente contém tudo o que você precisa. Bem, vou deixar isso aqui, caso alguém se depare com isso enquanto se pergunta sobre como fazê-lo em uma linguagem estática ...

Jules
fonte
0

Aqui está uma versão para currying de funções variadas no Python3 que usa a abordagem "terminator" do @amon, aproveitando os argumentos opcionais do Python:

def curry_vargs(g):
    actual_args = []
    def f(a, force=False):
        nonlocal actual_args
        actual_args.append(a)
        if force:
            res = g(*actual_args)
            actual_args = []
            return res
        else:
            return f
    return f

def g(*args): return sum(args)
f = curry_vargs(g)
f(1)(2)(3)(4,True) # => 10

A função retornada fcoleta os argumentos passados ​​a ela em chamadas sucessivas em uma matriz que está vinculada ao escopo externo. Somente quando o forceargumento é verdadeiro, a função original é chamada com todos os argumentos coletados até o momento.

Advertências nesta implementação são que você sempre deve passar um primeiro argumento para fque não possa criar um "thunk", uma função em que todos os argumentos estão vinculados e só podem ser chamados com a lista de argumentos vazia (mas acho que isso está alinhado com a implementação típica de curry).

Outra ressalva é que, depois de passar um argumento errado (por exemplo, do tipo errado), é necessário refazer a função original. Não há outra maneira de redefinir a matriz interna, isso é feito somente após uma execução bem-sucedida da função ao curry.

Não sei se sua pergunta simplificada, "um objeto pode ser uma função e um valor não funcional ao mesmo tempo?", Pode ser implementada no Python, pois uma referência a uma função sem parênteses é avaliada para o objeto interno da função . Não sei se isso pode ser dobrado para retornar um valor arbitrário.

Provavelmente seria fácil no Lisp, pois os símbolos Lisp podem ter um valor e um valor de função ao mesmo tempo; o valor da função é simplesmente selecionado quando o símbolo aparece na posição da função (como o primeiro elemento em uma lista).

ThomasH
fonte