ugrás a tartalomhoz

Funkcionális programozás JavaScriptben

Poetro · 2015. Szep. 1. (K), 07.05

Folytatom a JavaScript bemutatását, a függvények és az objektumorientált programozás után most a funkcionális programozásra fókuszálva.

A funkcionális programozás egy programozási paradigma – meghatározza milyen elemekből épül fel a programunk. A funkcionális program minden számítást matematikai függvényekkel ír le, alapja a lambda-kalkulus:

A λ-kalkulust nyugodtan nevezhetjük a legegyszerűbb általános célú programozási nyelvnek. Csak egyfajta értéket ismer: a függvényt (absztrakciót), és csak egyfajta művelet van benne: a függvény alkalmazás (változó-behelyettesítés). Ezen látszólagos egyszerűsége ellenére minden algoritmus, ami Turing-gépen megvalósítható, az megvalósítható tisztán a λ-kalkulusban is.

Lambda-kalkulus – Wikipédia

A legnépszerűbb funkcionális programozási nyelvek a Lisp, Scheme, Clojure, Erlang, OCaml, Haskell és az F#. A JavaScript ugyan nem tartozik a tisztán funkcionális nyelvek közé, de alkalmas a paradigma használatára.

Koncepciók

Létezik pár koncepció, ami a funkcionális programozáshoz tartozik, ezek a következők:

Tiszta függvények

Azokat a függvényeket nevezzük tisztának, amelyek akárhányszor is hívjuk meg őket, ugyanarra a bemenetre mindig ugyanazt a kimenetet adják vissza, és amelyeknek nincsen mellékhatása, vagyis a hívó fél szemszögéből nem változik meg a függvénynek átadott érték vagy más állapot (memória vagy I/O). Ezek sok hasznos tulajdonsággal rendelkeznek:

  • A függvényhívások eredménye gyorsítótárazható.
  • Ha nincs adatbeli függőség két függvény között, akkor hívási sorrendjük felcserélhető, illetve párhuzamosan futtathatók.
  • Ha az eredményt nem használjuk, akkor a hívás mindenféle következmény nélkül eltávolítható.
  • Ha egy nyelv nem enged meg mellékhatásokat, akkor akármilyen kiértékelési stratégia alkalmazható.

Rekurzió

A rekurzív függvények saját magukat hívják, hogy megvalósítsanak egy műveletet, amíg egy elemi esethez nem érnek, ahol már nem szükséges további rekurzió. Funkcionális nyelvekben legtöbbször az iterációt is rekurzióval valósítják meg:

function each(array, callback, context) {
    function _each(array, index, callback, context) {
        if (index === array.length) {
            return; // elemi eset
        } else {
            if (index in array) {
                callback.call(context, array[index], index, array);
            }
            
            _each(array, index + 1, callback, context); // rekurzív hívás
        }
    }
    
    _each(array, 0, callback, context);
}

function map(array, callback, context) {
    function _map(array, index, callback, context, result) {
        if (index === array.length) {
            return result;
        } else {
            if (index in array) {
                result[index] = callback.call(context, array[index], index, array);
            }
            
            return _map(array, index + 1, callback, context, result);
        }
    }
    
    return _map(array, 0, callback, context, []);
}

Elsődleges és magasabb rendű függvények

A függvények a JavaScriptben elsődleges (first-class) típusok, azaz hozzárendelhetjük őket változókhoz és objektum tulajdonságokhoz, átadhatjuk őket paraméterként más függvényeknek, valamint egy függvény illetve metódus is térhet vissza függvénnyel

A magasabb rendű függvények (higher order function) olyan függvények, melyek más függvényekkel dolgoznak, azaz vagy paraméterként várják őket, vagy függvényeket adnak vissza.

JavaScript függvények

Az elsődleges és magasabb rendű függvények lehetővé teszik a részleges alkalmazást (partial application). Ez azt jelenti, hogy a függvény egyes paramétereit előre megadjuk, létrehozva egy új függvényt.

Az ES5 óta a nyelv részét képezi a Function#bind(), ami a kontextus megadása mellett lehetővé teszi a részleges alkalmazást is:

function add(a, b) {
    return a + b;
}

var add10 = add.bind(null, 10);
add10(1); // 11

add.bind(null, 'Hello ')('World!'); // 'Hello World!'

A részleges alkalmazáshoz hasonló a currying. Ha létrehozunk egy „curry-vel fűszerezett” változatot a függvényből, akkor annak minden egyes meghívásával részlegesen alkalmazzuk az átadott paramétereket. Ha az átadott paraméterek száma eléri a függvény argumentumainak számát, akkor pedig visszaadja az eredeti függvény visszatérési értékét a korábban megadott paraméterekkel.

function curry(f, arity) {
    arity = arity || f.length;
    
    return function _curry() {
        var args = Array.prototype.slice.call(arguments);
        
        if args.length >= arity {
            return f.apply(null, args);
        } else {
            return function curried() {
                var _args = Array.prototype.slice.call(arguments);
                return _curry.apply(null, args.concat(_args));
            };
        }
    };
}

var curriedAdd = curry(add);

curriedAdd(3)(4);               // 7
curriedAdd('Hello ', 'World!'); // 'Hello World!'

Mind a részleges alkalmazás, mind a currying megvalósítható jobbról is, azaz az átadott paraméterek a paraméterlista vége helyett az elejére kerülnek.

function partialRight(f) {
    var args = Array.prototype.slice.call(arguments, 1);
    
    return function _partialRight() {
        var _args = Array.prototype.slice.call(arguments)
        return f.apply(null, _args.concat(args));
    }
}

function curryRight(f, arity) {
    arity = arity || f.length;
    
    return function _curry() {
        var args = Array.prototype.slice.call(arguments);
        
        if (args.length >= arity) {
            return f.apply(null, args);
        } else {
            return function curried() {
                var _args = Array.prototype.slice.call(arguments)
                return _curry.apply(null, _args.concat(args))
            };
        }
    };
}

partialRight(add, 'World!')('Hello '); // 'Hello World!'
curryRight(add)('World!')('Hello ');   // 'Hello World!'

Tömbök

A ECMAScript 5-ös változata rengeteg segítséget ad a funkcionális programozáshoz. Feljebb már bemutattam, hogy létezik beépített eszköz a részleges alkalmazásra, de a nyelv tömbök esetén is sok segítséget ad a paradigma gyakorlásához (Array#extras). Ezek végigiterálnak a tömbön, minden egyes elemre meghívva az átadott függvényt.

Array#forEach()

Az Array#forEach() hívásakor nem igazán tudunk tiszta függvényt átadni, de attól még az azt használó függvényünk maradhat tiszta, ha csak az átadott függvény végez I/O-t. Az átadott függvény három paramétert kap: az aktuális elem, az elem sorszáma és maga a tömb. Például, hogyha ki akarjuk íratni az tömbünk elemeit:

[1, 2, 3, 4].forEach(console.log.bind(console, 'Element:'));

// > Element: 1
// > Element: 2
// > Element: 3
// > Element: 4

Array#map()

Az Array#map() segítségével egy új tömböt hozhatunk létre a már meglévő elemeit használva. Három paramétert ad át a függvénynek, hasonlóan az Array#forEach()-hez, és a visszaadott érték lesz az új tömb vonatkozó eleme.

[1, 2, 3, 4].map(function elementTimesIndex(element, index) {
    return element * index;
}); // [0, 2, 6, 12]

[1, 2, 3, 4].map(add); // [1, 3, 5, 7]

Array#filter()

Az Array#filter() segítségével egy olyan tömböt hozhatunk létre, melyben csak azokat az elemeket tartjuk meg, amelyek eleget tesznek a feltételnek. Három paramétert ad át a függvénynek, hasonlóan az Array#forEach()-hez. Például, ha csak a páratlan elemeket szeretnénk megtartani:

function isOdd(n) {
    return n % 2 === 1;
}

[1, 2, 3, 4].filter(isOdd); // [1, 3]

Hogyan tudnánk erre építve létrehozni egy olyan függvényt, ami a párosakat adja vissza?

function negate(predicate) {
    return function() {
        !predicate.apply(this, arguments);
    };
}

var isEven = negate(isOdd);

[1, 2, 3, 4].filter(isEven); // [2, 4]

Array#reduce() és Array#reduceRight()

Az Array#reduce() segítségével a tömbünket egyetlen értékre redukálhatjuk. Paraméterként egy függvényt és opcionális kezdőértéket vár. Négy paramétert ad át a függvénynek: az előző érték, az aktuális elem, az elem sorszáma és a tömb. A függvénynek az új értéket kell visszaadnia. Ha nem adunk meg kezdőértéket, akkor a kezdőérték az első elem lesz, és az iteráció a második elemmel indul. Segítségével könnyen összeadhatjuk a tömbünk elemeit:

[1, 2, 3, 4].reduce(add);                 // 10
[' ', 'World', '!'].reduce(add, 'Hello'); // 'Hello World!'

Az Array#reduceRight() megegyezik a Array#reduce()-szal, csak a tömb elemeit jobbról balra dolgozza fel. Ha nem adunk meg kezdőértéket, akkor a kezdőérték az utolsó elem lesz, és az iteráció a utolsó előtti elemmel indul.

['!', 'World', ' ', 'Hello'].reduceRight(add); // 'Hello World!'

Array#some() és Array#every()

Az Array#some() és az Array#every() az Array#reduce() egy speciális formája. Az Array#some() true értéket ad vissza, ha a tömb legalább egy eleme teljesíti a feltételt. Az Array#every() pedig akkor ad vissza true értéket, ha a tömb minden eleme teljesíti a feltételt. Az Array#some() nem iterál tovább, amennyiben talált egy elemet, ami megfelel a feltételnek. Az Array#every() pedig akkor, amikor egy elem nem felel meg a feltételnek.

[1, 2, 3, 4].some(isOdd);  // true
[1, 3].some(isEven);       // false
[1, 2, 3, 4].every(isOdd); // false
[1, 3].every(isOdd);       // true

Array#reduce() segítségével a következőképp írhatjuk fel őket:

function some(array, predicate, context) {
    return array.reduce(function (previous) {
        var args = Array.prototype.slice.call(arguments, 1);
        
        if (previous === true) {
            return true;
        } else {
            return !!predicate.apply(context, args);
        }
    }, false);
}

some([1, 2, 3, 4], isOdd); // true
some([1, 3], isEven);      // false

function every(array, predicate, context) {
    return array.reduce(function (previous) {
        var args = Array.prototype.slice.call(arguments, 1);
        
        if (previous === false) {
            return false;
        } else {
            return !!predicate.apply(context, args);
        }
    }, true);
}

every([1, 2, 3, 4], isOdd); // false
every([1, 3], isOdd);       // true

Felhasználás

Funkcionális programozáshoz alapvető, hogy rendelkezzünk egy általánosan használható függvénykönyvtárral, amikből újabb függvényeket készíthetünk. Bemutatok néhány hasznos ilyen függvényt, ezzel bővítve a rendelkezésre álló eszközöket.

Azonosság

Kezdjük pár hasznos, de egyszerű függvénnyel. A legalapabb ezek közül az identity(), amely visszaadja az első kapott paramétert:

function identity(value) {
    return value;
}

[0, 1, 2, null, undefined].filter(identity); // [1, 2]

Konstans

Másik hasznos függvény lehet a constant(), amely mindig az előre megadott értéket adja vissza:

function constant(value) {
    return function () {
        return value;
    };
}

[1, 2, 3, 4].map(constant(42)); // [42, 42, 42, 42]

Tulajdonság

Hozzunk létre egy függvényt, ami a megadott objektumnak visszaadja egy, tömbbel megadott elérési úton található elemét:

function property(object, path) {
    return path.reduce(function (previous, current) {
        if (previous !== undefined && previous !== null) {
            return previous[current];
        } else {
           return undefined;
        }
    }, object);
}

property([1, 2, 3, 4], [1]); // 2

var object = {a: {b: {c: 'd'}}};
property(object, ['a', 'b', 'c']); // "d"

Ezt később felhasználhatjuk arra, hogy generáljunk egy függvényt, ami mindig az adott elérési utat keresi.

function propertyAt() {
    var path = Array.prototype.slice.call(arguments);
    
    return function (object) {
        return property(object, path);
    };
}

var head = propertyAt(0);
head([1, 2, 3, 4]); // 1

var length = propertyAt('length');
length([1, 2, 3, 4]); // 4

Ezután, ha meg akarjuk kapni a kapott tömbök első elemét, vagy elemeinek hosszát, könnyű a dolgunk:

[[1, 2, 3, 4], [5, 6, 7], [8, 9]].map(head);             // [1, 5, 8]
['foo', 'bar', 'foobar', 'baz'].map(length);             // [3, 3, 6, 3]
['foo', 'bar', 'foobar', 'baz'].map(length).reduce(add); // 15

Ha adott objektum különböző tulajdonságait akarjuk kikeresni, generálhatunk egy másik függvényt:

function propertyOf(object) {
    return function () {
        var args = Array.prototype.slice.call(arguments);
        return property(object, args);
    };
}

var object = {a: 1, b: 2, c: 3, d: {e : 4}}
var getter = propertyOf(object);

getter('a')      // 1
getter('d', 'e') // 4

Ezek után már csak egy lépés egy általános tulajdonságkiemelő pluck() függvény felírása:

function pluck(array) {
    var path = Array.prototype.slice.call(arguments, 1);
    
    return array.map(function (object) {
        return property(object, path);
    });
}

var objects = [{name: 'Foo'}, {name: 'Bar'}, {name: 'Bazbar'}]
pluck(objects, 'name'); // ['Foo', 'Bar', 'Bazbar']

Ha meg akarjuk tudni, hogy mindegyik név hosszabb-e, mint két karakter, vagy van-e olyan, ami három karakter hosszú, egyszerű a dolgunk:

function greaterThan(a) {
    return function(b) {
        return a < b;
    }
}

function equals(a) {
    return function(b) {
        return a === b;
    }
}

var nameLengths = pluck(objects, 'name').map(length); // [3, 3, 6]

var equals3 = equals(3);

nameLengths.every(greaterThan(2)); // true
nameLengths.every(equals3);        // false
nameLengths.some(equals3);         // true

Kompozíció

Előfordulhat, hogy egy értékkel egymás után több műveletet szeretnénk végezni, mindig felhasználva az előző művelet eredményét. Erre való a flow():

function flow() {
    var functions = Array.prototype.slice.call(arguments);
    
    return function (target) {
        return functions.reduce(function(previous, current) {
            return current(previous);
        }, target);
    };
}

function multiply(a, b) {
    return a * b;
}

// f(x) = (x + 3) * 2
[0, 1, 2, 3].map(
    flow(add.bind(null, 3), multiply.bind(null, 2))
); // [6, 8, 10, 12]

Ha fordított sorrendben akarjuk megadni a függvényeket, akkor szokás a compose() elnevezéssel élni. Ez megfelel a függvények felírási sorrendjének egy egyenletben.

function compose() {
    var args = Array.prototype.slice.call(arguments);
    return flow.apply(null, args.reverse());
}

function divide(b, a) {
    return a / b;
}

function round(precision) {
    if (precision) {
        var multiplier = Math.pow(10, precision);
        
        return compose(
            divide.bind(null, multiplier),
            Math.round,
            multiply.bind(null, multiplier)
        );
    } else {
        return Math.round;
    }
}

var roundToOneDecimal = round(1);

[.12, .345, 67.89].map(roundToOneDecimal); // [0.1, 0.3, 67.9]

// f(x) = roundToOneDecimal(|cos(pi / 3 * x)|)
[0, 1, 2, 3].map(
    compose(
        roundToOneDecimal, 
        Math.abs,
        Math.cos, 
        multiply.bind(null, Math.PI / 3)
  )
); // [1, 0.5, 0.5, 1]

Letölthető függvénytárak

A két legnépszerűbb funkcionális függvénytár az Underscore.js és a lodash – utóbbi eredetileg az előbbi újraírt változata volt. Mindkettő rengeteg hasznos eszközt nyújt a funkcionális programozáshoz, utóbbi legtöbb esetben gyorsabb, és több lehetőséggel rendelkezik. Jellemzőjük, hogy általánosságban első paraméterként várják az objektumot, amin a műveletet végzik, hasonlóan a fent definiált each() és map() függvényekhez.

Jelentősen különbözik tőlük a Ramda, melynek alapvető tulajdonsága, hogy mindent a currying-re épít. Így például a map() nem első, hanem utolsó paramétere a tömb. Ezzel lehetővé válik, hogy előre deklaráljuk az átalakítás menetét, majd többször alkalmazzuk különböző tömbökre.

Az Underscore.js-hez hasonló a Highland.js, azzal a hatalmas különbséggel, hogy a műveleteket egy adatfolyamon végzi. Azaz ahogy érkezik be folyamatosan az adat, úgy születik az adatfolyam másik végén az átalakított eredmény.

A Reactive Extensions for JavaScript egy Microsoft által indított projekt, mely sok tekintetben hasonlít a Highland.js-hez, ugyanakkor sokkal több nála. Adatfolyam helyett események folyamára alapoz, és az API az Array#extras-ra épít paraméterezésében is.

Összefoglalás

Mint látható, igen minimális függvényekből komoly rendszert hozhatunk össze. Segítségükkel megkönnyíthetjük az adatfeldolgozást, validálást. A függvényeink egyszerűen tesztelhetők, működésük könnyen ellenőrizhető. Kombinálásukkal igen hasznos újabb függvényeket hozhatunk létre tovább egyszerűsítve az elvégzendő feladaton.

 
Poetro arcképe
Poetro
1998 óta foglalkozik webfejlesztéssel, amikor is a HTML és a JavaScript világa elvarázsolta. Azóta jópár évet dolgozott reklámügynökségeknél, és nemzetközi hírportálok fejlesztésével. Legfőképpen Drupal fejlesztéssel, site buildinggel és JavaScripttel, azon belül is jQuery-vel és Node.js-sel foglalkozik.
1

Kérdések

Hidvégi Gábor · 2015. Szep. 1. (K), 08.41
Köszönjük az összefoglalást. Amit én hiányolni szoktam szinte az összes ilyen leírásból (OOP, ez, vagy blogmarkban a monádok bemutatása stb.), hogy bár felsorolják hosszan az eszközöket, de hogy ezeket hol és miért célszerű használni, arról legjobb esetben is csak két mondat szól.

Tehát szerintem célszerű lenne pár életből vett példával szemléltetni, összehasonlítva mondjuk a mindenki által ismert imperatív vagy procedurális programozással, hogy hagyományosan ezt így oldanánk meg, de ekkor és ekkor érdemes funkcionálisan programozni, mert azt nyerem vele, hogy ..., ..., ...

Mik a hátrányai? Erről is ritkán esik szó, márpedig enélkül nehéz objektív döntést hozni. Egy array.forEach és egy for ciklus között akár ötven-százszoros különbség is lehet, amire nem nehéz magyarázatot találni: minden függvényhívás memóriafoglalással jár, amit utána fel is kell szabadítania a szemétgyűjtőnek, ami nem egy gyors folyamat. Megéri-e ezt az árat a funkcionális programozás egy olyan nyelvben, ahol nincs natívan támogatva?
2

hol és miért célszerű

Poetro · 2015. Szep. 1. (K), 10.25
hol és miért célszerű használni

Segítségükkel megkönnyíthetjük az adatfeldolgozást, validálást. A függvényeink egyszerűen tesztelhetők, működésük könnyen ellenőrizhető. Kombinálásukkal igen hasznos újabb függvényeket hozhatunk létre tovább egyszerűsítve az elvégzendő feladaton.

Mik a hátrányai?

Igen, a kod lassabb lesz, mondjuk nem annyival lassabb, mivel a magában a ciklusban valószínűleg komolyabb számítást végzel mint egy szám növelése, amivel általában tesztelni szokták a sebességkülönbséget. A korszerű JavaScript motorok pedig képesek a tiszta függvényeket inline változatra cserélni, azaz az egész forEach ciklust képesek lecserélni egy megfelelő for ciklusra.
Megéri-e ezt az árat a funkcionális programozás egy olyan nyelvben, ahol nincs natívan támogatva?

A funkcionális programozás natívan támogatva van JavaScriptben, egyedül a más funkcionális nyelvekben gyakran használt tail call optimalizáció nincs jelenleg, de az ES2015-ben már vannak lépések annak támogatására is.

célszerű lenne pár életből vett példával szemléltetni

Mindenkinél mások a példák, mert más feladatokat kell elvégeznie. Ha írnék pár példát amiket használok, valószínűleg nem sok hasznát tudnád venni, mert más alkalmazásokat fejlesztesz, mások a követelmények.

Vegyük a következő feladatot, amiben egy objektumokat tartalmazó tömböt akarunk átalakítani. Ezek mondjuk IoT eszközök adatai, de nem minden percre van minden műszertől adat. Ki akarjuk rajzolni az értékeket egy vonaldiagrammon. Ha az érték nem létezik a bemenetben, akkor oda null-t írunk.
convert([{a: 1}, {a: 2, b: 3}, {c: 1, b: 1}]) 
// [['a', 1, 2, null], ['b', null, 3, 1], ['c', null, null, 1]]
A convert felírásához lodash-t használok:
function numberOrNull(value) {
    return _.isNumber(value) ? value : null;
}
function convert(groupedFields) {
    return _.chain(groupedFields).map(_.keys).flatten().sort().uniq(true).map(function (key) {
        return [key].concat(groupedFields.map(_.property(key)).map(numberOrNull));
    }).valueOf();
}
6

Mik a hátrányai? 1)

Public · 2015. Szep. 2. (Sze), 07.18
Mik a hátrányai?


1) Értelmezhetőség: a kód összetettsége miatt nehezen értelmezhető.
2) Karbantarthatóság: egy bizonyos méret felett rémálom karbantartani
3) Moduláris kialakítás: minimális lehetőségek
4) Használhatóság
a) böngészők: egy-két kivételtől eltekintve (játékok, grafika, ..), minimális szükség van funkcionális megoldásokra, eltekintve persze a DOM-al is foglalkozó ilyen-olyan többé-kevésbé funkcionális szerszámokra (jQuery és társai)
b) szerver oldal: ez a terület, ahol már van a létjogosultsága egy bizonyos szintig

Szerintem..
7

Értelmezhetőség, karbantartás

Hidvégi Gábor · 2015. Szep. 2. (Sze), 07.44
Nagyon szokni kell. Nézegettem a Poetro által írt kódot, tele van bind, apply és call hívásokkal (én ezeket nyelvi bűznek tartom), amik miatt sokat kell agyalni, hogy pontosan mi is történik, indirekt az egész, össze-vissza kell ugrálni, ha meg szeretném érteni.

Úgyhogy ez alapján bennem tegnap először az fogalmazódott meg, hogy nem intuitív. A végeredmény lehet, hogy egyszerű, de a hozzá szükséges kód biztosan nem, ami megnehezíti a karbantartást
8

Ha használsz babel-t, akkor

MadBence · 2015. Szep. 2. (Sze), 09.31
Ha használsz babel-t, akkor lecserélheted ezeket a függvényeket nyelvi elemekre:

context::f; // f.bind(context)
context::f(a); // f.call(context, a)
f(...args); // f.apply(null, args);
context::f(...args); // f.apply(context, args);

function f(...args) {
  // var args = [].slice.call(null, arguments)
}

const div = a => b => a/b; // szegény embery curry-je
Function bind operator
Rest & Spread operator
9

Válaszok

Poetro · 2015. Szep. 4. (P), 09.27
  1. A kód nem lesz bonyolultabb, mintha más megközelítésben írnád meg.
  2. A kódot ugyanannyira lesz karabantartható, amennyire karbantarthatóra írod. Elég komoly rendszereket készítenek funkcionális programozással.
  3. A kódodat ugyanúgy modulokba tudod rakni, mint OOP vagy procedurális programozás esetén.
  4. A használhatóság kérdését nem értem. A funkcionális programozás alapvető eszközkészlete relatíve kicsi, csak kb. 1 tucat függvény ismeretével már nagyon messze lehet jutni.
    1. Nem kell egy egész alkalmazást funkcionális paradigma mentén írni, jobb megközelítés lehet, ha csak a megfelelő részek esetén használod. Böngészőben is rengeteg dolog történik, ami nem érinti közvetlenül a DOM-ot. Gondolj csak a SPA-kra, amik manapság már elég gyakoriak. Pár száz vagy ezer elem feldolgozása elég gyakori tud lenni böngészőben is.
    2. Szerver oldalon, ahol nagyobb mennyiségű adatot kell feldolgozni, pedig kifejezetten előnyös lehet. Leginkább akkor, ha adatfolyamot kell feldolgozni. Ekkor érdemes a feldolgozást több lépcsősre építeni, és ezáltal jobban elosztható a kapacitás az I/O és CPU műveletek között.

10

Nagy divat lett mostanság a

Public · 2015. Szep. 4. (P), 11.37
Nagy divat lett mostanság a funkcionális nyelveket favorizálni, azonban nagyon sok nem divat programozó nem értene Veled egyet a fentikkel. A gyakorlat is más mutat, nagyon kevés a nagy és komoly project ami funkcionális programozásban valósítanak meg, a részesedésük a béka segge alatt sincs.

Véleményem, amit írtam most is tartom..

1) Sokkal bonyolultabb értelmezni egy funkcionális kódot, annál nehezebb minél több egymásba ágyazás van.. meghívsz egy funkciót, ami "belső" függvényt használ, amiben szintén van egy függvény, ami meghív egy sokadikat, ami szintén.. és már csak az ember a fejét fogja, hogy most mit fog visszaadni..
Elhiszem, hogy tömbök, objektumok esetén egyszer kell megírni, csakhogy az életben nem csak tömbök és objektumok vannak.

2) Légy szíves írj már pár "komoly" rendszert ami legnagyobb része bizonyíthatóan funkcionális nyelven van megírva, mert én csak OOP-re kis milliót tudok írni a többiről nem beszélve.

3) Ebben lehet igazad van.. csak akkor nem igazán értem, hogy miként lehet, hogy pl.: a JQuery mind a mai napig nem moduláris, miért van tele olyan "szeméttel", ami más nyelveken modulba illik rakni és ez sajnos igaz, nagyon sok ismert library-ra.

4.a) Rossz példa az SPA, mert nem "gondolok" rá, hanem csinálom, a köze a funkcionális programozáshoz szinte nulla. Sokat elmond, hogy ahhoz képest hogy több száz és ezer elem feldolgozása kell egy online táblázatkezelőben, amit én eddig láttam mind OOP-s volt és nem funkcionális.


Persze ez csak az én véleményem, mindenkinek van joga véleményt alkotni.
12

Komoly rendszer

Hidvégi Gábor · 2015. Szep. 4. (P), 13.26
Joe Armstrong, az Erlang nyelv alkotója az Ericcsonnak készített ATM-es switch-ekhez szoftvert, 1,7 millió soros kód, 40 év alatt két óra karbantartásra van szükség.

Szóval lehetséges, de szerintem ez csak úgy lehetséges, ha az ember eleve funkcionálisan kezd programozni.
16

Azt mondják, hogy

inf3rno · 2015. Szep. 7. (H), 02.11
Azt mondják, hogy multi-thread esetén hasznos tud lenni, de js-ből ez pont hiányzik. Amúgy egy rakás fluent interface-es keretrendszer van, szóval nem lehet azt mondani, hogy hiányoznának. Az már más kérdés, hogy ezek mennyire vannak funkcionálisan megírva.

szerk: Annyira azért mégsem hiányzik már egy jó ideje, nem értem miért írtam ezt a marhaságot.
11

A funkcionális programozás

Hidvégi Gábor · 2015. Szep. 4. (P), 13.11
A funkcionális programozás natívan támogatva van JavaScriptben
Melyik része? A .forEach inline-olására gondolsz? Vagy a flow és a compose része a Javascriptnek? Utóbbi nélkül várható, hogy funkcionálisan lassabb lesz, mint procedurálisan, hisz több függvényhívásra van szükség.

Méréseim szerint imperatív módon tízszer gyorsabb:
function kerekito(_szam, _precizio) {
  if (_precizio) {
    var szorzo = Math.pow(10, _precizio);
    return Math.round(_szam * szorzo) / szorzo;
  }
  return Math.round(_szam);
}

function imperialista() {
  var tomb = [0, 1, 2, 3];
  var eredmeny = [];
  for (var i = 0; i < tomb.length; i++) {
    eredmeny.push(
      kerekito(
        Math.abs(
        Math.cos(
        Math.PI / 3 * tomb[i]
      ))
      , 1)
    );
  }
  return eredmeny;
}
13

támogatva van ~ a függvények

MadBence · 2015. Szep. 4. (P), 13.57
támogatva van ~ a függvények first class citizenek a nyelvben.

JS-ben a funkcionális megközelítés nyilván lassabb, szerintem senki sem állította az ellenkezőjét. Kliens oldalon amúgy sem szokás erőforrásigényes programokat íri (olyan programokat, aminél észrevehető lesz ez a sebességkülönbség), a hálózati kérések, a DOM manipulálás több nagyságrenddel lassabb, így gyakorlati szempontból teljesen mindegy, hogy imperatív vagy deklaratív megközelítést használsz...
14

Ennyi erővel a C-ben is

Hidvégi Gábor · 2015. Szep. 4. (P), 15.49
Ennyi erővel a C-ben is támogatva van.

Amennyit eddig olvastam a témában, a natív funkcionális nyelvek egyik nagy erőssége, hogy egy programon belül minimális számú változóval dolgoznak (tipikusan csak olyanokkal, amelyek I/O műveletek eredményei), a legtöbb érték, amit mi változónak nevezünk, ott megváltoztathatlan (immutable). Ezzel garantálják, hogy egy program a lehető legkevesebb állapotot vehessen fel, amivel jelentősen lecsökkentik a hibázás lehetőségét. Ez szükséges a párhuzamos programfuttatáshoz is.

A változókezelés miatt a funkcionális programozás memóriaigénye is jóval nagyobb a hagyományosnál. Úgy gondolom, hogy a JS-ben használt szemétgyűjtős módszer ennek a kezelésére nagyon nem hatékony.

így gyakorlati szempontból teljesen mindegy, hogy imperatív vagy deklaratív megközelítést használsz
Még mindig örülnék pár élő példának, hogy hol érdemes funkcionális stílust használni.
15

Ennyi erővel a C-ben is

MadBence · 2015. Szep. 4. (P), 18.10
Ennyi erővel a C-ben is támogatva van.
Ha úgy vesszük, akkor igen, támogatva van (bár létrehozni nem tudsz új függvényeket). A funkcionális programozás nem egy nagy vasziszdasz, C-ben is tudsz így programozni (persze egy kicsit kényelmetlen, mivel nem ez volt az elsődleges szempont a nyelv megtervezésénél).

A mellékhatások elkerülése (ide tartozik az immutability is) azért fontos, mert így egy csomó optimalizációt meg tud tenni a futtatókörnyezet.
Prelude> let fib x = case x of 0 -> 1; 1 -> 1; _ -> fib (x - 1) + fib (x - 2)
Prelude> length [1, 2, 3, fib 30]
4
Mivel nem volt szükség a fib 30 kiértékelésére, így a fordító ezt megspórolta.
Prelude> let f x = x + 1 in [f 1, f 1]
[2,2]
Mivel nincsenek mellékhatások, így az [f 1, f 1] kifejezésben minden további nélkül kicserélhetjük az f 1-et a kifejezés eredményére (amit csak egyszer kell kiértékelni, hiszen ugyanolyan argumentumokra ugyanazt fogja adni a kifejezés).

Az immutability azt az érzést adja, hogy egy objektum módosítása költséges, hiszen magát az objektumot nem tudjuk módosítani, csak újat tudunk készíteni, amihez le kell másolnunk az eredetit. Ám mivel az eredeti nem fog változni, az új objektum nyugodtan hivatkozhat az eredeti objektumra, nem kell fizikailag minden adatot átmásolni.

Gyakorlatilag minden magas szintű nyelv GC-vel operál manuális memóriakezelés helyett (C, C++, Rust jut hirtelen eszembe, amik nem ilyenek), és az ipar tapasztalatai alapján teljesen működőképes megoldás (nyilván vannak olyan területek, ahol szükség van a valósidejű működésre, ott egy GC pause egyszerűen nem fér bele az időbe).

Funkcionálisan programozni ott érdemes, ahol valamilyen adatot szeretnél transzformálni, hiszen a deklaratív megközelítés pont az ilyen dolgokat írja le jól, pl a kedvenc példám:
Prelude Data.Function Data.List> sortBy (compare `on` length) ["foo", "foobar", "foob"]
["foo","foob","foobar"]
17

In functional code, the

inf3rno · 2015. Szep. 30. (Sze), 09.38
In functional code, the output value of a function depends only on the arguments that are input to the function, so calling a function f twice with the same value for an argument x will produce the same result f(x) each time.

https://en.wikipedia.org/wiki/Functional_programming

Nagyjából az az előnye a funkcionális megközelítésnek, hogy stateless. Minden függvényhívás csak a paraméterekkel dolgozik, nem ránt be semmit scope-ból, illetve nem mutálja a paramétereket, hanem újonnan előállított adatot szór visszatérő értékbe. Ez azért hasznos, mert így különösebb erőfeszítés nélkül párhuzamosítható, mondjuk kitehető egy függvény vagy függvény sorozat worker thread-ekbe vagy child process-ekbe. Js-ben erre már van lehetőség egyikre-másikra egy ideje böngészőben és szerver oldalon is. A másik előnye, hogy könnyebb karbantartani, mert nem a scope-ból ránt be dolgokat, mint a procedurális. Az oo-nak többek között ugyanez az előnye megvan, csak nála a context-be injektálunk dolgokat, ahelyett, hogy a scope-ból rántanánk be őket.

Csak úgy btw. a REST is pont emiatt a stateless megközelítés miatt skálázható jobban horizontálisan, mint a SOAP. Minden a request-el jön, ami ahhoz kell, hogy teljesítsük a kérést, nem kell session-ben turkálni szerver oldalon, hogy előrántsuk a client state azon darabkáját, ami kell a kérés teljesítéséhez. Így a végeredmény csak a kéréstől és a resource state-től függ, amit az adatbázis tárol. It is lehet valami ilyesmi analógiát vonni: request = paraméterek, response = visszatérő érték, resource state = context, client state (session) = scope.
18

Minden függvényhívás csak a

Joó Ádám · 2015. Szep. 30. (Sze), 22.32
Minden függvényhívás csak a paraméterekkel dolgozik, nem ránt be semmit scope-ból


Egy félreértés: a funkcionális függvények is használják a hatókörben látható változókat, az ún. szabad változóik (azok, amelyek értéke nem argumentumokként kerül átadásra) kötésére. Lévén a változók csak egyszer kapnak értéket, ez nem ássa alá a transzparenciát.

Ez azért hasznos, mert így különösebb erőfeszítés nélkül párhuzamosítható


És mert transzparens, ezért az értékével behelyettesíthető.

Minden a request-el jön, ami ahhoz kell, hogy teljesítsük a kérést, nem kell session-ben turkálni szerver oldalon, hogy előrántsuk a client state azon darabkáját, ami kell a kérés teljesítéséhez. Így a végeredmény csak a kéréstől és a resource state-től függ, amit az adatbázis tárol.


Egy másik (közkeletű) félreértés: a REST nem attól stateless, hogy nem használsz sessiont az alkalmazás szintjén. Egy PHP session, amit a lemezen tárolsz, semmiben sem különbözik a MySQL-ben tárolt adataidtól. A session cookie ugyanolyan paramétere a kérésnek mint a query string. A különbség az, hogy kérés és kérés a protokoll szemszögéből független a HTTP esetén, szemben például az FTP-vel.
19

Egy félreértés: a

inf3rno · 2015. Okt. 1. (Cs), 00.31
Egy félreértés: a funkcionális függvények is használják a hatókörben látható változókat, az ún. szabad változóik (azok, amelyek értéke nem argumentumokként kerül átadásra) kötésére. Lévén a változók csak egyszer kapnak értéket, ez nem ássa alá a transzparenciát.


Köszi, ezt nem tudtam. Ezek szerint a skálázhatóságnál az immutability számít igazán.

Egy másik (közkeletű) félreértés: a REST nem attól stateless, hogy nem használsz sessiont az alkalmazás szintjén.


De a REST attól stateless.

Egy PHP session, amit a lemezen tárolsz, semmiben sem különbözik a MySQL-ben tárolt adataidtól.


De különbözik, a session részben cache funkciókat lát el, hogy ne kelljen mondjuk a jogosultságokat újra és újra elkérni az adatbázistól ez gyakorlatilag query cache a resource state-hez. A resource state, amit az adatbázisban tárolsz, mondjuk termék lista, felhasználó lista, stb. Ami problémásabb a session-el az a másik fele, ami a client state egy darabkája. Pl hogy logged in a user, vagy hogy éppen a mobil nézetet nézi narancssárga design-al, és nem a desktopot kék design-al, és így tovább. Gyakorlatilag amit egy böngészős REST kliens-nél a monitoron látsz az mind a client state, és mind bekerülhet egy hagyományos webalkalmazásnál a session-be, ami a szerver oldalon tárolódik. A REST = representational state transfer, mint a neve is mutatja arról szól, hogy a client state és a resource state darabkáit reprezentáció formájában küldözgeti egymásnak a kliens és a szerver, és így változtatják meg egymás állapotát. A session használatával a client state egy darabkája átkerül a szerverre. Mivel a client state sokkal gyakrabban változik, mint a resource state, ezért ez egy méret (bizonyos felhasználószám) felett túl nagy terhet ró a szerverre, ami alatt az összeroppan. A public SOAP API-k legnagyobb részt azért buktak el, mert nem tudták megoldani ezt a problémát, illetve valszeg fel sem ismerték a problémát a megalkotóik.

A session cookie ugyanolyan paramétere a kérésnek mint a query string.

Nem. A query string önmagában értelmes a szerver számára, a session cookie értelmezéséhez szükség van a session storage-ben tárolt, hozzá kapcsolódó adatokra. Ezért ha session cookie-t használsz, akkor nem tudod normálisan használni a HTTP cache-et, mert a kérés nem fog tartalmazni minden információt ahhoz, hogy megfelelően összepárosítható legyen egy adott válasszal. A client state egy töredéke hiányozni fog, mert a cache-nél nem került mentésre, a session storage-ben meg azóta már megváltozott az értéke. Ha csak simán cookie-ba teszel információt, akkor az használható lehet, feltéve, hogy a HTTP cache menti a cookie-t is, és felhasználja egy-egy kérés-válasz összepárosításánál. Ebben nem vagyok biztos, nem szoktam cookie-t használni REST-el. Úgy rémlik, hogy van egy szűk lista, amit felhasznál, és ami nagyjából method + uri + auth header + accept header, vagy valami ilyesmi. Nem rémlik, hogy a cookie benne lenne, de utána fogok nzéni.

A különbség az, hogy kérés és kérés a protokoll szemszögéből független a HTTP esetén, szemben például az FTP-vel.

Igen, ez a különbség a HTTP és az FTP protokollok között. A REST és egy hagyományos webalkalmazás között meg többek között az, hogy a REST nem tárol a szerveren client state-hez tartozó információkat, a hagyományos webapp meg igen. Így a REST stateless, a hagyományos webapp meg stateful kéréseket küld. A protokoll itt csak annyit számít, hogy a HTTP lehetőséget ad stateless kérések küldésére.

szerk:

ugyanez rövidebben:
http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_3

5.1.3 Stateless

We next add a constraint to the client-server interaction: communication must be stateless in nature, as in the client-stateless-server (CSS) style of Section 3.4.3 (Figure 5-3), such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client.
Figure 5-3: The client-stateless-server style

This constraint induces the properties of visibility, reliability, and scalability. Visibility is improved because a monitoring system does not have to look beyond a single request datum in order to determine the full nature of the request. Reliability is improved because it eases the task of recovering from partial failures [133]. Scalability is improved because not having to store state between requests allows the server component to quickly free resources, and further simplifies implementation because the server doesn't have to manage resource usage across requests.

Like most architectural choices, the stateless constraint reflects a design trade-off. The disadvantage is that it may decrease network performance by increasing the repetitive data (per-interaction overhead) sent in a series of requests, since that data cannot be left on the server in a shared context. In addition, placing the application state on the client-side reduces the server's control over consistent application behavior, since the application becomes dependent on the correct implementation of semantics across multiple client versions.



szerk:

Nem rémlik, hogy a cookie benne lenne, de utána fogok nzéni.


Nem derült ki, hogy alapból nézi e a cookie tartalmát a cache. Valszeg nem, mert láttam SO-n kérdést ezzel kapcsolatban, de ki kellene próbálni. Ami kiderült, hogy a "vary: cookie" header-el biztosan beállítható a dolog. Néhány böngésző régen (5+ év) még bugos volt ezen a téren, úgy néztem, de azóta ezeket már biztosan javították.

https://tools.ietf.org/html/rfc7234#page-8

4. Constructing Responses from Caches

When presented with a request, a cache MUST NOT reuse a stored
response, unless:
o The presented effective request URI (Section 5.5 of [RFC7230]) and
that of the stored response match, and
o the request method associated with the stored response allows it
to be used for the presented request, and
o selecting header fields nominated by the stored response (if any)
match those presented (see Section 4.1), and


https://tools.ietf.org/html/rfc7234#section-4.1

4.1. Calculating Secondary Keys with Vary

When a cache receives a request that can be satisfied by a stored
response that has a Vary header field (Section 7.1.4 of [RFC7231]),
it MUST NOT use that response unless all of the selecting header
fields nominated by the Vary header field match in both the original
request (i.e., that associated with the stored response), and the
presented request.


https://tools.ietf.org/html/rfc7231#section-7.1.4

The "Vary" header field in a response describes what parts of a
request message, aside from the method, Host header field, and
request target, might influence the origin server's process for
selecting and representing this response. The value consists of
either a single asterisk ("*") or a list of header field names
(case-insensitive).
20

a session részben cache

Joó Ádám · 2015. Okt. 1. (Cs), 01.55
a session részben cache funkciókat lát el, hogy ne kelljen mondjuk a jogosultságokat újra és újra elkérni az adatbázistól ez gyakorlatilag query cache a resource state-hez


Ez egy implementációs részlet, milyen különbséget jelent ez a kliens szempontjából?

Pl hogy logged in a user, vagy hogy éppen a mobil nézetet nézi narancssárga design-al, és nem a desktopot kék design-al, és így tovább.


Ez mind ugyanúgy egy resource (az alkalmazás session) állapota, mint egy terméklista elemei. Az, hogy a listának mely elemeit látod, egy kérés paraméter függvénye, amit valószínűleg a query stringben küldesz; az, hogy a narancssárga mobilnézetet látod-e, ugyanúgy egy kérés paraméter függvénye, amit valószínűleg egy cookie-ban küldesz.

A query string önmagában értelmes a szerver számára, a session cookie értelmezéséhez szükség van a session storage-ben tárolt, hozzá kapcsolódó adatokra.


Talán még emlékszel, hogy egy időben dívott a session id-t query paraméterben küldeni… Hogy is van ez? :)

Ezért ha session cookie-t használsz, akkor nem tudod normálisan használni a HTTP cache-et, mert a kérés nem fog tartalmazni minden információt ahhoz, hogy megfelelően összepárosítható legyen egy adott válasszal.


Erre való a Vary header :)

A REST = representational state transfer, mint a neve is mutatja arról szól, hogy a client state és a resource state darabkáit reprezentáció formájában küldözgeti egymásnak a kliens és a szerver, és így változtatják meg egymás állapotát. A session használatával a client state egy darabkája átkerül a szerverre. Mivel a client state sokkal gyakrabban változik, mint a resource state, ezért ez egy méret (bizonyos felhasználószám) felett túl nagy terhet ró a szerverre, ami alatt az összeroppan.


Ez így még megáll a lábán, habár ezzel csak annyit mondtál, hogy a felhasználói adatokat kezelő alkalmazásoknak nagyobb a terhelése, mert több adatot kezelnek, mint a felhasználó nélküliek. Ezzel sokra nem megyünk. Azonban átvezet a problémához:

Igen, ez a különbség a HTTP és az FTP protokollok között. A REST és egy hagyományos webalkalmazás között meg többek között az, hogy a REST nem tárol a szerveren client state-hez tartozó információkat, a hagyományos webapp meg igen. Így a REST stateless, a hagyományos webapp meg stateful kéréseket küld. A protokoll itt csak annyit számít, hogy a HTTP lehetőséget ad stateless kérések küldésére.


A REST egy hálózati architektúra stílus, amit Roy Fieldingék a HTTP/1.1 tervezésekor fogalmaztak meg, és a HTTP/1.1-gyel megvalósítottak. Ebben az értelemben lehetetlen HTTP-t használva nem REST-et használni. A zavar abból adódik, hogy össze van mosva a sztenderd, stateless kommunikációs protokoll (a HTTP) és a felette megfogalmazott, egyedi, stateful protokoll (a webalkalmazás). A REST elvek mentén működő web szempontjából a kommunikáció stateless, mert a webszervernek nem kell állapotot tárolnia két kérés között. Az általad, az alkalmazáslogikád képében megfogalmazott protokoll azonban természetesen stateful, mert máshogy nem volna sok haszna.

A perspektíva kedvéért: a stateless HTTP alatt egyébként a TCP stateful, ami alatt az IP stateless.
21

Ez egy implementációs

inf3rno · 2015. Okt. 1. (Cs), 04.14
Ez egy implementációs részlet, milyen különbséget jelent ez a kliens szempontjából?


Semmilyen, éppen ezért ez a része maradhat a szerveren REST esetében, a másik, ami a klienshez tartozik, meg muszáj, hogy átkerüljön oda.

resource (az alkalmazás session) állapota

Hát ez bekerülhetne az aranyköpések közé. :D

Csak hogy tisztázzuk az elnevezéseket: client state = session state = application state, amik szinonímák. Mindhárom a kliens állapotát jelenti, amit mindig az aktuális kliens tart karban REST esetében. Hagyományos webappok esetében vegyesen a kliens és a szerver tartja karban, az utóbbi egy session storage-ben. A resource state, amit a szerver tart karban legtöbbször adatbázisban.

Talán még emlékszel, hogy egy időben dívott a session id-t query paraméterben küldeni… Hogy is van ez? :)


Úgy, hogyha kiragadod kontextusból, amit írok, akkor jelenthet egész mást. Nincs kedvem ismételni magam egyébként sem annak van jelentősége, hogy a request melyik részében küldöd el a session id-t, hanem hogy áthelyezi a client state és a kérés egy darabját a szerverre, amire pedig szükség volna a kesseléshez.

Erre való a Vary header :)


Nem, nem erre való a vary header.

a felhasználói adatokat kezelő alkalmazásoknak nagyobb a terhelése, mert több adatot kezelnek, mint a felhasználó nélküliek. Ezzel sokra nem megyünk.

Pedig pont ez a lényege a stateless constraint-nek, de láthatóan ez nem jut el hozzád. Ha egy public API a világ minden pontjáról fogad kéréseket, és egyszerre több millióan használják, akkor ha a szervernek kell karban tartani az összes kliens állapotát, akkor összeomlik a terhelés alatt, és ez a probléma skálázással nem megoldható a jelenlegi technológiákkal. Ennyi a történet.

A REST egy hálózati architektúra stílus, amit Roy Fieldingék a HTTP/1.1 tervezésekor fogalmaztak meg, és a HTTP/1.1-gyel megvalósítottak. Ebben az értelemben lehetetlen HTTP-t használva nem REST-et használni.

Nem értem ezeket a kijelentéseidet mire alapozod? Hadd idézzek neked Roy Fielding-től:
I am getting frustrated by the number of people calling any HTTP-based interface a REST API.

- http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
25

A resource state, amit a

Joó Ádám · 2015. Okt. 1. (Cs), 21.24
A resource state, amit a szerver tart karban legtöbbször adatbázisban.


Ugye érzed, hogy ez így definíciónak nem lesz jó?

egyébként sem annak van jelentősége, hogy a request melyik részében küldöd el a session id-t, hanem hogy áthelyezi a client state és a kérés egy darabját a szerverre, amire pedig szükség volna a kesseléshez.


Mellesleg a client state-et sem definiáltad még. A cache-elést mi akadályozza?

Nem, nem erre való a vary header.


Valóban? Akkor mire? :)

Pedig pont ez a lényege a stateless constraint-nek, de láthatóan ez nem jut el hozzád. Ha egy public API a világ minden pontjáról fogad kéréseket, és egyszerre több millióan használják, akkor ha a szervernek kell karban tartani az összes kliens állapotát, akkor összeomlik a terhelés alatt, és ez a probléma skálázással nem megoldható a jelenlegi technológiákkal. Ennyi a történet.


Valóban, ha felhasználókhoz köthető adatokat tárolsz, és sok felhasználód van, akkor sok adatod lesz. Tényleg nem értem, a javasolt megoldás szerinted az, hogy akkor ne tároljunk adatokat? Miért csak a felhasználókhoz köthető adatoktól akarunk megszabadulni?

Nem értem ezeket a kijelentéseidet mire alapozod?


Fielding doktori disszertációjára.

Hadd idézzek neked Roy Fielding-től


Ja, egy ponton túl szerintem eléggé túlpörgi ő maga is. De persze lehet, hogy csak nem értem, lásd a fenti kérdéseim.
27

Valóban, ha felhasználókhoz

inf3rno · 2015. Okt. 2. (P), 03.36
Valóban, ha felhasználókhoz köthető adatokat tárolsz, és sok felhasználód van, akkor sok adatod lesz. Tényleg nem értem, a javasolt megoldás szerinted az, hogy akkor ne tároljunk adatokat? Miért csak a felhasználókhoz köthető adatoktól akarunk megszabadulni?


Ezek nem felhasználóhoz köthető adatok, hanem munkamenethez köthető adatok. A kettő nagyon más. A felhasználóhoz köthető adatokat tárolhatod szerveren REST-nél, a munkamenethez köthetőeket viszont a kliens kell, hogy karbantartsa.

Fielding doktori disszertációjára. Ja, egy ponton túl szerintem eléggé túlpörgi ő maga is. De persze lehet, hogy csak nem értem, lásd a fenti kérdéseim.


Fielding sosem írt ilyet a disszertációjában, hogy a HTTP 1.1 megvalósítja a REST-et. Te értetted félre. Abban igazad van, hogy a HTTP 1.1-et úgy tervezték, hogy megfeleljen a REST-nek. Ez nem véletlen. A disszertáció azt írja le, hogy hogyan kellene használni a HTTP 1.1-et, mi volt a cél, amikor megtervezték a szabványt. A REST ezen kívül használhat más stateless protocol-t is, de kevés olyan jó van hozzá, mint a HTTP, ezért nem szokás ilyesmit meglépni.

A REST uniform interface constraint-jének a lényege, hogy meglévő standard-ekből építesz fel egy interface-t a webalkalmazásodhoz, pl. HTTP + URI + RDF + RDF vocabs, de használhatsz más standard-eket is ha az jobban esik. Ilyen módon lazán csatolt lesz a kliens a szerver implementációjához, mert csak az interface-hez kell szorosan csatoltnak lennie. Nem sokban különbözik ez a polimorfizmustól, ha belegondolsz. A különbség csak a méretekben van: a REST-nél webalkalmazásokról meg szabványokból összerakott uniform interface-ről van szó, amit használnak, a polimorfizmusnál meg osztályokról és az interface-ről, amit megvalósítanak.

Írtam még régebben 2 blog bejegyzést a témában, ha gondolod olvasd át őket: 1 2
32

Ezek nem felhasználóhoz

Joó Ádám · 2015. Okt. 2. (P), 22.44
Ezek nem felhasználóhoz köthető adatok, hanem munkamenethez köthető adatok. A kettő nagyon más. A felhasználóhoz köthető adatokat tárolhatod szerveren REST-nél, a munkamenethez köthetőeket viszont a kliens kell, hogy karbantartsa.


Milyen kritérium alapján döntesz, amikor tárolni kell valamit, hogy a kliensen vagy a szerveren helyezed el? Hol a határ a munkamenet és a profil között?
33

A munkamenetes adatok

inf3rno · 2015. Okt. 3. (Szo), 11.34
A munkamenetes adatok mennyisége a munkamenetek számával nő, a profil adatok mennyisége meg a felhasználók számával. Igazából ez a fajta hozzáférés a ritkább, amiről beszélünk, mert a kliens legtöbbször nem emberi vezérlés, hanem gépi vezérlés alatt szokott lenni. Kb ugyanúgy, mint a SOA-nál vagy bármelyik RPC-nél. Hogy egy példát mondjak, mondjuk egy nagyker lehetőséget ad REST service-en keresztül a kiskereskedőknek, hogy árut rendeljenek meg lekérjék a katalógust. Ilyenkor nem azt szoktuk, hogy HTML-t generálunk az info-ból, aztán hagyjuk a kiskereskedőnek, hogy beárazzon az alapján, meg megrendelje a termékeket. Hanem inkább azt szoktuk, hogy írunk egy REST kliens-t, ami CRON-al vagy domain event-el indítva bizonyos időközönként nézi a raktárkészletet, és rendel árut a nagykertől, ha fogy a készlet, illetve valamilyen felárral automatikusan beáraz a nagyker árai alapján. Ugyanígy a 3rd party facebook appoknak (amik REST kliensek, amik 3. fél szerverén futnak) a felhasználó csak jogokat ad át a saját jogaiból, de nincs hatása arra, hogy ezekkel a jogokkal mit kezdenek a 3rd party appok, vagy mikor futnak. Nem fejlesztettem még fb-ra, elképzelhető, hogy aláiratnak minden kérést a felhasználóval, vagy hogy valami idő limit van, és így a fenti állítások tévesek (bár nem ez volt a benyomásom az eddigiek alapján).
34

Egy félreértés: a

Fraki · 2016. Ápr. 2. (Szo), 12.11
Egy félreértés: a funkcionális függvények is használják a hatókörben látható változókat, az ún. szabad változóik (azok, amelyek értéke nem argumentumokként kerül átadásra) kötésére. Lévén a változók csak egyszer kapnak értéket, ez nem ássa alá a transzparenciát.


Ha itt a closure változókról van szó, akkor azt semmi nem garantálja, hogy csak egyszer kapnak értéket. Azok state-ek. Legalábbis JS-ben. Vannak szigorúbb funkcionális nyelvek, ahol nem változhatnak, de az nem JS.

Ezért egyébként a JS closure egy picit ellentmondásos jelenség, mivel olyan szempontból a funkcionális paradigba reprezentánsa, hogy a functions-are-first-class-citizen elvre épül, olyan szempontból viszont nem, hogy a lexical scope változói kőkemény state-ek.
35

Ha itt a closure változókról

inf3rno · 2016. Ápr. 2. (Szo), 15.42
Ha itt a closure változókról van szó, akkor azt semmi nem garantálja, hogy csak egyszer kapnak értéket.

const o = {x:1, y:2};
Object.freeze(o);
36

Mi az állításod? :)

Fraki · 2016. Ápr. 2. (Szo), 19.22
Mi az állításod? :)
37

Hogy garantálható

inf3rno · 2016. Ápr. 3. (V), 09.00
Hogy garantálható closure-ban, hogy csak egyszer kapjanak értéket, ha const használsz primitívek esetében, illetve objektumok esetében immutability is garantálható, ha befagyasztod az állapotukat. Szóval van lehetőség ilyesmire is, ha valaki tényleg funkcionálisan akar szabálykövetően programozgatni. (Én nem akarok.)
39

Ezek a closure-től teljesen

Fraki · 2016. Ápr. 9. (Szo), 14.26
Ezek a closure-től teljesen független dolgok, amivel JS-ben garantálható egy referencia immutabilitása. Amire én reagáltam, az egy kicsit másról szólt:

Lévén a változók csak egyszer kapnak értéket


Ha jól értem, ez egy ab ovo kijelentés, miszerint a closure biztosítja a scope változók immutabilitását - na ez nyelvfüggő. Az nem volt odaírva zárójelbe, hogy "mert amúgy a user saját magának garantálta" :)
38

Ha itt a closure változókról

Joó Ádám · 2016. Ápr. 7. (Cs), 01.00
Ha itt a closure változókról van szó, akkor azt semmi nem garantálja, hogy csak egyszer kapnak értéket. Azok state-ek. Legalábbis JS-ben. Vannak szigorúbb funkcionális nyelvek, ahol nem változhatnak, de az nem JS.


Az eredeti állítás általában a funkcionális programozás, nem pedig a JavaScriptben történő funkcionális programozás kontextusában lett megfogalmazva. JavaScriptben az argumentumok változatlanságát sem garantálja semmi.

Ezért egyébként a JS closure egy picit ellentmondásos jelenség, mivel olyan szempontból a funkcionális paradigba reprezentánsa, hogy a functions-are-first-class-citizen elvre épül, olyan szempontból viszont nem, hogy a lexical scope változói kőkemény state-ek.


A funkcionális programozás mint paradigma maga hordozza ezt a ketősséget: eredetileg a függvényekkel mint elsődleges adattípusokkal történő programozásról volt szó (lásd Lispek), később kapcsolódott hozzá az egyszeri értékadás filozófiája (Hope és követői).
40

Mindennel egyetértek, egy

Fraki · 2016. Ápr. 9. (Szo), 14.41
Mindennel egyetértek, egy dologban kötekednék csak :)

JavaScriptben az argumentumok változatlanságát sem garantálja semmi.


Ennek nem sok jelentősége van, mert az életciklusuk így is úgy is a függvényen belül marad, tehát nem state-ek.

Itt a kérdés az, hogy a scope változók state-ek vagy sem. JS-ben state-ek (PHP-ban nem).
41

Abból a szempontból igazad

Joó Ádám · 2016. Ápr. 12. (K), 01.00
Abból a szempontból igazad van, hogy szemben az örököltével az argumentumok hatókörének életciklusa a függvényhívásra korlátozódik, így az itt végzett értékadás kívülről nem észlelhető, az objektumok azonban referenciaként kerülnek átadásra, az ezeken keresztül végzett értékadás pedig már nagyon is.
42

az objektumok azonban

Fraki · 2016. Ápr. 12. (K), 23.41
az objektumok azonban referenciaként kerülnek átadásra


Ez nem így van, illetve a megfogalmazásod félrevezető. Objektumok esetében nincs másik argumentum pass policy. Ha referenciaként kerülnének átadásra, akkor reassignolni lehetne őket belülről úgy, hogy annak hatása kívül megmaradna. Ahogyan ez PHP-ban is történik a referenciaargumentumokkal, függetlenül attól, hogy primitívek, vagy sem.

Az már igaz, hogy a rajtuk "keresztül" végzett értékadás meg tud maradni, ha ez alatt azt értjük, hogy az objektum egy alkulcsára assignolunk, de ennek oka nem az, hogy az objektum referenciaként került volna átadásra, hanem az a triviális dolog, hogy az objektum nem más, mint egy referencia.

A referencia viszont, mint olyan, általában véve is egy olyan joker ebben a témában, amivel mindenféle immutability paradigmába state csempészhető, JavaScripttől teljesen függetlenül. Bár mondjuk a JavaScriptes Object.freeze-re is rámondható, hogy az alkulcsok mutábilisek maradnak, de ott van a szigorúbb closure policyt megvalósító PHP reference arg rendszere is, ami a closure változókra is él, illetve ahogy nézem, még a Haskellben is lehet szórakozni: http://stackoverflow.com/a/9422659

Itt a nagy különbség az, hogy a closure változók érték vagy referencia szerint vannak bindolva, illetve hogy mennyire könnyű ezen a skálán mozogni. Az egyik véglet a Haskell: érték szerint, de workaroundolható hozzá referencia. Középen áll a PHP: a szokásos opt-in rendszerével (& prefix), a túlszélen pedig ott a JS, ahol referenciaként vannak bindolva a closure változók, emiatt kifejezetten nehézkes garantálni a stateless jelleget.
43

Annyira próbáltam

Joó Ádám · 2016. Ápr. 13. (Sze), 01.18
Annyira próbáltam körültekintően fogalmazni, hogy végül csak sikerült leírni, amit nem akartam. De tegyük hozzá, hogy a te kijelentésed, miszerint az objektumok referenciák, sem kevésbé félrevezető, mert az objektumok nem referenciák, hanem objektumok.

A JavaScript (ahogy a Java) változói objektumra nem, csak mutatókra tudnak hivatkozni, így függvényhíváskor is mutató kerül átadásra, érték szerint, éppcsak szerettem volna elkerülni a mutató szó használatát. Szemben például a C-vel, ahol teljes objektum adható át, érték szerint.

Egy szigorúan egyszeri értékadást követő nyelvben az örökölt hatókör és az argumentumoké esetén is érdektelen volna ebből a szempontból az átadás jellege, mert hiába direkt az elérés, ha csak olvasásra jogosít.
22

Nagyjából az az előnye a

Hidvégi Gábor · 2015. Okt. 1. (Cs), 08.20
Nagyjából az az előnye a funkcionális megközelítésnek, hogy stateless.
Én ezen finomítanék, mert így ebben a formában nem fedi a valóságot. Egy program sohasem lesz stateless, ha IO-val dolgozik (99%?), állapota mindig lesz, más kérdés, hogy mennyire teljesül a "nem nézek oda" faktor.

Procedurális (imperatív) megközelítésnél a függvényeken belül a global vagy static kulcsszót kell használnod, hogy elérd a scope változóit, de az csak megegyezés kérdése, hogy mindent paraméterként adsz át, és onnantól kezdve ugyanott vagy, mint a funkcionális programozásnál, de használhatsz valódi változókat és ciklusokat, azaz átláthatóbb és gyorsabb lesz a programod.

Az OOP ebből a szempontból tragédia, hisz minden objektumnak saját állapota lehet. Ha egy osztályban használsz private vagy protected változókat, a kód ismerete nélkül sohasem tudod megmondani biztosan, hogy egy metódust ugyanazokkal a paraméterekkel meghívva (ugyanazon publikus adatokat tartalmazó objektumon) ugyanazt az eredményt kapod-e vissza. Innentől kezdve nem teljesül az a kijelentés, hogy az objektum fekete dobozként használható, azaz ebből is csak az látszik, hogy az OOP elvei mennyire instabilak.

Az oo-nak többek között ugyanez az előnye megvan, csak nála a context-be injektálunk dolgokat
Ha itt arra gondolsz, hogy például egy adatbáziskapcsolat-objektumot injektálsz egy azt használó objektumba, az pontosan ekvivalens azzal, mint amikor global kulcsszót használunk procedurálisan. Annyi különbséggel, hogy OOP-ben az injektálás után már this-szel kell hivatkozni rá, de ettől még ugyanúgy globális marad, és az alkalmazás állapotát határozza meg.
23

Ha itt arra gondolsz, hogy

inf3rno · 2015. Okt. 1. (Cs), 08.55
Ha itt arra gondolsz, hogy például egy adatbáziskapcsolat-objektumot injektálsz egy azt használó objektumba, az pontosan ekvivalens azzal, mint amikor global kulcsszót használunk procedurálisan. Annyi különbséggel, hogy OOP-ben az injektálás után már this-szel kell hivatkozni rá, de ettől még ugyanúgy globális marad, és az alkalmazás állapotát határozza meg.


Igen, de szabályozva van, hogy hol injektáljuk be. Általában a példányosításnál csináljuk, amiért sokszor egy dependency injection container felelős. Szóval ha tudni akarjuk, hogy honnan jött valami, akkor csak megkeressük, hogy melyik container példányosította az objektumot, és ha esetleg az injektált példányváltozó is objektum, akkor legtöbbször azt is ugyanaz a container példányosította, szóval azt is hamar meg tudjuk találni. Ezzel szemben a procedurálisnál nehéz megtalálni, hogy a scope-ban hol kapott legutóbb értéket egy változó. Különösen rossz a helyzet, ha több helyen is írják. Mert nem követhető könnyen, hogy ezeknek mi az időrendje. Amit Ádám javasolt, hogy csak egyszer állítsunk be értéket a scope-ban, szóval legyen immutable, az sokat javít a helyzeten.
24

Ezek mind megegyezés

Hidvégi Gábor · 2015. Okt. 1. (Cs), 09.40
Ezek mind megegyezés kérdései, akár procedurális, akár OOP technikáról beszélünk.

Az OOP-ban az objektumok állapotai jóval nagyobb problémát jelentenek, hisz ha most azt mondanád egy átlagos programozónak, hogy ezentúl ne használjon belső változókat, csak publikusakat, akkor úgy nézne rád, mint ti rám, amikor az OOP-t kritizálom.

Minden belső változó a program komplexitását növeli, és minél több van bennük, annál nagyobb a valószínűsége, hogy teszteletlen ágra fusson a program, mivel a készítője nem tud átgondolni minden interakciót, ami az objektumok között történik.

Ennél csak jobb lehet, ha nem használunk globális változókat, hanem mindent paraméterként adunk át, vagy megegyezés szerint ezeket csak függvénnyel írjuk, közvetlenül sosem.
26

Két észrevétel, ami el van

BlaZe · 2015. Okt. 1. (Cs), 22.12
Két észrevétel, ami el van bújtatva a hozzászólásodban.

Minden belső változó a program komplexitását növeli, és minél több van bennük, annál nagyobb a valószínűsége, hogy teszteletlen ágra fusson a program, mivel a készítője nem tud átgondolni minden interakciót, ami az objektumok között történik.
Pont ezért (na jó, ezért is) bontjuk nagyon pici egységekre a programokat és unit testeljük azok interface-ei mentén. A jól definiált, szemmel átlátható mennyiségű kódban tudnak gondolkodni a programozók, és tudnak jól megválasztott inputra tesztelni. Ez persze még mindig nem 100%-os lefedettséget jelent, és önmagában még semmit nem mond az alkalmazás egészének minőségéről, de ez a belépő a magas stabilitású, megbízhatóságú komplex rendszerek összerakásához. Magasabb szintű és manuális tesztek azért persze nem ártanak hozzá :)

Ennél csak jobb lehet, ha nem használunk globális változókat, hanem mindent paraméterként adunk át, vagy megegyezés szerint ezeket csak függvénnyel írjuk, közvetlenül sosem.
És akkor végre kimondtuk, hogy az encapsulation nem hülyeség :)
28

Pont ezért (na jó, ezért is)

inf3rno · 2015. Okt. 2. (P), 03.43
Pont ezért (na jó, ezért is) bontjuk nagyon pici egységekre a programokat és unit testeljük azok interface-ei mentén.


Ő szerintem a testing pyramid-re gondolt. A unit test-eket még meg tudod írni mindet, az integrációs teszteknél viszont gondban leszel, mert nem tudsz minden útvonalat tesztelni. Legalábbis hagyományos webalkalmazásoknál. Katonainál gondolom rászánják a pénzt, és minden egyes if-else-t tesztelnek.

Szvsz a biztonságot nem az adja, hogy valami tesztelt, hanem az, ha tesztek alapján fejlesztjük. Így garantálható, hogy nem került bele olyan kód, amihez nincsen legalább valamilyen szintű teszt.

És akkor végre kimondtuk, hogy az encapsulation nem hülyeség :)


Hát ja. Gábor is eljutott idáig. :D (de mindjárt kimagyarázza, ne aggódj :D)
31

A unit test-eket még meg

BlaZe · 2015. Okt. 2. (P), 22.42
A unit test-eket még meg tudod írni mindet, az integrációs teszteknél viszont gondban leszel, mert nem tudsz minden útvonalat tesztelni. Legalábbis hagyományos webalkalmazásoknál. Katonainál gondolom rászánják a pénzt, és minden egyes if-else-t tesztelnek.
Ideális világban amikor a fejlesztő leül kódolni a feladatát, ahhoz tartozik egy acceptance criteria, ami definiálja, hogy milyen állapotra, milyen inputra a programtól milyen viselkedést várunk el, gondolva edge case-ekre is. Szintén ideális világban a fejlesztés valóban az ezek mentén készített tesztek alapján folyik. Ez bár valóban nem fed le minden branchet, de a unit tesztekkel kiegészítve elég magas megbízhatóságot tud nyújtani. Természetesen nem 100%-ot, de elég jót. Én ezt nem domainhez, rendelkezésre álló pénzügyi erőforrásokhoz, hanem inkább professzionalizmushoz, igényességhez és fegyelmezettséghez kötném. Enélkül valójában a fejlesztőnek semmi garancia nincs a kezében arról, hogy a program az elvárt módon működik a tervezett körülmények között. Arról meg pláne nincs, hogy ez a későbbiekben is így lesz. És ez nem hogy nem több pénzt igényel, de az alkalmazás fejlesztése és karbantartása során rengeteget spórolunk azáltal, hogy potenciális hibalehetőségeket előre kiszűrtünk.

Ami a 100%-os lefedettséget illeti, olyan valójában nincs. Se katonai, se orvosi, se pénzügyi stb területen. Mi azt gondolom nagyon szigorú tesztek, lefedettség és processek alapján fejlesztünk, de 'csodák' mindig vannak :) Mindenre nem lehet felkészíteni egy programot.
29

Pont ezért (na jó, ezért is)

Hidvégi Gábor · 2015. Okt. 2. (P), 08.48
Pont ezért (na jó, ezért is) bontjuk nagyon pici egységekre a programokat és unit testeljük azok interface-ei mentén.
Ez a bekezdésed nekem sántít, mert bár lehet, hogy a kis egységek átláthatóak, de ettől a program komplexitása és az objektumok közti interakciók száma nem csökken.

És akkor végre kimondtuk, hogy az encapsulation nem hülyeség
Mitől lenne bármi köze az egységbezáráshoz annak, amit írtam?

Encapsulation is the packing of data and functions into a single component
(Wikipedia)
30

Ez a bekezdésed nekem sántít,

BlaZe · 2015. Okt. 2. (P), 22.01
Ez a bekezdésed nekem sántít, mert bár lehet, hogy a kis egységek átláthatóak, de ettől a program komplexitása és az objektumok közti interakciók száma nem csökken.
Nem is írtam olyat, hogy csökken. Azt írtam, hogy a kis egységre bontással a fejlesztő fókuszáltabban tud gondolkodni, könnyebben tárja fel a potenciális hibákat, helyesebben tudja megítélni az egység helyes viselkedésének garantálásához szükséges teszteket. Ez pedig nagyban hozzájárul ahhoz, hogy egy komplex program kezelhető legyen. Aztán persze ezeket az egységeket össze is kell tudni integrálni, amire szintén kell jó teszteket készíteni. De az már másik dolog. Nem mondtam ellent neked, csak leírtam a következményét a gondolatmenetednek.

Mitől lenne bármi köze az egységbezáráshoz annak, amit írtam?
Az, hogy pont az az egységbezárás, amit írtál :) Deklaráltad, hogy a változód csak az adott függvényen, függvényeken keresztül lehet elérni, módosítani. Ez az egységbezárás. Akkor is, ha erre használsz nyelvi elemet (pl objektum változói és metódusai), és akkor is, ha nem (global scope és függvény). Nyilván az objektum itt nagyobb garanciát ad, de ettől még amit írtál, pont ugyanazt célozza.
3

Mit rontok el?

Hidvégi Gábor · 2015. Szep. 1. (K), 13.39
Az utolsó példát ki szerettem volna próbálni, a megfelelő függvényeket bemásoltam egy html fájlba, majd ennyit tettem hozzá:
var eredmeny = [0, 1, 2, 3].map(
  compose(
    roundToOneDecimal, 
    Math.abs,
    Math.cos, 
    multiply.bind(null, Math.PI / 3)
  )
);
console.log(eredmeny);
De az eredménynek [0, 1, 2, 3]-at ad vissza.

Az utolsó blokkban a divide definíciója helyes?
function divide(b, a) {
  return a / b;
}
4

Hiba

Poetro · 2015. Szep. 1. (K), 14.18
Úgy tűnik a compose függvénybe került egy hiba.
function compose() {  
    var args = Array.prototype.slice.call(arguments);  
    return flow.apply(null, args.reverse());  
}
Az eredetiben kicsit máshogy nézett ki. Szólok, hogy javítsák.

A divide jó, bár nálam még divider-nek hívták, ezért is volt felcserélve az argumentum.
5

Most jó

Hidvégi Gábor · 2015. Szep. 1. (K), 14.26
Köszönöm, így már működik.