Awesome
Słowniczek programowania funkcyjnego
Programowanie funkcyjne (FP) ma wiele zalet, w wyniku czego zyskuje popularność. Jednak każdy paradygmat programowania ma swój unikalny żargon i programowanie funkcyjne nie jest tutaj wyjątkiem. Poprzez dostarczenie Wam słowniczka, mamy nadzieję, że jego nauka będzie łatwiejsza.
Przykłady są przedstawione w JavaScript (ES2015). Dlaczego JavaScript?
Tam, gdzie to możliwe, dokument ten korzysta z terminów zdefiniowanych w Fantasy Land spec
Tłumaczenia
Spis treści
<!-- RM(noparent,notop) -->- Arity
- Higher-Order Functions (HOF)
- Closure
- Partial Application
- Currying
- Auto Currying
- Function Composition
- Continuation
- Purity
- Side effects
- Idempotent
- Point-Free Style
- Predicate
- Contracts
- Category
- Value
- Constant
- Functor
- Pointed Functor
- Lift
- Referential Transparency
- Equational Reasoning
- Lambda
- Lambda Calculus
- Lazy evaluation
- Monoid
- Monad
- Comonad
- Applicative Functor
- Morphism
- Setoid
- Semigroup
- Foldable
- Lens
- Type Signatures
- Algebraic data type
- Option
- Functional Programming Libraries in JavaScript
Arity
Arność. Liczba argumentów pobieranych przez funkcję. Pochodzi od słów takich jak unary, binary, ternary itd. To słowo wyróżnia się tym, że składa się z dwóch przyrostków ("-ary" i "-ity"). Np. dodawanie pobiera dwa argumenty, zatem jest zdefiniowane jako funkcja binarna lub jako funkcja o arności równej 2. Ludzie, którzy preferują greckie korzenie nazewnictwa (zamiast łacińskich), mogą czasem taką funkcję nazywać "diadyczną". Kiedy ilość argumentów funkcji może być różna, jest ona nazywana funkcją ze zmienną liczbą argumentów (variadic). Natomiast funkcja binarna musi mieć dwa i tylko dwa argumenty. Zobacz poniżej: Currying i Partial Application
const sum = (a, b) => a + b
const arity = sum.length
console.log(arity) // 2
// Arność funkcji sum wynosi 2
Higher-Order Functions (HOF)
Funkcja, która pobiera funkcję jako argument i/lub zwraca funkcję.
const filter = (predicate, xs) => xs.filter(predicate)
const is = (type) => (x) => Object(x) instanceof type
filter(is(Number), [0, '1', 2, null]) // [0, 2]
Closure
Closure to trwały zakres zmiennych dostępnych dla danej funkcji w momencie, kiedy została utworzona. Jest to ważne dla partial application.
const addTo = (x) => {
return (y) => {
return x + y
}
}
Możemy wywołać addTo
, podając pewną liczbę jako argument i otrzymamy funkcję z "wbudowaną" wartościąx
.
var addToFive = addTo(5)
W tym przypadku x
jest zawarte w closure addToFive
z wartością 5. Możemy zatem wywołać addToFive
z y
i wtedy otrzymać pożądany wynik.
addToFive(3) // => 8
To działa, ponieważ zmienne, które są w zasięgu rodzica, nie są usuwane przez garbage-collectora tak długo, jak funkcja jest zachowana.
Closure są powszechnie wykorzystywane w event handlerach, dzięki czemu nadal mają dostęp do zmiennych zdefiniowanych u ich rodziców, kiedy są wywołane.
Dalsza lektura
Partial Application
Partial application polega na tym, że tworzymy nową funkcję, która z góry jest wypełniona niektórymi argumentami z funkcji bazowej.
// Pomocnik do tworzenia funkcji korzystających z partial application
// Pobiera funkcję i część argumentów
const partial = (f, ...args) =>
// zwraca funkcję, która pobiera resztę argumentów
(...moreArgs) =>
// i wywołuje oryginalną funkcję posiadającą je wszystkie
f(...args, ...moreArgs)
// Przykładowa funkcja
const add3 = (a, b, c) => a + b + c
// Wykorzystujemy partial application do "wbudowania" liczb 2 i 3 do funkcji add3. Daje to nam jednoargumentową funkcję.
const fivePlus = partial(add3, 2, 3) // (c) => 2 + 3 + c
fivePlus(4) // 9
Możesz także użyć Function.prototype.bind
, by wykonać partial application dla funkcji w JS:
const add1More = add3.bind(null, 2, 3) // (c) => 2 + 3 + c
Partial application pomaga w tworzeniu prostszych funkcji poprzez "wbudowywanie" do nich danych dla funkcji bardziej złożonych. "Curried functions" automatycznie korzystają z partial application.
Currying
Proces zamiany funkcji, która pobiera wiele argumentów, w funkcję, która pobiera je wszystkie na raz.
Za każdym razem, kiedy funkcja jest wywołana, przyjmuje tylko jeden argument i zwraca funkcję, która pobiera jeden argument. Proces trwa do momentu, aż wszystkie argumenty zostaną przekazane.
const sum = (a, b) => a + b
const curriedSum = (a) => (b) => a + b
curriedSum(40)(2) // 42.
const add2 = curriedSum(2) // (b) => 2 + b
add2(10) // 12
Auto Currying
Przekształcanie funkcji, która pobiera wiele argumentów, w jedną, która ma taką cechę: jeśli podamy mniej argumentów niż wynosi jej arność, to zwraca funkcję, która pobiera resztę potrzebnych argumentów. Kiedy funkcja dostaje odpowiednią liczbę argumentów, to wtedy obliczana jest jej wartość.
lodash & Ramda mają funkcję curry
, która działa w ten sposób.
const add = (x, y) => x + y
const curriedAdd = _.curry(add)
curriedAdd(1, 2) // 3
curriedAdd(1) // (y) => 1 + y
curriedAdd(1)(2) // 3
Dalsza lektura
Function Composition
Łączenie dwóch funkcji w trzecią funkcję tak, że wyjście (output) jednej funkcji jest wejściem (input) drugiej.
const compose = (f, g) => (a) => f(g(a)) // Definicja
const floorAndToString = compose((val) => val.toString(), Math.floor) // Użycie
floorAndToString(121.212121) // '121'
Continuation
W dowolnym miejscu w programie, ta część kodu, która pozostała jeszcze do wykonania, nazywana jest kontynuacją.
const printAsString = (num) => console.log(`Wynik: ${num}`)
const addOneAndContinue = (num, cc) => {
const result = num + 1
cc(result)
}
addOneAndContinue(2, printAsString) // 'Wynik: 3'
Kontynuacje często się pojawiają w programowaniu asynchronicznym, kiedy program potrzebuje zaczekać na otrzymanie danych, zanim będzie mógł kontynuować. Odpowiedź często jest przekazywana do reszty programu, która jest kontynuacją po jej otrzymaniu.
const continueProgramWith = (data) => {
// Kontynuuję wykonywanie programu z tymi danymi
}
readFileAsync('path/to/file', (err, response) => {
if (err) {
// obsługa błędu
return
}
continueProgramWith(response)
})
Purity
Czystość. Funkcja jest "czysta" (pure) wtedy, kiedy wartość przez nią zwracana jest zdeterminowana jedynie przez jej wartości wejściowe i nie ma przy tym żadnych "efektów ubocznych" (side effects).
const greet = (name) => `Hi, ${name}`
greet('Brianne') // 'Hi, Brianne'
W przeciwieństwie do poniższych przykładów:
window.name = 'Brianne'
const greet = () => `Hi, ${window.name}`
greet() // "Hi, Brianne"
Wynik powyższego przykładu bazuje na danych przechowywanych poza funkcją...
let greeting
const greet = (name) => {
greeting = `Hi, ${name}`
}
greet('Brianne')
greeting // "Hi, Brianne"
... a w tym przykładzie modyfikowany jest stan na zewnątrz funkcji.
Side effects
Funkcja lub wyrażenie ma "efekt uboczny" (side effect) wtedy, kiedy oprócz zwracania wartości, zachodzi interakcja (odczyt lub zapis) z zewnętrznym, mutowalnym stanem.
const differentEveryTime = new Date()
console.log('IO is a side effect!')
Idempotent
Funkcja jest idempotentna wtedy, kiedy jej ponowne wywołanie na jej wartości (zobacz poniżej) nie zwraca innego wyniku.
f(f(x)) ≍ f(x)
Math.abs(Math.abs(10))
sort(sort(sort([2, 1])))
Point-Free Style
Sposób pisania funkcji, w którym nie mają one jawnej definicji argumentów, z jakich korzystają. Ten styl zazwyczaj potrzebuje wykorzystania currying lub innych funkcji wyższego rzędu. Styl znany również jako "Tacit programming".
// Na początku mamy:
const map = (fn) => (list) => list.map(fn)
const add = (a) => (b) => a + b
// Później:
// Nie w stylu points-free - `numbers` to argument jawny
const incrementAll = (numbers) => map(add(1))(numbers)
// W stylu points-free - lista jest argumentem niejawnym
const incrementAll2 = map(add(1))
incrementAll
identyfikuje i korzysta z parametru numbers
, więc to nie jest w stylu points-free. Funkcja incrementalAll2
jest napisana poprzez łączenie funkcji i wartości w taki sposób, że nie ma wzmianki o jej argumentach. Jest to zatem points-free.
Definicja funkcji napisanej w stylu points-free wygląda tak, jak normalne przypisania bez function
czy =>
.
Predicate
Predykat to funkcja, która zwraca albo prawdę (true), albo fałsz (false) dla podanej wartości. Powszechnym zastosowaniem predykatu jest callback dla array filter.
const predicate = (a) => a > 2
;[1, 2, 3, 4].filter(predicate) // [3, 4]
Contracts
Kontrakt określa obowiązki i gwarancje zachowania z funkcji lub wyrażenia w momencie wykonywania. Zachowuje się jak zestaw zasad, które są oczekiwane przez wejście i wyjście funkcji lub wyrażenia. Kiedykolwiek kontrakt zostanie naruszony, błędy są raportowane.
// Zdefiniuj nasz kontrakt : int -> int
const contract = (input) => {
if (typeof input === 'number') return true
throw new Error('Contract violated: expected int -> int')
}
const addOne = (num) => contract(num) && num + 1
addOne(2) // 3
addOne('some string') // Kontrakt naruszony: oczekiwany jest int -> int
Category
Kategoria (w teorii kategorii) jest kolekcją obiektów i morfizmów między nimi. W programowaniu, typy zwykle zachowują się jak obiekty, a funkcje jak morfizmy.
Aby kategoria była poprawna, muszą być spełnione 3 zasady:
- Musi być morfizm tożsamości, który mapuje obiekt do siebie.
Gdzie
a
jest obiektem w jakiejś kategorii, tam musi być funkcja postaci:a -> a
. - Morfizmy muszą być "składalne".
Gdzie
a
,b
ic
są obiektami jakiejś kategorii if
jest morfizmem postacia -> b
orazg
jest morfizmem postacib -> c
;g(f(fx))
musi być równoważne(g • f)(x)
. - Kompozycja musi być asocjacyjna.
f • (g • h)
jest tym samym, co(f • g) • h
Ponieważ te zasady rządzą kompozycją na bardzo abstrakcyjnym poziomie, teoria kategorii jest świetna w odkrywaniu nowych sposobów komponowania rzeczy.
Dalsza lektura
Value
Value (wartość) to cokolwiek, co może zostać przypisane do zmiennej.
5
Object.freeze({name: 'John', age: 30}) // Funkcja `freeze` wymusza niemutowalność
;(a) => a
;[1]
undefined
Constant
Stała (constant) to zmienna, do której nie można już nic przypisać, gdy raz została już zdefiniowana.
const five = 5
const john = Object.freeze({name: 'John', age: 30})
Stałe cechuje [referential transparency](#referential-transparency. That is, they can be replaced with the values that they represent without affecting the result.
With the above two constants the following expression will always return true
.
john.age + five === ({name: 'John', age: 30}).age + (5)
Functor
Obiekt, który implementuje funkcję map
, która - działając na każdej wartości obiektu i tworząc nowy obiekt - trzyma się dwóch zasad:
Zachowuje tożsamość
object.map(x => x) ≍ object
Jest kompozycyjna (zobacz niżej)
object.map(compose(f, g)) ≍ object.map(g).map(f)
(f
, g
są dowolnymi funkcjami)
Powszechnym funktorem w JavaScripcie jest Array
, ponieważ stosuje się do dwóch zasad funktora:
;[1, 2, 3].map(x => x) // = [1, 2, 3]
oraz
const f = x => x + 1
const g = x => x * 2
;[1, 2, 3].map(x => f(g(x))) // = [3, 5, 7]
;[1, 2, 3].map(g).map(f) // = [3, 5, 7]
Pointed Functor
Obiekt z funkcją of
, która umieszcza w nim pojedynczą wartość any.
ES2015 udostępnia Array.of
, który zmienia tablice w pointed functory.
Array.of(1) // [1]
Lift
Lifting jest wtedy, kiedy bierzesz wartość i wrzucasz ją do obiektum tak jak w pointed functor. Jeśli liftingujesz funkcję do Applicative Functor, możesz sprawić, że zadziała także na tych wartościach, które są w tym funktorze.
Aby ułatwić korzystanie z funkcji na funktorach, niektóre implementacje mają funkcje lift
lub liftA2
.
const liftA2 = (f) => (a, b) => a.map(f).ap(b) // zauważ, że to jest `ap`, a nie `map`
const mult = a => b => a * b
const liftedMult = liftA2(mult) // ta funkcja działa teraz na funktorach tak, jak tablica
liftedMult([1, 2], [3]) // [3, 6]
liftA2(a => b => a + b)([1, 2], [3, 4]) // [4, 5, 5, 6]
Lifting to jednoargumentowa funkcja i używanie jej robi to samo, co map
.
const increment = (x) => x + 1
lift(increment)([2]) // [3]
;[2].map(increment) // [3]
Referential Transparency
Referential transparency cechuje się tym, że wyrażenie może być podmienione na swoją wartość bez zmieniania zachowania programu.
Mamy funkcję greet
:
const greet = () => 'Hello World!'
Każde wywołanie greet()
może być zamienione na Hello World!
, zatem greet
jest referencyjnie transparentne.
Equational Reasoning
Kiedy aplikacja jest złożona z wyrażeń i pozbawiona side effectów, prawdy o systemie można wyprowadzić z jego części.
Lambda
Funkcja anonimowa, która może być traktowana jak wartość.
;(function (a) {
return a + 1
})
;(a) => a + 1
Lambdy są często przekazywane jako argumenty do funkcji wyższego rzędu.
;[1, 2].map((a) => a + 1) // [2, 3]
Możesz przypisać wyrażenie lambda do zmiennej.
const add1 = (a) => a + 1
Lambda Calculus
Gałąź matematyki, która wykorzystuje funkcje, aby stworzyć uniwersalny model obliczeń.
Lazy evaluation
Lazy evaluation to mechanizm wartościowania typu call-by-need. Ewaluacja jest opóźniona do momentu, kiedy wartość jest potrzebna. W językach funkcyjnych pozwala to na struktury takie jak nieskończone listy, które normalnie nie byłyby dostępne w języku imperatywnym, gdzie sekwencjonowanie poleceń jest istotne.
const rand = function*() {
while (1 < 2) {
yield Math.random()
}
}
const randIter = rand()
randIter.next() // Każde wywołanie zwraca losową wartość. Wyrażenie jest obliczane wtedy, kiedy jest potrzebne.
Monoid
Obiekt z funkcją, która "łączy" ten obiekt z innym obiektem tego samego typu.
Przykładem prostego monoidu jest dodawanie liczb:
1 + 1 // 2
W tym przypadku liczba jest obiektem, a +
jest funkcją.
Musi również istnieć wartość tożsamości (identity value) - taka, że kiedy połączyć ją z wartością, to ona się nie zmienia. Zobacz poniżej:
Wartością tożsamości dla dodawania jest 0
.
1 + 0 // 1
Jest także wymagane to, żeby grupowanie operacji nie wpływało na wynik (asocjatywność):
1 + (2 + 3) === (1 + 2) + 3 // true
Konkatenacja tablic również tworzy monoid:
;[1, 2].concat([3, 4]) // [1, 2, 3, 4]
W tym przypadku wartością tożsamości jest pusta tablica []
;[1, 2].concat([]) // [1, 2]
Jeśli mamy funkcje tożsamości oraz kompozycji (obie), one także tworzą monoid:
const identity = (a) => a
const compose = (f, g) => (x) => f(g(x))
foo
jest dowolną jednoargumentową funkcją.
compose(foo, identity) ≍ compose(identity, foo) ≍ foo
Monad
Monada jest obiektem z funkcją of
oraz chain
. chain
jest jak map
z tym wyjątkiem, że "odgnieżdża" zagnieżdżony obiekt.
// Implementacja
Array.prototype.chain = function (f) {
return this.reduce((acc, it) => acc.concat(f(it)), [])
}
// Użycie
Array.of('cat,dog', 'fish,bird').chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird']
// Różnica w stosunku do map
Array.of('cat,dog', 'fish,bird').map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']]
of
w innych językach funkcyjnych jest znane także jako return
chain
w innych językach jest znane także jako flatmap
i bind
Comonad
Comonad to obiekt, który ma funkcje extract
oraz extend
.
const CoIdentity = (v) => ({
val: v,
extract () {
return this.val
},
extend (f) {
return CoIdentity(f(this))
}
})
extract
wydobywa wartość z funktora.
CoIdentity(1).extract() // 1
extend
wykonuje funkcję na comonadzie. Funkcja ta powinna zwracać ten sam typ, jakim jest ten comonad.
CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)
Applicative Functor
Funktorem aplikacyjnym jest obiekt z funkcją ap
. Funkcja ta aplikuje funkcję z tego obiektu do wartości innego obiektu tego samego typu.
// Implementacja
Array.prototype.ap = function (xs) {
return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}
// Pzykładowe użycie
;[(a) => a + 1].ap([1]) // [2]
Jest to użyteczne, kiedy masz dwa obiekty i chcesz zaaplikować funkcję binarną do ich zawartości.
// Tablice, które chcesz połączyć:
const arg1 = [1, 3]
const arg2 = [4, 5]
// łącząca funkcja - w tym przypadku trzeba zastosować currying, aby działało, jak trzeba
const add = (x) => (y) => x + y
const partiallyAppliedAdds = [add].ap(arg1) // [(y) => 1 + y, (y) => 3 + y]
W wyniku otrzymujesz tablicę funkcji, na której możesz wywołać ap
, aby otrzymać wynik:
partiallyAppliedAdds.ap(arg2) // [5, 6, 7, 8]
Morphism
Morfizm to funkcja transformacji.
Endomorphism
Endomorfizm to funkcja, której typ wejściowy (input) jest taki sam, jak jej typ wyjścia (output).
// uppercase :: String -> String
const uppercase = (str) => str.toUpperCase()
// decrement :: Number -> Number
const decrement = (x) => x - 1
Isomorphism
Izomorfizm to para przekształceń między 2 typami obiektów, która jest strukturalna z natury i żadne dane nie są utracone.
Na przykład: współrzędne 2D mogłyby być przechowywane jako tablica [2,3]
lub obiekt {x: 2, y: 3}
.
// Dostarczenie funkcji do konwersji w obu kierunkach czyni je izomorficznymi.
const pairToCoords = (pair) => ({x: pair[0], y: pair[1]})
const coordsToPair = (coords) => [coords.x, coords.y]
coordsToPair(pairToCoords([1, 2])) // [1, 2]
pairToCoords(coordsToPair({x: 1, y: 2})) // {x: 1, y: 2}
Setoid
Setoid to obiekt, który ma funkcję equals
, która może być użyta, aby porównać inne obiektu tego samego typu.
Spraw, żeby tablica była setoidem:
Array.prototype.equals = function (arr) {
const len = this.length
if (len !== arr.length) {
return false
}
for (let i = 0; i < len; i++) {
if (this[i] !== arr[i]) {
return false
}
}
return true
}
;[1, 2].equals([1, 2]) // true
;[1, 2].equals([0]) // false
Semigroup
Obiekt, który ma funkcję concat
, która łączy go z innym obiektem tego samego typu.
;[1].concat([2]) // [1, 2]
Foldable
Obiekt, który ma funkcję reduce
, która może przekształcić ten obiekt do jakiegoś innego typu.
const sum = (list) => list.reduce((acc, val) => acc + val, 0)
sum([1, 2, 3]) // 6
Lens
Lens to struktura (często obiekt lub funkcja), która łączy w parę getter oraz niemutujący setter dla jakiejś struktury danych.
// Korzystamy z [Ramda's lens](http://ramdajs.com/docs/#lens)
const nameLens = R.lens(
// getter dla właściwości "name" obiektu
(obj) => obj.name,
// setter dla właściwości "name"
(val, obj) => Object.assign({}, obj, {name: val})
)
Posiadanie pary gettera i settera dla danej struktury daje kilka istotnych możliwości.
const person = {name: 'Gertrude Blanch'}
// wywołaj getter
R.view(nameLens, person) // 'Gertrude Blanch'
// wywołaj setter
R.set(nameLens, 'Shafi Goldwasser', person) // {name: 'Shafi Goldwasser'}
// odpal funkcję na wartości w strukturze
R.over(nameLens, uppercase, person) // {name: 'GERTRUDE BLANCH'}
Lensy są także kompozycyjne. To pozwala na proste, niemutowalne aktualizacje głęboko zagnieżdżonych danych.
// Ten lens skupia się na pierwszym elemencie niepustej tablicy
const firstLens = R.lens(
// pobierz pierwszy element tablicy
xs => xs[0],
// niemutujący setter dla pierwszego elementu tablicy
(val, [__, ...xs]) => [val, ...xs]
)
const people = [{name: 'Gertrude Blanch'}, {name: 'Shafi Goldwasser'}]
// Poimo tego, co być może zakładasz, lensy komponują się od lewej do prawej.
R.over(compose(firstLens, nameLens), uppercase, people) // [{'name': 'GERTRUDE BLANCH'}, {'name': 'Shafi Goldwasser'}]
Inne implementacje:
- partial.lenses - Smakowity lukier składniowy oraz wiele potężnych możliwości
- nanoscope - Płynny interfejs
Type Signatures
Funkcje w JavaScripcie często zawierają komentarze, które wskazują na typy ich argumentów oraz zwracanych wartości.
W społeczności występują spore różnice, ale często można zaobserwować te wzorce:
// nazwaFunkcji :: typPierwszegoArgumentu -> typDrugiegoArgumentu -> typZwracany
// add :: Number -> Number -> Number
const add = (x) => (y) => x + y
// increment :: Number -> Number
const increment = (x) => x + 1
Jeśli argumentem jest funkcja, to pisana jest wtedy w nawiasie.
// call :: (a -> b) -> a -> b
const call = (f) => (x) => f(x)
Litery a
, b
, c
, d
są używane do oznaczenia, że argument może być dowolnego typu. Następująca wersja map
pobiera funkcję (która przekształca wartość pewnego typu a
do innego typu b
) i tablicę wartości typu a
, i finalnie zwraca tablicę wartości typu b
.
// map :: (a -> b) -> [a] -> [b]
const map = (f) => (list) => list.map(f)
Dalsza lektura
- Ramda's type signatures
- Najbardziej odpowiedni przewodnik
- Co to jest Hindley-Milner? na Stack Overflow
Algebraic data type
Typ złożony, stworzony z umieszczenia różnych typów razem. Dwie powszechne klasy typów algebraicznych to sum oraz product.
Sum type
Jest to połączenie dwóch typów w jeden nowy. Nazwa "sum" wzięła się z tego, że liczba możliwych wartości w typie wynikowym jest sumą typów wejściowych.
JavaScript nie ma tego rodzaju typów, ale możemy skorzystać zSet
ów, by niejako je "udawać":
// wyobraź sobie, że zamiast setów mamy tu typy, które mogą posiadać tylko takie wartości
const bools = new Set([true, false])
const halfTrue = new Set(['half-true'])
// Typ weakLogic zawiera sumę wartości z bools i halfTrue
const weakLogicValues = new Set([...bools, ...halfTrue])
Typy sum są czasem nazywane typami unii (union types), dyskryminowanch unii (discriminated unions) lub unii oznaczonych (tagged unions).
W JavaScripcie istnieje kilka bibliotek, które pomagają przy definiowaniu i korzystaniu z unii.
Flow ma union types, a TypeScript ma Enums, które spełniają tę samą rolę.
Product type
Product type łączy typy razem w sposób, który prawdopodobnie jest Ci bardziej znany:
// point :: (Number, Number) -> {x: Number, y: Number}
const point = (x, y) => ({ x, y })
Nazwa wzięła się stąd, że wszystkie możliwe wartości tej struktury danych są produktem innych wartości. Wiele języków ma typ "tuple", który jest najprostszym sformułowaniem product type.
Zobacz także Teoria setów.
Option
Opcja to sum type z dwoma przypadkami, zwykle nazywanymi Some
i None
.
Opcja jest użyteczna w komponowaniu funkcji, które mogą nie zwracać wartości.
// Definicja naiwna
const Some = (v) => ({
val: v,
map (f) {
return Some(f(this.val))
},
chain (f) {
return f(this.val)
}
})
const None = () => ({
map (f) {
return this
},
chain (f) {
return this
}
})
// maybeProp :: (String, {a}) -> Option a
const maybeProp = (key, obj) => typeof obj[key] === 'undefined' ? None() : Some(obj[key])
Użyj chain
do ustawienia kolejności funkcji, które zwracają Option
.
// getItem :: Cart -> Option CartItem
const getItem = (cart) => maybeProp('item', cart)
// getPrice :: Item -> Option Number
const getPrice = (item) => maybeProp('price', item)
// getNestedPrice :: cart -> Option a
const getNestedPrice = (cart) => getItem(cart).chain(getPrice)
getNestedPrice({}) // None()
getNestedPrice({item: {foo: 1}}) // None()
getNestedPrice({item: {price: 9.99}}) // Some(9.99)
Option
jest znane również jako Maybe
. Some
jest czasami nazywane Just
. None
jest czasem nazywane Nothing
.
Biblioteki programowania funkcyjnego w JavaScripcie
- mori
- Immutable
- Ramda
- ramda-adjunct
- Folktale
- monet.js
- lodash
- Underscore.js
- Lazy.js
- maryamyriameliamurphies.js
- Haskell in ES6
- Sanctuary
- Crocks
P.S: To repo się udało dzięki udziałowi różnych ludzi!