Differenze tra i meccanismi della "funzione di prima classe"

4

Alcune lingue (Javascript, Python) hanno la nozione che una funzione è un oggetto:

//Javascript
var fn = console.log;

Ciò significa che le funzioni possono essere trattate come qualsiasi altro oggetto ( funzioni di prima classe ), ad es. passato come argomento ad un'altra funzione:

var invoker = function(toInvoke) {
    toInvoke();
};
invoker(fn); //will call console.log

Altre lingue (C ++, C #, VB.NET) non definiscono le funzioni come oggetti reali:

//C#
Type t = Console.WriteLine.GetType();
//This code will not compile, because:
//"'Console.WriteLine()' is a method, which is not valid in the given context"

Piuttosto questi linguaggi possono avere oggetti che possono puntare a una funzione (come i puntatori di funzione C ++) e possono essere passati in giro come qualsiasi altro oggetto. Nella CLI, questi oggetti wrapper sono chiamati delegati o istanze delegate :

//C#
void Invoker(Action toInvoke) {
    toInvoke();
}

Action action = Console.WriteLine;
Invoker(action);

//also valid, and the toInvoke argument will now contain a delegate which points to Console.WriteLine
//Invoker(Console.WriteLine);

Quali differenze di capacità derivano da questi due meccanismi: "oggetto funzione" vs "puntatore-a-funzione come oggetto"?

    
posta Zev Spitz 25.03.2016 - 07:18
fonte

3 risposte

2

Vedo due aree di comportamento diverso che derivano dai due meccanismi:

  1. L'oggetto puntatore è un oggetto separato dalla funzione di riferimento e avrà un'identità separata dalla funzione e da altri puntatori alla funzione
  2. L'oggetto puntatore può avere comportamenti oltre a quelli della funzione originale. Ad esempio, nella CLI:
    • Le istanze delegate conoscono il relativo this (e possono associarlo al metodo)
    • Le istanze delegate possono contenere puntatori a più funzioni

(Nota: questo deriva dalla mia esperienza in C # / VB.NET e Javascript.Altre lingue possono avere diverse varianti di entrambi i meccanismi.)

Equality di riferimento all'oggetto

Se una funzione è un "oggetto reale", qualsiasi variabile che punta alla funzione punta effettivamente allo stesso oggetto:

//Javascript
var fn = console.log;
var fn1 = console.log;
console.log(fn === fn1); //prints true

Gli oggetti puntatore hanno una propria identità, anche quando entrambi puntano alla stessa funzione:

//C#
Action action = Console.WriteLine;
Action action2 = Console.WriteLine;
Console.WriteLine(Object.ReferenceEquals(action, action2)); //prints False

Comportamenti aggiuntivi dell'oggetto puntatore

Associazione target

Gli oggetti funzione non hanno alcuna conoscenza della classe a cui sono collegati:

//Javascript
var a1 = {
    data: 5,
    writeData: function() {
        'use strict'; //otherwise 'this' would be the global object; 'this.data' would probably return 'undefined'
        console.log(this.data);
    }
};

var action = a1.writeData;
action(); //Uncaught TypeError: Cannot read property 'data' of undefined

Pertanto, parte del chiamare la funzione come metodo di istanza, è il vincolo implicito di this all'interno della funzione dell'oggetto:

a1.writeData(); //prints 5

Possiamo anche legare esplicitamente this con bind , apply o call :

action = a1.writeData.bind(a1);
action(); //prints 5

Tuttavia (come @amon ha sottolineato in questa risposta ), l'istanza delegata conserva tali informazioni:

//C#
public class A {
    public int Data;
    public void WriteData() {
        Console.WriteLine(this.Data);
    }
}

var a1 = new A() {Data=4};
Action action = a1.WriteData;

perché action contiene la conoscenza della destinazione dei metodi:

Console.WriteLine(action.Target == a1); //prints True

Delegato multicast

Le variabili / proprietà Javascript che si riferiscono a un oggetto funzione funzionano come riferimenti a qualsiasi altro oggetto e pertanto non possono riferirsi a più oggetti funzione contemporaneamente.

D'altro canto, un'istanza delegata in .NET può puntare a più funzioni:

//C#
public static class Writers {
    public static void WriteOne() {
        Console.WriteLine(1);
    }
    public static void WriteTwo() {
        Console.WriteLine(2);
    }
}

action = Writers.WriteOne;
action += Writers.WriteTwo;
action(); //prints 1, and then prints 2
    
risposta data 28.03.2016 - 21:18
fonte
5

I due pezzi di codice non sono equivalenti tra loro. Una lingua può essere implementata in modo tale che ogni metodo possa essere usato direttamente come oggetto di prima classe. Ciò influisce sull'ABI, sulla convocazione della convenzione e sul meccanismo di collegamento, ma non è straordinariamente speciale. La maggior parte delle implementazioni linguistiche moderne allegano già molti metadati a ciascuna funzione.

Tuttavia, il significato di object.method differisce sostanzialmente tra il suo uso nell'assegnazione dei delegati e il suo uso nella semplice assegnazione delle variabili:

  • In C #, il codice Func<…> m = obj.method; m() equivale a obj.method() . Cioè, un Func<…> un qualche tipo di oggetto che conosce a quale oggetto appartiene (cioè il metodo è "legato" a un oggetto specifico). Questo è esattamente equivalente a una chiusura. Pertanto il Func<…> risultante deve ricordare sia il metodo che l'oggetto target. Poiché un metodo può essere associato a più di un oggetto, ogni associazione genera un nuovo valore.

  • Al contrario, obj.method in JavaScript risolve semplicemente il metodo senza associarlo a un oggetto. Dobbiamo farlo da soli: obj.method() equivale a var m = obj.method.bind(obj); m() . Vedrai che legare un metodo a diversi oggetti risulterà in valori non uguali l'uno all'altro, mentre ovviamente il metodo non associato è identico.

In generale, preferisco l'approccio C #, dove c'è una semantica equivalente tra obj.method() e tutti gli usi disponibili di obj.method . D'altra parte, in Javascript, obj.method() introduce una semantica diversa dal obj.method simile.

    
risposta data 25.03.2016 - 11:36
fonte
0

Anche se ci sono delle differenze come si nota, è forse importante considerare che una chiusura (cioè una funzione con variabili locali catturate localmente, come ad esempio esiste in Javascript) è computazionalmente equivalente a un oggetto con un unico metodo. Dal punto di vista di ciò che può essere fatto con loro, almeno a livello teorico, non c'è differenza tra loro. In pratica, esistono due categorie di differenze effettive:

  • Differenze sintattiche (ovvero come interagiscono con le caratteristiche del linguaggio come il modo in cui vengono create, se hanno bisogno di una sintassi speciale da richiamare, quali operazioni - come la composizione delle funzioni - possono essere eseguite automaticamente su di esse senza dover scrivere adattatori e così via)

  • Differenze di compatibilità (vale a dire se possono interagire o meno con codice preesistente).

Entrambe le classi di differenza possono essere superate scrivendo adattatori banali che convertono tra una chiusura o un oggetto come richiesto.

    
risposta data 08.07.2016 - 09:34
fonte

Leggi altre domande sui tag