2016-04-16 7 views
4

ich ein Buch lese, die das folgende Beispiel enthält:JavaScript komponieren Funktionen

var composition1 = function(f, g) { 
    return function(x) { 
    return f(g(x)); 
    } 
}; 

Dann schreibt der Autor:“... naive Umsetzung der Zusammensetzung, weil es nicht den Ausführungskontext nicht berücksichtigt .. So ist „

die bevorzugte Funktion, dass man:.

var composition2 = function(f, g) { 
    return function() { 
    return f.call(this, g.apply(this, arguments)); 
    } 
}; 

Gefolgt von einem ganzen Beispiel:

var composition2 = function composition2(f, g) { 
    return function() { 
     return f.call(this, g.apply(this, arguments)); 
    } 
}; 

var addFour = function addFour(x) { 
    return x + 4; 
}; 

var timesSeven = function timesSeven(x) { 
    return x * 7; 
}; 

var addFourtimesSeven2 = composition2(timesSeven, addFour); 
var result2 = addFourtimesSeven2(2); 
console.log(result2); 

Könnte mir bitte jemand erklären, warum die Funktion composition2 die bevorzugte ist (vielleicht mit einem Beispiel)?

EDIT:

In der Zwischenzeit habe ich versucht haben Methoden als Argumente zu verwenden, wie vorgeschlagen, aber es hat nicht funktioniert. Das Ergebnis war NaN:

var composition1 = function composition1(f, g) { 
    return function(x) { 
     return f(g(x)); 
    }; 
}; 

var composition2 = function composition2(f, g) { 
    return function() { 
     return f.call(this, g.apply(this, arguments)); 
    } 
}; 

var addFour = { 
    myMethod: function addFour(x) { 
     return x + this.number; 
    }, 
    number: 4 
}; 

var timesSeven = { 
    myMethod: function timesSeven(x) { 
     return x * this.number; 
    }, 
    number: 7 
}; 

var addFourtimesSeven1 = composition1(timesSeven.myMethod, addFour.myMethod); 
var result1 = addFourtimesSeven1(2); 
console.log(result1); 

var addFourtimesSeven2 = composition2(timesSeven.myMethod, addFour.myMethod); 
var result2 = addFourtimesSeven2(2); 
console.log(result2); 
+0

Das hat eigentlich nichts mit Curry zu tun. – Bergi

+0

Sie haben Recht, ich habe den Wortlaut geändert. – JShinigami

+0

Es heißt FUNKTION Zusammensetzung! Warum komponierst du Methoden, um Himmels willen? Funktionszusammensetzung bedeutet, reine Funktionen in Curry-Form ohne Nebenwirkungen im punktfreien Stil zu kombinieren: 'const compose = f => g => x => f (g (x))'. Curry-Funktionen mit Ausnahme nur eines einzigen Arguments (wie unsere 'compose'-Funktion). Funktionszusammensetzung funktioniert nur mit Curry-Funktionen, da eine Funktion nur einen einzelnen Wert zurückgibt. – ftor

Antwort

1

Das nur beantwortet, was composition2 tatsächlich tut:

composition2 verwendet, wenn Sie this als Kontext in den Funktionen selbst behalten wollen. Das folgende Beispiel zeigt, dass das Ergebnis 60 unter Verwendung data.a und data.b:

'use strict'; 

var multiply = function(value) { 
    return value * this.a; 
} 
var add = function(value) { 
    return value + this.b; 
} 

var data = { 
    a: 10, 
    b: 4, 
    func: composition2(multiply, add) 
}; 

var result = data.func(2); 
// uses 'data' as 'this' inside the 'add' and 'multiply' functions 
// (2 + 4) * 10 = 60 

Aber noch bricht es immer noch das folgende Beispiel (leider):

'use strict'; 

function Foo() { 
    this.a = 10; 
    this.b = 4; 
} 
Foo.prototype.multiply = function(value) { 
    return value * this.a; 
}; 
Foo.prototype.add = function(value) { 
    return value + this.b; 
}; 


var foo = new Foo(); 

var func = composition2(foo.multiply, foo.add); 
var result = func(2); // Uncaught TypeError: Cannot read property 'b' of undefined 

Da der Zusammenhang von composition2 (this) ist undefined (und wird nicht auf andere Weise aufgerufen, wie .apply, .call oder obj.func()), Sie würden mit this enden, die auch in den Funktionen undefined ist.

Auf der anderen Seite können wir es einen anderen Kontext geben, indem Sie den folgenden Code:

'use strict'; 
var foo = new Foo(); 

var data = { 
    a: 20, 
    b: 8, 
    func: composition2(foo.multiply, foo.add) 
} 

var result = data.func(2); 
// uses 'data' as 'this' 
// (2 + 8) * 10 = 200 :) 

Oder durch den Kontext explizit festlegen:

'use strict'; 

var multiply = function(value) { 
    return value * this.a; 
}; 
var add = function(value) { 
    return value + this.b; 
}; 


var a = 20; 
var b = 8; 

var func = composition2(multiply, add); 

// All the same 
var result1 = this.func(2); 
var result2 = func.call(this, 2); 
var result3 = func.apply(this, [2]); 
+0

Ich hoffe das macht Sinn, wenn es mich nicht wissen lässt. Ich werde versuchen, bei Bedarf umzuformulieren. – Caramiriel

+0

Nun, Sie können das tun, aber dann arbeiten Sie aktiv gegen die Sprache, die Sie verwenden. Werfen Sie einen Blick auf meine Antwort, warum es keinen Sinn macht, solche Dinge zu tun; obwohl du sie tun kannst. – Thomas

+0

Ich lasse diese Antwort als Referenz, was die 'composition2' tatsächlich tut, aber ich stimme dir zu.:) – Caramiriel

0

Zusammensetzung1 nicht Argumente übergeben andere ist als die erste zu g()

Wenn Sie das tun:

var composition1 = function(f, g) { 
    return function(x1, x2, x3) { 
    return f(g(x1, x2, x3)); 
    } 
}; 

die Funktion wird für die ersten drei Argumente arbeiten. Wenn Sie möchten, dass es für eine beliebige Nummer funktioniert, müssen Sie Function.prototype.apply verwenden.

f.call(...) wird verwendet, um this wie in Caramiriels Antwort gezeigt zu setzen.

0

ich mit dem Autor nicht einverstanden sind.

Denken Sie an den Anwendungsfall für die Funktionszusammensetzung.Meistens nutze ich die Funktionszusammensetzung für Transformer-Funktionen (reine Funktionen; Argument (en) in, Ergebnis out und this ist irrelevant).

2. Die Verwendung von arguments die Art, wie er es tut, führt zu einer schlechten Praxis/Sackgasse, weil es impliziert, dass die Funktion g() von mehreren Argumenten abhängen könnte.

Das bedeutet, dass die Zusammensetzung, die ich erstelle, nicht mehr zusammensetzbar ist, weil sie nicht alle Argumente bekommt, die sie benötigt.
Zusammensetzung, die Zusammensetzung verhindert; scheitern

(Und als Nebeneffekt: zu einer anderen Funktion die Argumente-Objekt übergeben ist ein Performance-no-go, weil die JS-Motor dies nicht mehr optimieren)

einen Blick auf das Nehmen Thema von partial application, in der Regel als currying in JS falsch referenziert, was im Grunde ist: Wenn nicht alle Argumente übergeben werden, gibt die Funktion eine andere Funktion zurück, die die verbleibenden Argumente übernimmt; Bis ich alle meine Argumente habe, muss ich sie verarbeiten.

Dann sollten Sie die Art und Weise, wie Sie die Argumentreihenfolge implementieren, überdenken, da dies am besten funktioniert, wenn Sie sie als configs-first, data-last definieren.
Beispiel:

//a transformer: value in, lowercased string out 
var toLowerCase = function(str){ 
    return String(str).toLowerCase(); 
} 

//the original function expects 3 arguments, 
//two configs and the data to process. 
var replace = curry(function(needle, heystack, str){ 
    return String(str).replace(needle, heystack); 
}); 

//now I pass a partially applied function to map() that only 
//needs the data to process; this is really composable 
arr.map(replace(/\s[A-Z]/g, toLowerCase)); 

//or I create another utility by only applying the first argument 
var replaceWhitespaceWith = replace(/\s+/g); 
//and pass the remaining configs later 
arr.map(replaceWhitespaceWith("-")); 

Ein etwas anderer Ansatz ist es, Funktionen zu erstellen, die sind, durch Design, bekommen nicht dazu gedacht, alle Argumente in einem Schritt vergangen, aber eins nach dem anderen (oder in sinnvolle Gruppen)

var prepend = a => b => String(a) + String(b); //one by one 
var substr = (from, to) => value => String(str).substr(from, to); //or grouped 

arr.map(compose(prepend("foo"), substr(0, 5))); 
arr.map(compose(prepend("bar"), substr(5))); 
//and the `to`-argument is undefined; by intent 

Ich habe nicht vor, solche Funktionen mit allen Argumenten aufzurufen, alles, was ich ihnen übergeben will, ist ihre Configs, und eine Funktion, die den Job auf die übergebenen Daten/Wert erfüllt.

Anstelle von substr(0, 5, someString) würde ich immer schreiben someString.substr(0, 5), also warum irgendwelche Anstrengungen unternehmen, um das letzte Argument (Daten) im ersten Aufruf anwendbar zu machen?