Objeto de função - Function object

Na programação de computadores , um objeto de função é uma construção que permite que um objeto seja invocado ou chamado como se fosse uma função comum , geralmente com a mesma sintaxe (um parâmetro de função que também pode ser uma função). Os objetos de função costumam ser chamados de functores .

Descrição

Um uso típico de um objeto de função é escrever funções de retorno de chamada . Um retorno de chamada em linguagens procedurais , como C , pode ser executado usando ponteiros de função . No entanto, pode ser difícil ou estranho passar um estado para dentro ou para fora da função de retorno de chamada. Essa restrição também inibe o comportamento mais dinâmico da função. Um objeto de função resolve esses problemas, pois a função é na verdade uma fachada para um objeto completo, carregando seu próprio estado.

Muitas linguagens modernas (e algumas mais antigas), por exemplo, C ++ , Eiffel , Groovy , Lisp , Smalltalk , Perl , PHP , Python , Ruby , Scala e muitas outras, suportam objetos de função de primeira classe e podem até fazer uso significativo deles. As linguagens de programação funcional também suportam encerramentos , ou seja, funções de primeira classe que podem 'fechar' as variáveis ​​em seu ambiente circundante no momento da criação. Durante a compilação, uma transformação conhecida como levantamento lambda converte os fechamentos em objetos de função.

Em C e C ++

Considere o exemplo de uma rotina de classificação que usa uma função de retorno de chamada para definir uma relação de classificação entre um par de itens. O seguinte programa C usa ponteiros de função:

#include <stdlib.h>

/* qsort() callback function, returns < 0 if a < b, > 0 if a > b, 0 if a == b */
int compareInts(const void* a, const void* b)
{
    return (*(int *)a - *(int *)b));
}
...
// prototype of qsort is
// void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *));
...
int main(void)
{
    int items[] = { 4, 3, 1, 2 };
    qsort(items, sizeof(items) / sizeof(items[0]), sizeof(items[0]), compareInts);
    return 0;
}

Em C ++, um objeto de função pode ser usado em vez de uma função comum, definindo uma classe que sobrecarrega o operador de chamada de função , definindo uma operator() função de membro. Em C ++, isso pode aparecer da seguinte maneira:

// comparator predicate: returns true if a < b, false otherwise
struct IntComparator
{
  bool operator()(const int &a, const int &b) const
  {
    return a < b;
  }
};

int main()
{
    std::vector<int> items { 4, 3, 1, 2 };
    std::sort(items.begin(), items.end(), IntComparator());
    return 0;
}

Observe que a sintaxe para fornecer o retorno de chamada para a std::sort() função é idêntica, mas um objeto é passado em vez de um ponteiro de função. Quando chamada, a função de retorno de chamada é executada como qualquer outra função de membro e, portanto, tem acesso total aos outros membros (dados ou funções) do objeto. Claro, este é apenas um exemplo trivial. Para entender qual poder um functor fornece mais do que uma função regular, considere o caso de uso comum de classificar objetos por um campo específico. No exemplo a seguir, um functor é usado para classificar um banco de dados simples de funcionários pelo número de identificação de cada funcionário.

struct CompareBy
{
    const std::string SORT_FIELD;
    CompareBy(const std::string& sort_field="name")
      : SORT_FIELD(sort_field)
    {
        /* validate sort_field */
    }
    
    bool operator()(const Employee& a, const Employee& b)
    {
        if (SORT_FIELD == "name")
            return a.name < b.name;
        else if (SORT_FIELD == "age")
            return a.age < b.age;
        else if (SORT_FIELD == "idnum")
            return a.idnum < b.idnum;
        else
            /* throw exception or something */
    }
};

int main()
{
    std::vector<Employee> emps;
    
    /* code to populate database */
    
    // Sort the database by employee ID number
    std::sort(emps.begin(), emps.end(), CompareBy("idnum"));
    
    return 0;
}

No C ++ 11 , a expressão lambda fornece uma maneira mais sucinta de fazer a mesma coisa.

int main()
{
    std::vector<Employee> emps;
    /* code to populate database */
    const std::string sort_field = "idnum";
    std::sort(emps.begin(), emps.end(), [&sort_field](const Employee& a, const Employee& b){ /* code to select and compare field */ });
    return 0;
}


É possível usar objetos de função em situações diferentes das funções de retorno de chamada. Nesse caso, o termo abreviado functor normalmente não é usado sobre o objeto de função. Continuando o exemplo,

IntComparator cpm;
bool result = cpm(a, b);

Além dos functores de tipo de classe, outros tipos de objetos de função também são possíveis em C ++. Eles podem aproveitar as vantagens dos recursos de ponteiro de membro ou modelo do C ++ . A expressividade dos modelos permite que algumas técnicas de programação funcional sejam usadas, como a definição de objetos de função em termos de outros objetos de função (como composição de função ). Muito da C ++ Standard Template Library (STL) faz uso intenso de objetos de função baseados em template.

Mantendo estado

Outra vantagem dos objetos de função é sua capacidade de manter um estado que afeta operator() entre as chamadas. Por exemplo, o código a seguir define um gerador que conta de 10 para cima e é chamado 11 vezes.

#include <algorithm>
#include <iostream>
#include <iterator>

class CountFrom {
 public:
  CountFrom(int count) : count_(count) {}
  
  int operator()() { return count_++; }

 private:
  int count_;
};

int main() {
  const int state(10);
  std::generate_n(std::ostream_iterator<int>(std::cout, "\n"), 11,
                  CountFrom(state));
}

Em C ++ 14 ou posterior, o exemplo acima pode ser reescrito como:

#include <algorithm>
#include <iostream>
#include <iterator>

int main() {
  std::generate_n(std::ostream_iterator<int>(std::cout, "\n"), 11,
                  [count=10]() mutable { return count++; });
}

Em C #

Em C # , os objetos de função são declarados por meio de delegados . Um delegado pode ser declarado usando um método nomeado ou uma expressão lambda . Aqui está um exemplo usando um método nomeado.

using System;
using System.Collections.Generic;

public class ComparisonClass1
{
    public static int CompareFunction(int x, int y)
    {
        return x - y;
    }

    public static void Main()
    {
        var items = new List<int> { 4, 3, 1, 2 };
        Comparison<int> del = CompareFunction;
        items.Sort(del);
    }
}

Aqui está um exemplo usando uma expressão lambda.

using System;
using System.Collections.Generic;

public class ComparisonClass2
{
    public static void Main()
    {
        var items = new List<int> { 4, 3, 1, 2 };
        items.Sort((x, y) => x - y);
    }
}

Em D

D fornece várias maneiras de declarar objetos de função: estilo Lisp / Python por meio de fechamentos ou estilo C # por meio de delegados , respectivamente:

bool find(T)(T[] haystack, bool delegate(T) needle_test) {
  foreach (straw; haystack) {
    if (needle_test(straw))
      return true;
  }
  return false;
}

void main() {
    int[] haystack = [345, 15, 457, 9, 56, 123, 456];
    int   needle = 123;
    bool needleTest(int n) {
      return n == needle;
    }
    assert(find(haystack, &needleTest));
}

A diferença entre um delegado e um encerramento em D é determinada de forma automática e conservadora pelo compilador. D também suporta literais de função, que permitem uma definição de estilo lambda:

void main() {
    int[] haystack = [345, 15, 457, 9, 56, 123, 456];
    int   needle = 123;
    assert(find(haystack, (int n) { return n == needle; }));
}

Para permitir que o compilador incorpore o código (veja acima), os objetos de função também podem ser especificados no estilo C ++ por meio de sobrecarga de operador :

bool find(T, F)(T[] haystack, F needle_test) {
  foreach (straw; haystack) {
    if (needle_test(straw))
      return true;
  }
  return false;
}

void main() {
    int[] haystack = [345, 15, 457, 9, 56, 123, 456];
    int   needle = 123;
    class NeedleTest {
      int needle;
      this(int n) { needle = n; }
      bool opCall(int n) {
        return n == needle;
      }
    }
    assert(find(haystack, new NeedleTest(needle)));
}

Em eiffel

No método e na linguagem de desenvolvimento de software Eiffel , operações e objetos são vistos sempre como conceitos separados. No entanto, o mecanismo do agente facilita a modelagem de operações como objetos de tempo de execução. Os agentes satisfazem a gama de aplicações atribuídas a objetos de função, como serem passados ​​como argumentos em chamadas de procedimento ou especificados como rotinas de retorno de chamada. O projeto do mecanismo do agente em Eiffel tenta refletir a natureza orientada a objetos do método e da linguagem. Um agente é um objeto que geralmente é uma instância direta de uma das duas classes da biblioteca, que modelam os dois tipos de rotinas em Eiffel: PROCEDURE e FUNCTION . Essas duas classes descendem da mais abstrata ROUTINE .

No texto do software, a palavra-chave do idioma agent permite que os agentes sejam construídos de forma compacta. No exemplo a seguir, o objetivo é adicionar a ação de avançar o medidor à lista de ações a serem executadas caso um botão seja clicado.

my_button.select_actions.extend (agent my_gauge.step_forward)

A rotina extend referenciada no exemplo acima é um recurso de uma classe em uma biblioteca de interface gráfica com o usuário (GUI) para fornecer recursos de programação orientada a eventos .

Em outras classes de biblioteca, os agentes são usados ​​para finalidades diferentes. Em uma biblioteca que suporta estruturas de dados, por exemplo, uma classe que modela estruturas lineares efetua a quantificação universal com uma função for_all do tipo BOOLEAN que aceita um agente, uma instância de FUNCTION , como um argumento. Portanto, no exemplo a seguir, my_action é executado apenas se todos os membros de my_list contiverem o caractere '!'

    my_list: LINKED_LIST [STRING]
        ...
            if my_list.for_all (agent {STRING}.has ('!')) then
                my_action
            end
        ...

Quando os agentes são criados, os argumentos para as rotinas que modelam e até mesmo o objeto de destino ao qual são aplicados podem ser fechados ou deixados em aberto . Os argumentos e alvos fechados recebem valores no momento da criação do agente. A atribuição de valores para argumentos e alvos abertos é adiada até algum ponto após a criação do agente. A rotina for_all espera como um argumento um agente que representa uma função com um argumento aberto ou destino que está em conformidade com o parâmetro genérico real para a estrutura ( STRING neste exemplo).

Quando o destino de um agente é deixado aberto, o nome da classe do destino esperado, entre colchetes, é substituído por uma referência de objeto, conforme mostrado no texto agent {STRING}.has ('!') do exemplo acima. Quando um argumento é deixado em aberto, o caractere de ponto de interrogação ('?') É codificado como um espaço reservado para o argumento aberto.

A capacidade de fechar ou deixar alvos e argumentos abertos se destina a melhorar a flexibilidade do mecanismo do agente. Considere uma classe que contém o seguinte procedimento para imprimir uma string na saída padrão após uma nova linha:

    print_on_new_line (s: STRING)
            -- Print `s' preceded by a new line
        do
            print ("%N" + s)
        end

O fragmento a seguir, assumido estar na mesma classe, usa print_on_new_line para demonstrar a mistura de argumentos abertos e destinos abertos em agentes usados ​​como argumentos para a mesma rotina.

    my_list: LINKED_LIST [STRING]
        ...
            my_list.do_all (agent print_on_new_line (?))
            my_list.do_all (agent {STRING}.to_lower)
            my_list.do_all (agent print_on_new_line (?))
        ...

Este exemplo usa o procedimento do_all para estruturas lineares, que executa a rotina modelada por um agente para cada item da estrutura.

A seqüência de três instruções imprime as strings my_list , converte as strings em minúsculas e as imprime novamente.

O procedimento do_all itera em toda a estrutura, executando a rotina, substituindo o item atual pelo argumento aberto (no caso dos agentes baseados em print_on_new_line ) ou pelo destino aberto (no caso do agente baseado em to_lower ).

Argumentos e alvos abertos e fechados também permitem o uso de rotinas que exigem mais argumentos do que o necessário fechando todos, exceto o número necessário de argumentos:

my_list.do_all (agent my_multi_arg_procedure (closed_arg_1, ?, closed_arg_2, closed_arg_3)

O mecanismo do agente Eiffel é detalhado no documento padrão Eiffel ISO / ECMA .

Em Java

Java não tem funções de primeira classe , portanto, os objetos de função são geralmente expressos por uma interface com um único método (mais comumente a Callable interface), geralmente com a implementação sendo uma classe interna anônima ou, começando em Java 8, um lambda .

Para obter um exemplo da biblioteca padrão do Java, java.util.Collections.sort() usa um List e um functor cuja função é comparar objetos na Lista. Sem funções de primeira classe, a função faz parte da interface do Comparator. Isso pode ser usado da seguinte maneira.

List<String> list = Arrays.asList("10", "1", "20", "11", "21", "12");
		
Comparator<String> numStringComparator = new Comparator<String>() {
    public int compare(String str1, String str2) {
        return Integer.valueOf(str1).compareTo(Integer.valueOf(str2));
    }
};

Collections.sort(list, numStringComparator);

Em Java 8+, isso pode ser escrito como:

List<String> list = Arrays.asList("10", "1", "20", "11", "21", "12");
		
Comparator<String> numStringComparator = (str1, str2) -> Integer.valueOf(str1).compareTo(Integer.valueOf(str2));

Collections.sort(list, numStringComparator);

Em JavaScript

Em JavaScript , funções são objetos de primeira classe. JavaScript também oferece suporte a encerramentos.

Compare o seguinte com o exemplo Python subsequente.

function Accumulator(start) {
  var current = start;
  return function (x) {
    return current += x;
  };
}

Um exemplo disso em uso:

var a = Accumulator(4);
var x = a(5);   // x has value 9
x = a(2);       // x has value 11

var b = Accumulator(42);
x = b(7);       // x has value 49 (current = 49 in closure b)
x = a(7);       // x has value 18 (current = 18 in closure a)

Em julia

Em Julia , os métodos são associados a tipos, portanto, é possível tornar qualquer objeto Julia arbitrário "chamável" adicionando métodos a seu tipo. (Esses objetos "chamáveis" às vezes são chamados de "functores".)

Um exemplo é esta estrutura mutável acumuladora (com base no estudo de Paul Graham sobre a sintaxe e clareza da linguagem de programação):

julia> mutable struct Accumulator
           n::Int
       end

julia> function (acc::Accumulator)(n2)
           acc.n += n2
       end

julia> a = Accumulator(4)
Accumulator(4)

julia> a(5)
9

julia> a(2)
11

julia> b = Accumulator(42)
Accumulator(42)

julia> b(7)
49

Esse acumulador também pode ser implementado usando o fechamento:

julia> function Accumulator(n0)
           n = n0
           function(n2)
               n += n2
           end
       end
Accumulator (generic function with 1 method)

julia> a = Accumulator(4)
(::#1) (generic function with 1 method)

julia> a(5)
9

julia> a(2)
11

julia> b = Accumulator(42)
(::#1) (generic function with 1 method)

julia> b(7)
49

Em Lisp e Scheme

Em linguagens da família Lisp, como Common Lisp , Scheme e outras, funções são objetos, assim como strings, vetores, listas e números. Um operador de construção de fechamento cria um objeto de função a partir de uma parte do programa: a parte do código fornecida como um argumento para o operador é parte da função, assim como o ambiente léxico: as ligações das variáveis ​​lexicamente visíveis são capturadas e armazenado no objeto de função, que é mais comumente chamado de encerramento . As ligações capturadas desempenham o papel de variáveis de membro e a parte do código do encerramento desempenha o papel da função de membro anônima , assim como operator () em C ++.

O construtor de encerramento possui a sintaxe (lambda (parameters ...) code ...) . A (parameters ...) parte permite que uma interface seja declarada, de forma que a função receba os parâmetros declarados. A code ... parte consiste em expressões que são avaliadas quando o functor é chamado.

Muitos usos de functores em linguagens como C ++ são simplesmente emulações do construtor de fechamento ausente. Uma vez que o programador não pode construir diretamente um encerramento, ele deve definir uma classe que tenha todas as variáveis ​​de estado necessárias e também uma função de membro. Em seguida, construa uma instância dessa classe, garantindo que todas as variáveis ​​de membro sejam inicializadas por meio de seu construtor. Os valores são derivados precisamente das variáveis ​​locais que devem ser capturadas diretamente por um fechamento.

Um objeto-função usando o sistema de classes, sem uso de encerramentos:

(defclass counter ()
  ((value :initarg :value :accessor value-of)))

(defmethod functor-call ((c counter))
  (incf (value-of c)))

(defun make-counter (initial-value)
  (make-instance 'counter :value initial-value))

;;; use the counter:
(defvar *c* (make-counter 10))
(functor-call *c*) --> 11
(functor-call *c*) --> 12

Como não há uma maneira padrão de criar objetos funcionais em Lisp, nós o falsificamos definindo uma função genérica chamada FUNCTOR-CALL. Isso pode ser especializado para qualquer classe. A função FUNCALL padrão não é genérica; ele só aceita objetos de função.

É essa função genérica FUNCTOR-CALL que nos fornece objetos de função, que são uma construção de programação de computador que permite que um objeto seja invocado ou chamado como se fosse uma função comum, geralmente com a mesma sintaxe. Temos quase a mesma sintaxe: FUNCTOR-CALL em vez de FUNCALL. Alguns Lisps fornecer funcallable objetos como uma simples extensão. Tornar objetos chamáveis ​​usando a mesma sintaxe das funções é um negócio bastante trivial. Fazer um operador de chamada de função trabalhar com diferentes tipos de coisas de função , sejam eles objetos de classe ou fechamentos, não é mais complicado do que fazer um operador + que funcione com diferentes tipos de números, como inteiros, reais ou números complexos.

Agora, um contador implementado usando um fechamento. Isso é muito mais breve e direto. O argumento INITIAL-VALUE da função de fábrica MAKE-COUNTER é capturado e usado diretamente. Ele não precisa ser copiado para algum objeto de classe auxiliar por meio de um construtor. Ele é o contador. Um objeto auxiliar é criado, mas isso acontece nos bastidores .

(defun make-counter (value)
  (lambda () (incf value)))

;;; use the counter
(defvar *c* (make-counter 10))
(funcall *c*) ; --> 11
(funcall *c*) ; --> 12

O Scheme torna os fechamentos ainda mais simples, e o código do Scheme tende a usar essa programação de ordem superior de forma um pouco mais linguística.

(define (make-counter value)
  (lambda () (set! value (+ value 1)) value))
;;; use the counter
(define c (make-counter 10))
(c) ; --> 11
(c) ; --> 12

Mais de um fechamento pode ser criado no mesmo ambiente lexical. Um vetor de fechamentos, cada um implementando um tipo específico de operação, pode emular com bastante fidelidade um objeto que possui um conjunto de operações virtuais. Esse tipo de programação orientada a objeto de despacho único pode ser feito totalmente com fechamentos.

Portanto, existe uma espécie de túnel sendo cavado de ambos os lados da montanha proverbial. Os programadores em linguagens OOP descobrem objetos de função restringindo os objetos para ter uma função principal para fazer o propósito funcional daquele objeto e até mesmo eliminar seu nome para que pareça que o objeto está sendo chamado! Embora os programadores que usam encerramentos não fiquem surpresos com o fato de um objeto ser chamado como uma função, eles descobrem que vários encerramentos compartilhando o mesmo ambiente podem fornecer um conjunto completo de operações abstratas como uma tabela virtual para um único tipo de despacho OOP.

Em Objective-C

Em Objective-C , um objeto de função pode ser criado a partir da NSInvocation classe. A construção de um objeto de função requer uma assinatura de método, o objeto de destino e o seletor de destino. Aqui está um exemplo para criar uma invocação para o objeto atual myMethod :

// Construct a function object
SEL sel = @selector(myMethod);
NSInvocation* inv = [NSInvocation invocationWithMethodSignature:
                     [self methodSignatureForSelector:sel]];
[inv setTarget:self];
[inv setSelector:sel];

// Do the actual invocation
[inv invoke];

Uma vantagem NSInvocation é que o objeto de destino pode ser modificado após a criação. Um único NSInvocation pode ser criado e então chamado para cada um de qualquer número de alvos, por exemplo, a partir de um objeto observável. Um NSInvocation pode ser criado apenas a partir de um protocolo, mas não é simples. Veja aqui .

Em Perl

Em Perl , um objeto de função pode ser criado a partir de um construtor de classe, retornando uma função fechada sobre os dados de instância do objeto, abençoada na classe:

package Acc1;
sub new {
    my $class = shift;
    my $arg = shift;
    my $obj = sub {
        my $num = shift;
        $arg += $num;
    };
    bless $obj, $class;
}
1;

ou sobrecarregando o &{} operador para que o objeto possa ser usado como uma função:

package Acc2;
use overload
    '&{}' =>
        sub {
            my $self = shift;
            sub {
                my $num = shift;
                $self->{arg} += $num;
            }
        };

sub new {
    my $class = shift;
    my $arg = shift;
    my $obj = { arg => $arg };
    bless $obj, $class;
}
1;

Em ambos os casos, o objeto de função pode ser usado usando a sintaxe de seta de desreferenciação $ ref -> (@ argumentos) :

use Acc1;
my $a = Acc1->new(42);
print $a->(10), "\n";    # prints 52
print $a->(8), "\n";     # prints 60

ou usando o coderef dereferencing sintaxe & $ ref (@arguments) :

use Acc2;
my $a = Acc2->new(12);
print &$a(10), "\n";     # prints 22
print &$a(8), "\n";      # prints 30

Em PHP

PHP 5.3+ tem funções de primeira classe que podem ser usadas, por exemplo, como parâmetro para a função usort ():

$a = array(3, 1, 4);
usort($a, function ($x, $y) { return $x - $y; });

PHP 5.3+, também suporta funções lambda e encerramentos.

function Accumulator($start)
{
    $current = $start;
    return function($x) use(&$current)
    {
        return $current += $x;
    };
}

Um exemplo disso em uso:

$a = Accumulator(4);
$x = $a(5);
echo "x = $x<br/>";	// x = 9
$x = $a(2);
echo "x = $x<br/>";	// x = 11

Também é possível no PHP 5.3+ tornar objetos invocáveis ​​adicionando um método mágico __invoke () à sua classe:

class Minus
{
    public function __invoke($x, $y)
    {
        return $x - $y;
    }
}

$a = array(3, 1, 4);
usort($a, new Minus());

Em PowerShell

Na linguagem Windows PowerShell , um bloco de script é uma coleção de instruções ou expressões que podem ser usadas como uma única unidade. Um bloco de script pode aceitar argumentos e retornar valores. Um bloco de script é uma instância de um tipo System.Management.Automation.ScriptBlock do Microsoft .NET Framework .

Function Get-Accumulator($x) {
    {
        param($y)
        return $x += $y
    }.GetNewClosure()
}
PS C:\> $a = Get-Accumulator 4
PS C:\> & $a 5
9
PS C:\> & $a 2
11
PS C:\> $b = Get-Accumulator 32
PS C:\> & $b 10
42

Em Python

Em Python , as funções são objetos de primeira classe, assim como strings, números, listas etc. Esse recurso elimina a necessidade de escrever um objeto de função em muitos casos. Qualquer objeto com um __call__() método pode ser chamado usando a sintaxe de chamada de função.

Um exemplo é esta classe acumuladora (baseada no estudo de Paul Graham sobre a sintaxe e clareza da linguagem de programação):

class Accumulator:
    def __init__(self, n) -> None:
        self.n = n

    def __call__(self, x):
        self.n += x
        return self.n

Um exemplo disso em uso (usando o interpretador interativo):

>>> a = Accumulator(4)
>>> a(5)
9
>>> a(2)
11
>>> b = Accumulator(42)
>>> b(7)
49

Como as funções são objetos, elas também podem ser definidas localmente, dados atributos e retornados por outras funções, conforme demonstrado no exemplo a seguir:

def Accumulator(n):
    def inc(x):
        nonlocal n
        n += x
        return n
    return inc

Em Ruby

Em Ruby , vários objetos podem ser considerados objetos de função, em particular objetos Method e Proc. Ruby também tem dois tipos de objetos que podem ser considerados objetos de semi-função: UnboundMethod e block. UnboundMethods deve primeiro ser vinculado a um objeto (tornando-se assim um Método) antes de poderem ser usados ​​como um objeto de função. Os blocos podem ser chamados como objetos de função, mas para serem usados ​​em qualquer outra capacidade como um objeto (por exemplo, passados ​​como um argumento), eles devem primeiro ser convertidos para um Proc. Mais recentemente, os símbolos (acessados ​​por meio do indicador unário literal : ) também podem ser convertidos em Proc s. Usando o & operador unário de Ruby - equivalente a chamar to_proc um objeto e assumindo que o método existe - o Projeto de extensões Ruby criou um hack simples.

class Symbol
  def to_proc
    proc { |obj, *args| obj.send(self, *args) }
  end
end

Agora, o método foo pode ser um objeto de função, ou seja Proc , a , via &:foo e usado via takes_a_functor(&:foo) . Symbol.to_proc foi adicionado oficialmente ao Ruby em 11 de junho de 2006 durante o RubyKaigi2006. [1]

Devido à variedade de formas, o termo Functor geralmente não é usado em Ruby para significar um objeto Function. Apenas um tipo de delegação de despacho introduzido pelo projeto Ruby Facets é denominado Functor. A definição mais básica é:

class Functor
  def initialize(&func)
    @func = func
  end
  def method_missing(op, *args, &blk)
    @func.call(op, *args, &blk)
  end
end

Esse uso é mais semelhante ao usado por linguagens de programação funcionais, como ML , e a terminologia matemática original.

Outros significados

Em um contexto mais teórico, um objeto de função pode ser considerado qualquer instância da classe de funções, especialmente em linguagens como Common Lisp, nas quais as funções são objetos de primeira classe .

A família ML de linguagens de programação funcional usa o termo functor para representar um mapeamento de módulos para módulos ou de tipos para tipos e é uma técnica para reutilizar código. Os funções usados ​​desta maneira são análogos ao significado matemático original de functor na teoria das categorias , ou ao uso de programação genérica em C ++, Java ou Ada .

Em Haskell , o termo é usado no mesmo sentido que na teoria das categorias.

Em Prolog e linguagens relacionadas, functor é sinônimo de símbolo de função .

Veja também

Notas

Referências

Leitura adicional

  • David Vandevoorde e Nicolai M Josuttis (2006). Modelos C ++: o guia completo , ISBN   0-201-73484-2 : Especificamente, o capítulo 22 é dedicado a objetos de função.

links externos