Informationen zur Zusammensetzung von Funktionen in JavaScript

Lassen Sie uns über die funktionale Komposition fantasieren und die Bedeutung des Kompositions- / Pipeline-Operators klären.


TL; DR
Compose-Funktionen wie ein Chef:
Bild
Beliebte Implementierungen compose- Beim Aufruf werden neue und neue Funktionen erstellt, die auf Rekursion basieren, welche Nachteile bestehen und wie man sie umgehen kann.


Sie können die Compose-Funktion als reine Funktion betrachten, die nur von den Argumenten abhängt. Wenn wir also die gleichen Funktionen in derselben Reihenfolge zusammenstellen, müssen wir eine identische Funktion erhalten, aber in der JavaScript-Welt ist dies nicht der Fall. Jeder Composer-Aufruf führt zu einer neuen Funktion. Dies führt zur Erstellung von immer mehr neuen Funktionen im Speicher sowie zu den Fragen zu deren Memoisierung, Vergleich und Debugging.
Muss etwas tun.


Motivation


  • Assoziative Identität erhalten:

Es ist sehr wünschenswert, keine neuen Objekte zu erstellen und die vorherigen Ergebnisse der Compose-Funktion wiederzuverwenden. Eines der Probleme Reaktion des Entwicklers - die Implementierung von shallowCompare, die mit dem Ergebnis der Zusammenstellung von Funktionen arbeitet. Wenn Sie beispielsweise ein Ereignis mit einem Rückruf senden, wird immer eine neue Funktion erstellt, die zur Aktualisierung des Werts der Eigenschaft führt.


Beliebte Implementierungen der Komposition haben nicht die Identität des Rückgabewerts.
Zum Teil kann die Frage nach der Identität der Kompositionen durch Auswendiglernen der Argumente gelöst werden. Die Frage der assoziativen Identität bleibt jedoch bestehen:


import {memoize} from'ramda'const memoCompose = memoize(compose)
memoCompose(a, b) === memoCompose(a, b) 
// да, аргументы одинаковые
memoCompose(memoCompose(a, b), c) === memoCompose(a, memoCompose(b, c)) 
// нет, мемоизация не помогает так как аргументы разные

  • Vereinfachen Sie das Kompositions-Debugging:

Die Verwendung von Tap-Funktionen hilft natürlich beim Debuggen von Funktionen, die einen einzelnen Ausdruck im Körper enthalten. Es ist jedoch ratsam, einen möglichst flachen Call-Stack für das Debugging zu haben.


  • Befreien Sie sich vom Rekursions-Overheadprojektor:

Die rekursive Implementierung einer funktionalen Komposition hat einen Aufwand, der neue Elemente im Aufrufstapel erzeugt. Wenn Sie eine Komposition mit 5 oder mehr Funktionen aufrufen, ist dies deutlich sichtbar. Mit funktionalen Ansätzen in der Entwicklung ist es notwendig, Kompositionen aus Dutzenden sehr einfacher Funktionen zu erstellen.


Lösung


Erstellen Sie ein Monoid (oder ein Semigruppo mit Unterstützung für die Kategoriespezifikation) in Bezug auf Fantasy-Land:


import compose, {identity} from 'lazy-compose'
import {add} from 'ramda'
const a = add(1)
const b = add(2)
const c = add(3)
test('Laws', () => {
    compose(a, compose(b, c)) === compose(compose(a, b), c) // ассоциативность
    compose(a, identity) === a  //right identity
    compose(identity, a) === a  //left identity
}

Anwendungsfälle


  • Nützlich für das Memosizing von Komposit-Kompositionen bei der Arbeit mit Redaks. Zum Beispiel für redux / mapStateToProps und erneut
    auswählen.
  • Die Zusammensetzung der Linsen.

Sie können ausschließlich äquivalente Objektive erstellen und wiederverwenden, die auf dieselbe Stelle fokussiert sind.


    import {lensProp, memoize} from 'ramda'
    import compose from 'lazy-compose'
    const constantLens = memoize(lensProp)
    const lensA = constantLens('a')
    const lensB = constantLens('b')
    const lensC = constantLens('c')
    const lensAB = compose(lensB, lensA)
    console.log(
        compose(lensC, lensAB) === compose(lensC, lensB, lensA)
    )

  • Memotized Callbacks, mit der Möglichkeit der Zusammenstellung bis zur endgültigen Funktion zum Senden eines Ereignisses.

In diesem Beispiel wird derselbe Rückruf an die Listenelemente übergeben.


```jsx
import {compose, constant} from './src/lazyCompose'
// constant - returns the same memoized function for each argrum
// just like React.useCallback
import {compose, constant} from 'lazy-compose'
const List = ({dispatch, data}) =>
    data.map( id =>
        <Button
            key={id}
            onClick={compose(dispatch, makeAction, contsant(id))}
        />
    )
const Button = React.memo( props => 
    <button {...props} />
)
const makeAction = payload => ({
    type: 'onClick',
    payload,
})
```

  • Faule Zusammensetzung von React-Komponenten, ohne Komponenten höherer Ordnung zu erzeugen. In diesem Fall reduziert die faule Komposition das Funktionsfeld, ohne zusätzliche Schließungen zu erstellen. Dieses Problem ist für viele Entwickler von Belang, die die Rekompositionsbibliothek verwenden.


    import {memoize, mergeRight} from 'ramda'
    import {constant, compose} from './src/lazyCompose'
    const defaultProps = memoize(mergeRight)
    const withState = memoize( defaultState =>
        props => {
            const [state, setState] = React.useState(defaultState)
            return {...props, state, setState}
        }
    )
    const Component = ({value, label, ...props)) => 
        <label {...props}>{label} : {value}</label>    
    const withCounter = compose(  
        ({setState, state, ...props}) => ({
            ...props
            value: state,
            onClick: compose(setState,  constant(state + 1))
        }),
        withState(0),
    )   
    const Counter = compose(
        Component, 
        withCounter,
        defaultProps({label: 'Clicks'}),
    )
    

  • Monaden und Antragsteller (in Bezug auf Fantasieland) mit strikter Gleichwertigkeit durch Zwischenspeichern des Ergebnisses der Komposition. Wenn Sie innerhalb des Typkonstruktors auf das Wörterbuch der zuvor erstellten Objekte zugreifen, erhalten Sie Folgendes:



    type Info = {
          age?: number
    }
    type User = {
          info?: Info   
    }
    const mayBeAge = LazyMaybe<Info>.of(identity)
          .map(getAge)
          .contramap(getInfo)
    const age = mayBeAge.ap(data)
    const maybeAge2 =  LazyMaybe<User>.of(compose(getAge, getInfo))
    console.log(maybeAge === maybeAge2)  
    // создав эквивалентные объекты, мы можем мемоизировать их вместе
    // переиспользовать как один объект и бонусом получить короткий стек вызовов

Ich habe diesen Ansatz schon lange verwendet, ich habe hier ein Repository erstellt .
NPM - Paket: npm i lazy-compose.


Es ist interessant, Rückmeldungen über die Begrenzung des Cache zu erhalten, der in Laufzeitfunktionen erstellt wird, die von der Schaltung abhängig sind.


UPD
Ich sehe die offensichtlichen Fragen voraus:
Ja, Sie können Map durch WeakMap ersetzen.
Ja, Sie müssen den Anschluss eines Drittanbieter-Caches als Middleware ermöglichen.
Es ist nicht notwendig, eine Debatte über Caches zu veranstalten, es gibt keine perfekte Caching-Strategie.
Warum Schwanz und Kopf, wenn alles in der Liste enthalten ist - Schwanz und Kopf, Teil der Implementierung mit Memoisierung auf der Grundlage der Teile der Komposition, und nicht jede Funktion separat.


Jetzt auch beliebt: