functional-programmingKomma igång med funktionell programmering


Anmärkningar

Funktionell programmering är ett programmeringsparadigm som modellerar beräkningar (och därmed program) som utvärdering av matematiska funktioner. Det har sina rötter i lambda-kalkylen, som utvecklades av Alonzo-kyrkan i sin forskning om beräknbarhet.

Funktionell programmering har några intressanta begrepp:

  • Högre ordningsfunktioner
  • Renhet
  • Rekursion
  • Lättja
  • Referensöppenhet
  • Currying
  • funktorer
  • monader
  • Memoization & Tail-call Optimization
  • Test av funktionell enhet

Några exempel på funktionella programmeringsspråk är Lisp , Haskell , Scala och Clojure , men även andra språk, som Python , R och Javascript tillåter att skriva (delar av) dina program i en funktionell stil. Även i Java har funktionell programmering hittat sin plats med Lambda Expressions och Stream API som introducerades i Java 8.

Currying

Currying är processen för att transformera en funktion som tar flera argument till en sekvens av funktioner som var och en endast har en enda parameter. Currying är relaterad till, men inte samma som, delvis applikation.

Låt oss överväga följande funktion i JavaScript:

var add = (x, y) => x + y
 

Vi kan använda definitionen av currying för att skriva om add-funktionen:

var add = x => y => x + y
 

Denna nya version tar en enda parameter, x , och returnerar en funktion som tar en enda parameter, y , som i slutändan kommer att returnera resultatet av att lägga till x och y .

var add5 = add(5)
var fifteen = add5(10) // fifteen = 15
 

Ett annat exempel är när vi har följande funktioner som placerar parenteser runt strängar:

var generalBracket = (prefix, str, suffix) => prefix + str + suffix
 

Nu, varje gång vi använder generalBracket måste vi passera inom parentes:

var bracketedJim = generalBracket("{", "Jim", "}") // "{Jim}"
var doubleBracketedJim = generalBracket("{{", "Jim", "}}") // "{{Jim}}"
 

Dessutom, om vi passerar strängarna som inte är parenteser, ger vår funktion fortfarande ett felaktigt resultat. Låt oss fixa det:

var generalBracket = (prefix, suffix) => str => prefix + str + suffix
var bracket = generalBracket("{", "}")
var doubleBracket = generalBracket("{{", "}}")
 

Lägg märke till att både bracket och doubleBracket nu är funktioner som väntar på sin slutliga parameter:

var bracketedJim = bracket("Jim") // "{Jim}"
var doubleBracketedJim = doubleBracket("Jim") // "{{Jim}}"
 

Högre ordningsfunktioner

Högre ordningsfunktioner tar andra funktioner som argument och / eller returnerar dem som resultat. De utgör byggstenarna för funktionell programmering. De flesta funktionella språk har till exempel någon form av filterfunktion. Detta är en högre ordning, som tar en lista och ett predikat (funktion som returnerar sant eller fel) som argument.

Funktioner som inte gör någon av dessa kallas ofta för first-order functions .

function validate(number,predicate) {
    if (predicate) {    // Is Predicate defined
        return predicate(number);
    }
    return false;
}
 

Här är "predikat" en funktion som testar för vissa villkor som involverar dess argument och returnerar sant eller falskt.

Ett exempel på ovanstående är:

validate(someNumber, function(arg) {
    return arg % 10 == 0;
    }
);
 

Ett vanligt krav är att lägga till nummer inom ett intervall. Genom att använda högre ordning kan vi utöka denna grundläggande kapacitet genom att tillämpa en transformationsfunktion på varje nummer innan vi inkluderar den i summan.

Du vill lägga till alla heltal inom ett visst intervall (med hjälp av Scala)

def sumOfInts(a: Int, b: Int): Int = {
  if(a > b) 0
  else a + sumOfInts(a+1, b)
}
 

Du vill lägga till rutor för alla heltal inom ett visst intervall

def square(a: Int): Int = a * a

def sumOfSquares(a: Int, b: Int): Int = {
  if(a > b) 0
  else square(a) + sumOfSquares(a + 1, b)
}
 

Lägg märke till att dessa saker har en sak gemensamt, att du vill använda en funktion på varje argument och sedan lägga till dem.

Låter skapa en högre ordning för att göra båda:

def sumHOF(f: Int => Int, a: Int, b: Int): Int = {
  if(a > b) 0
  else f(a) + sumHOF(f, a + 1, b)
}
 

Du kan kalla det så här:

def identity(a: Int): Int = a

def square(a: Int): Int = a * a
 

Observera att sumOfInts och sumOfSquare kan definieras som:

def sumOfInts(a: Int, b: Int): Int = sumHOF(identity, a, b)

def sumOfSquares(a: Int, b: Int): Int = sumHOF(square, a, b)
 

Som du kan se från detta enkla exempel, ger högre ordning funktioner mer generaliserade lösningar och minskar kodduplicering.

Jag har använt Scala genom exempel - av Martin Odersky som referens.

Oföränderlighet

I traditionella objektorienterade språk är x = x + 1 ett enkelt och juridiskt uttryck. Men i funktionell programmering är det olagligt .

Variabler finns inte i funktionell programmering. Lagrade värden kallas fortfarande variabler endast på grund av historia. I själva verket är de konstanter . När x tar ett värde, är det det värdet för livet.

Så om en variabel är en konstant , hur kan vi ändra dess värde?

Funktionell programmering handlar om förändringar i värden i en post genom att göra en kopia av posten med de ändrade värdena.

I stället för att göra:

var numbers = [1, 2, 3];
numbers[0] += 1; // numbers = [2, 2, 3];
 

Du gör:

var numbers = [1, 2, 3];
var newNumbers = numbers.map(function(number) {
    if (numbers.indexOf(number) == 0)
        return number + 1
    return number
});
console.log(newNumbers) // prints [2, 2, 3]
 

Och det finns inga slingor i funktionell programmering. Vi använder rekursions- eller högre ordning funktioner som map , filter och reduce att undvika looping.

Låt oss skapa en enkel slinga i JavaScript:

var acc = 0;
for (var i = 1; i <= 10; ++i)
    acc += i;
console.log(acc); // prints 55
 

Vi kan fortfarande göra bättre genom att ändra acc livstid från global till lokal:

function sumRange(start, end, acc) {
    if (start > end)
        return acc;
    return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); // 55
 

Inga variabler eller slingor betyder enklare, säkrare och mer läsbar kod (särskilt när du felsöker eller testar - du behöver inte oroa dig för värdet på x efter ett antal uttalanden, det kommer aldrig att ändras).

Rena funktioner

Rena funktioner är fristående och har inga biverkningar. Med samma uppsättning ingångar returnerar en ren funktion alltid samma utgångsvärde.

Följande funktion är ren:

function pure(data) {
    return data.total + 3;
}
 

Men denna funktion är inte ren eftersom den modifierar en extern variabel:

function impure(data) {
    data.total += 3;
    return data.total;
}
 

Exempel:

data = {
    total: 6
};

pure(data);   // outputs: 9
impure(data); // outputs: 9 (but now data.total has changed)
impure(data); // outputs: 12