É 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?


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';
}

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 ...


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).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.