How to Make Front-End
More Functional
Julia Gao
@ryoia
{ "firstName" : "Julia"
, "lastName" : "Gao"
, "profession" : "Software Developer"
, "company" : "O.C. Tanner"
, "likesMath" : true
, "language" : "JavaScript"
, "likesJS" : Maybe
, "twitter" : "@ryoia"
}
- Functional Programming Concepts
- Maybe Types
- JavaScript alternatives
What The Functional?
- Immutable Data
- Pure Functions
- Composable Functions
var unchangeable = 2;
unchangeable = 3; // oh hell no
But ... doesn't every variable get changed?
Is mutable data good? Or just easy?
- No control over parameters
- No control over outputs
- Any changes on the same variables can produce bugs
- Not thread safe
- No reference sharing (more complexity)
It is so easy to ...
var scores = [90, 78, 100, 94, 80];
scores.concat(86);
But ... isn't copying everything slow?
Clojure(Script), Haskell(PureScript), Erlang...
Persistent Data Structure
Ok so it is not necessarily inefficient...
- No more locking on multi-thread operations
- Copy === Create a new reference (constant time)
- Value comparison === reference comparison
- Data consistency!
- Provides peace of mind
For/While Loops
function factorial(n) {
if (n === 0) return 1;
else {
return n * factorial(n - 1);
}
}
function factorial(n) {
var result = 1;
for (var i = 1; i <= n; i++) {
result *= i;
}
return result;
}
Loops encourage mutation
Pure Functions
Same input, same ouput
10 / 2
|-6|
10^2
Benefits
- Get expected results, every single time
- Easy to refactor
- Better organized code base
- Easier to bring people on board
split, slice, toLowerCase, toUpperCase, Math[...], etc...
JavaScript has pure functions...Lots!
var programs = [
...
];
function programChecker(program) {
if (program && program.name && programs.indexOf(program.name) > -1) {
return true;
}
return false;
}
Composable Functions
°
f ° g
f(g(x))
var foo = 'bar';
foo.toUpperCase().split('A').concat(['foo']);
//["B", "R", "foo"]
// not pure, mutable functions
var x = 'this sentence can be changed';
function mutation(str) {
if (x.indexOf(str) > -1) {
x = 'oops';
}
}
x.toUpperCase().indexOf('CHANGED'); // 21
mutation('sentence');
x.toUpperCase().indexOf('CHANGED'); // -1
// pure functions
var x = 'hopefully this sentence will not be changed';
function newCreation(str) {
if (x.indexOf(str) > -1) {
return 'oops';
}
return x;
}
x.toUpperCase().indexOf('CHANGED'); // 36
newCreation('sentence'); // oops
x.toUpperCase().indexOf('CHANGED'); // 36
function getDetails(group) {
group.exactId = group.id ? group.id : 123;
app.sendRequest('/url' + group.exactId, function(err, details) {
if (details.images && details.images !== '') {
group = imageProcesser(details.images, group);
return [details, group];
} else {
return 'images not found';
}
});
}
function imageProcessor(images, group) {
if (images && images.length > 0) {
...
group.imgs = images;
}
return group;
}
function getDetails(group) {
var id = group.id ? group.id : 123;
app.sendRequest('/url/' + id, function(err, details) {
if (details.images && details.images !== '') {
var processed = imageProcessor(details.images);
return [details, processed];
} else {
return 'images not found';
}
});
}
function imageProcessor(images, group) {
if (images && images.length > 0) {
...
return Object.assign({}, group, {imgs: images});
}
return group;
}
Types!
Every expression has types!
Your code cares about types...
using System;
public class Test
{
public static void Main()
{
Console.WriteLine("Hello" * 1);
}
}
error CS0019: Operator `*' cannot be applied to operands of type `string' and `int'
'1' * 2 -> 2
'hi' * 10 -> NaN
Even JavaScript cares about types...sort of
function checkAllTheThings(shouldBeNumber) {
if (isNaN(shouldBeNumber)) { return null; }
if (typeof shouldBeNumber === 'number') { ... }
}
Unit Test Much?
Don't you want to just ... not do that?
module WordNumber where
import Data.List (intercalate)
digitalToWord :: Int -> String
digitalToWord n
| n == 1 = "One"
| n == 2 = "Two"
| n == 3 = "Three"
| n == 4 = "Four"
| n == 5 = "Five"
| n == 6 = "Six"
| n == 7 = "Seven"
| n == 8 = "Eight"
| n == 9 = "Nine"
| otherwise = "wrong"
digits :: Int -> [Int]
digits n = count n []
count :: Int -> [Int] -> [Int]
count n c
| div n 10 /= 0 = count (div n 10) (combine n c)
| otherwise = (combine n c)
combine :: Int -> [Int] -> [Int]
combine x ys = [mod x 10] ++ ys
wordNumber :: Int -> String
wordNumber n = intercalate "-" $ map digitalToWord (digits n)
JavaScript Alternatives?
ImmutableJS
const regularMap = new Map()
regularMap.set(1, 'hi')
// Map { 1 => 'hi'}
regularMap.set(2, 'bye')
// Map { 1 => 'hi', 2 => 'bye'}
const immutableMap = Immutable.Map({1: 'hi'})
const changedMap = immutableMap.set({2: 'bye'})
// immutableMap.size -> 1
// changedMap.size -> 2
ClojureScript
(def v [1 2 3]) -> [1 2 3]
(conj v 4) -> [1 2 3 4]
v -> [1 2 3]
(defn increment [person]
(assoc person :age (+ 1 (:age person))))
PureScript
module Add where
import Control.Monad.Eff.Console (log)
main = log "An addition function!"
add :: Int -> Int -> Int
add x y = x + y
type Person = { name :: String
, age :: Int
}
increment :: Person -> Person
increment p = p { age = p.age + 1 }
Haskell: Purely functional language, typed,
promote modular functions
PureScript: Compiles to JS (easy to integrate),
fast growing community
Clojure: JVM (easy to integrate), not typed,
promote modular functions
ClojureScript: Compiles to JS, engaging community
F#: Typed, C# -> F#
Conclusion
- Immutable data structure is more efficient
- FP produces more reliable code
- FP makes us think more before coding
Thank you!
http://slides.com/ryoia/fp-js#/
Julia Gao
@ryoia
strange-loop-fp-js
By ryoia
strange-loop-fp-js
- 1,638