fact, fiction, and fp

Post on 23-Jun-2015

266 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Updated version of Fact/Fiction talk.

TRANSCRIPT

• Lots of functional JavaScript code

• Benefits and drawbacks

• Functional Optimizations

• Lots of unexplained type signatures

You will see

• Detailed explanations of the code

• Definitions for functional constructs

• Extensive performance analysis

You won’t see

59 // src :: FlickrItem -> URL 60 var src = compose(_.get('m'), _.get('media')); 61 62 // srcs :: FlickrSearch -> [URL] 63 var srcs = compose(map(src), _.get('items')); 64 65 // images :: FlickrSearch -> [DOM] 66 var images = compose(map(imageTag), srcs); 67 68 // tags :: FlickrSearch -> [DOM] 69 var tags = compose(toP, _.countBy(_.id), _.filter(_.isEmpty), chain(split(‘ ‘))); 70 71 // imagesAndTags :: Tuple [DOM] [DOM] 72 var imagesAndTags = liftA2(Tuple, images, tags); 73 74 // widget :: String -> PictureBox 75 var widget = compose(PictureBox, map(imagesAndTags), getJSON, url); 76 77 /////////////////////////////////////////////////////////////////////////////////// 78 79 mconcat([widget('cats'), widget('dogs')]).fork(log, function(x){ 80 compose(setHtml($('#flickr')), _.first)(x) 81 compose(setHtml($(‘#tag_cloud')), _.last)(x) 82 }); 83 });

No matter what language you work in, programming in a functional style provides benefits. You should do it whenever it is convenient, and you should think hard about the decision when it isn't convenient.

Questions

• Why might one do this?

• What problems are there?

• Is it performant?

• Is it “production ready?”

Pointfree

var replace = curry(function(regex, x, replaceable) { return replaceable.replace(regex, x);});

var squish = replace(/\s+/g, '');

squish("I like to move it move it"); // Iliketomoveitmoveit

We can curry

var wackyText = compose(capitalize, reverse, squish)

wackyText(“turtle power") // Rewopeltrut

We can compose

Pointfree

var clientApp = compose(render, doThings, httpGet(‘/posts'))

var serverApp = compose(sendJSON, doThings, Db.all(‘posts’))

var shellApp = compose(display, doThings, prompt("what's up?"))

httpGet('/post/2', function(json){ renderPost(json);});

Pointfree

httpGet('/post/2', function(json, err){ renderPost(json, err);});

Pointfree

httpGet(‘/post/2’, renderPost)

Pointfree

httpGet(‘/post/2’, renderPost)

Pointfree

GOOD

Pointfree

var goodArticles = function(articles) { return _.filter(articles, function(article){ return _.isDefined(article); })}

Pointfree

var goodArticles = function(articles) { return _.filter(articles, function(article){ return _.isDefined(article); })}

var goodArticles = filter(isDefined)

Pointfree

var goodArticles = function(articles) { return _.filter(articles, function(article){ return _.isDefined(article); })}

var goodArticles = filter(isDefined)

Pointfree

var goodArticles = function(articles) { return _.filter(articles, function(article){ return _.isDefined(article); })}

var goodArticles = filter(isDefined)

GOOD

Pointfree

var getChildren = function(el) { return el.childNodes;}

var getAllChildren = function(els) { return _.map(els, function(el) { return getChildren(el); });}

Pointfree

var getChildren = function(el) { return el.childNodes;}

var getAllChildren = function(els) { return _.map(els, function(el) { return getChildren(el); });}

var getChildren = get('childNodes')var getAllChildren = map(getChildren)

Pointfree

var getChildren = function(el) { return el.childNodes;}

var getAllChildren = function(els) { return _.map(els, function(el) { return getChildren(el); });}

var getChildren = get('childNodes')var getAllChildren = map(getChildren)

Pointfree

var getChildren = function(el) { return el.childNodes;}

var getAllChildren = function(els) { return _.map(els, function(el) { return getChildren(el); });}

var getChildren = get('childNodes')var getAllChildren = map(getChildren)

GOOD

Pointfree

var grandChildren = function(selector) { var el = document.querySelector(selector); var children = getChildren(el); return map(getChildren, children);}

var grandChildren = compose( map(getChildren) , getChildren , document.querySelector )

Pointfree

var grandChildren = function(selector) { var el = document.querySelector(selector); var children = getChildren(el); return map(getChildren, children);}

var grandChildren = compose( map(getChildren) , getChildren , document.querySelector )

GOOD

Pointfree

var video = { src: 'http://youtube.com?v=23423' , description: 'family matters ep1' , screenshots: [ { url: 'i.ytimg.com/OrIxGlo.webp', size: ‘120x120' } , { url: 'i.ytimg.com/3rAxRdb.webp', size: ‘1020x764' } ] }

var thumb = compose(_.first, _.get(‘screenshots'))

var thumbUrl = compose(_.get('url'), thumb)

var thumbWithHost = compose(concat('http://youtube.com'), thumbUrl)

thumbWithHost(video)// http://youtube.com/i.ytimg.com/OrIxaBJ9Glo.webp

Pointfree

var video = { src: 'http://youtube.com?v=23423' , description: 'family matters ep1' , screenshots: [ { url: 'i.ytimg.com/OrIxGlo.webp', size: ‘120x120' } , { url: 'i.ytimg.com/3rAxRdb.webp', size: ‘1020x764' } ] }

var thumbUrl = compose(_.get('url'), thumb)

var thumb = compose(_.first, _.get(‘screenshots'))

var thumbWithHost = compose(concat('http://youtube.com'), thumbUrl)

thumbWithHost(video)// TypeError

Pointfree

var video = { src: 'http://youtube.com?v=23423' , description: 'family matters ep1' , screenshots: [ { url: 'i.ytimg.com/OrIxGlo.webp', size: ‘120x120' } , { url: 'i.ytimg.com/3rAxRdb.webp', size: ‘1020x764' } ] }

var thumbUrl = compose(_.get('url'), thumb)

var thumb = compose(_.first, _.get(‘screenshots'))

var thumbWithHost = compose(concat('http://youtube.com'), thumbUrl)

thumbWithHost(video)// TypeError

Meh

First Class?

["10", "50", "20"].map(function(x){ return parseInt(x); });// [10, 50, 20]

["10", "50", "20"].map(parseInt);// [10, NaN, NaN]

First Class?

["10", "50", "20"].map(function(x){ return parseInt(x); });// [10, 50, 20]

["10", "50", "20"].map(parseInt);// [10, NaN, NaN]

// "The Madness of King JavaScript" by Reg Braithwaite (raganwald)

BAD

First Class?

var phone = { dial: function(x){ console.log("DIALING: ", this.format(x)); }, format: function(n) { return n.replace(/-/g, '') }};

var numbers = ["234-3535-2342", “1-653-124-8321"];

numbers.map(function(x){ return phone.dial(x); });// DIALING: 23435352342// DIALING: 16531248321

First Class?

var phone = { dial: function(x){ console.log("DIALING: ", this.format(x)); }, format: function(n) { return n.replace(/-/g, '') }};

var numbers = ["234-3535-2342", “1-653-124-8321"];

numbers.map(function(x){ return phone.dial(x); });// DIALING: 23435352342// DIALING: 16531248321

numbers.map(phone.dial);

First Class?

var phone = { dial: function(x){ console.log("DIALING: ", this.format(x)); }, format: function(n) { return n.replace(/-/g, '') }};

var numbers = ["234-3535-2342", “1-653-124-8321"];

numbers.map(function(x){ return phone.dial(x); });// DIALING: 23435352342// DIALING: 16531248321

numbers.map(phone.dial);// TypeError: Object #<Object> has no method 'format'

First Class?

var phone = { dial: function(x){ console.log("DIALING: ", this.format(x)); }, format: function(n) { return n.replace(/-/g, '') }};

var numbers = ["234-3535-2342", “1-653-124-8321"];

numbers.map(function(x){ return phone.dial(x); });// DIALING: 23435352342// DIALING: 16531248321

numbers.map(phone.dial.bind(phone));// DIALING: 23435352342// DIALING: 16531248321

First Class?

var phone = { dial: function(x){ console.log("DIALING: ", this.format(x)); }, format: function(n) { return n.replace(/-/g, '') }};

var numbers = ["234-3535-2342", “1-653-124-8321"];

numbers.map(function(x){ return phone.dial(x); });// DIALING: 23435352342// DIALING: 16531248321

numbers.map(phone.dial.bind(phone));// DIALING: 23435352342// DIALING: 16531248321

BAD

//+ render :: [Tag] -> DOMvar render = compose($(‘#tag-cloud').html, tagView, addFontSizes)

//+ tagCloud :: Params -> Future(DOM)var tagCloud = compose(map(render), httpGet('/tags'))

//+ setHtml :: String -> Html -> IO(DOM)var setHtml = curry(function(selector, h) { return IO(function() { return $(selector).html(h); });});

//+ render :: [Tag] -> IO(DOM)var render = compose(setHtml(‘#tag-cloud'), tagView, addFontSizes)

//+ tagCloud :: Params -> Future(IO(DOM))var tagCloud = compose(map(render), httpGet('/tags'))

Pointfree

• Removes unnecessary code

• High level declarative apps

• Encourages generic code

• Encourages purity

Pointfree

• Order of definition matters

• Weird interop with imperative community (impure, swiss army fn’s, & ‘this’)

Demos

Typeclass

Typeclass

promise.then(function(x){ return x + 1 }); // Promise(2)

[1].map(function(x) { return x + 1 }); // [2]

event_stream.subscribe(function(x) { return x + 1 }) // EventStream(2)

var Container = function(val) { this.val = val;}

Container.prototype.of = function(x) { return new Container(x);}

Container.prototype.chain = function(f) { return f(this.val);};

Typeclass

Typeclass

(a -> b) -> M(a) -> M(b)

We can map over them

Typeclass

(a -> b) -> M(a) -> M(b)

We can map over them

promise.map(function(x){ return x + 1 }); // Promise(2)

[1].map(function(x) { return x + 1 }); // [2]

event_stream.map(function(x) { return x + 1 }) // EventStream(2)

Just(1).map(function(x) { return x + 1 }) // Just(2)

Nothing.map(function(x) { return x + 1 }) // Nothing

IO(1).map(function(x) { return x + 1 }) // IO(2)

Typeclass

M(M(a)) -> M(a)

We can flatten/un-nest them

Typeclass

M(M(a)) -> M(a)

We can flatten/un-nest them

[['hello']] -> ['hello']

Just(Just(true)) -> Just(true)

Typeclass

M(a -> b) -> M(a) -> M(b)

We can apply functions within them

Typeclass

M(a -> b) -> M(a) -> M(b)

We can apply functions within them

var finished = curry(function(click, anim) { alert(“Finished!"); });

EventStream.of(finished).ap(button_clicks).ap(animation_done);

Typeclass

M(a) -> M(a) -> M(a)

We can combine them

Typeclass

M(a) -> M(a) -> M(a)

We can combine them

Api.get(‘/unstarred’).concat(Api.get('/starred'))

// Future([Item])

Typeclass

M(N(a)) -> MN(a)

We can compose them

Typeclass

M(N(a)) -> MN(a)

We can compose them

//+ askQuestion :: IO(Maybe(String))compose(map(map(storeResponse)), askQuestion)

//+ askQuestion :: Compose(IO(Maybe(String)))compose(map(storeResponse), Compose, askQuestion)

Typeclass

M(N(a)) -> N(M(a))

We can commute them

Typeclass

M(N(a)) -> N(M(a))

We can commute them

compose(sequenceA, map(readFile))

// Future([String])

Typeclass

We can derive lots of this!

derive(MyType, [Functor, Applicative, Foldable])

*https://github.com/fantasyland/fantasy-land/pull/66

Typeclass

//+ validate :: UserAttrs -> Either([Error], UserAttrs)//+ saveUser :: UserAttrs -> Future(User)//+ emailPassword :: User -> Future(Email)

//+ saveThenEmail :: UserAttrs -> Future(Email)var saveThenEmail = compose(chain(emailPassword), saveUser)

//+ createUser :: UserAttrs -> Either([Error], Future(Email))var createUser = compose(map(saveThenEmail), validate)

Typeclass

//+ validate :: UserAttrs -> Either([Error], UserAttrs)//+ saveUser :: UserAttrs -> Future(User)//+ emailPassword :: User -> Future(Email)

//+ saveThenEmail :: UserAttrs -> Future(Email)var saveThenEmail = compose(chain(emailPassword), saveUser)

//+ createUser :: UserAttrs -> Either([Error], Future(Email))var createUser = compose(map(saveThenEmail), validate)

GOOD

GOOD

Typeclass

//+ lookup :: String -> {String: a} -> a|nullvar lookup = curry(function(x, obj) { return obj[x];})

var upperName = compose(toUpperCase, lookup(‘name'))

upperName({name: “Tori Amos"}) // “TORI AMOS”

upperName({first_name: "Tori", last_name: “Spelling"}) // BOOM!

Typeclass

//+ safeLookup :: String -> {String: a} -> Maybe(a)var safeLookup = curry(function(x, obj) { return Maybe.fromNullable(obj[x]);});

var upperName = compose(map(toUpperCase), safeLookup(‘name'))

upperName({name: “Tori Amos”})// Just(“TORI AMOS”)

upperName({first_name: "Tori", last_name: “Spelling”})// Nothing

Typeclass

//+ safeLookup :: String -> {String: a} -> Maybe(a)var safeLookup = curry(function(x, obj) { return Maybe.fromNullable(obj[x]);});

var upperName = compose(map(toUpperCase), safeLookup(‘name'))

upperName({name: “Tori Amos”})// Just(“TORI AMOS”)

upperName({first_name: "Tori", last_name: “Spelling”})// Nothing

GOOD

GOOD

Typeclass

//+ readFile :: Future(Maybe(String))

//+ lipogram :: String -> Future(Maybe(String))var lipogram = compose(map(map(replace(/e/ig))), readFile);

Typeclass

//+ readFile :: String -> Compose(Future(Maybe(String)))var readFile = compose(Compose, _readFile)

//+ lipogram :: String -> Compose(Future(Maybe(String)))var lipogram = compose(map(replace(/e/ig)), readFile);

Typeclass

var WebApp = ReaderT(StateT(Future))

Typeclass

Meh

var WebApp = ReaderT(StateT(Future))

Typeclass

do a <- Just 3 b <- Just 1 return a + b

Just(3).chain(function(a) { return Just(1).chain(function(b) { return Maybe.of(a + b); });};

JavaScript

Haskell

Typeclass

do a <- Just 3 b <- Just 1 return a + b

liftM2(add, Just(3), Just(1))JavaScript

Haskell

Typeclass

do post_id <- param "id" post <- lift $ findPost post_id return (toJSON post)

chain(compose(lift, map(toJSON), findPost), param(‘id'))

JavaScript

Haskell

(lift . (fmap toJSON) . findPost) =<< param "id"

Typeclass

do post_id <- param "id" post <- lift $ findPost post_id return (toJSON post)

chain(compose(lift, map(toJSON), findPost), param(‘id'))

JavaScript

Haskell

(lift . (fmap toJSON) . findPost) =<< param "id"

Meh

Typeclass

do post_id <- param "id" post <- lift $ findPost post_id return (toJSON post)

chain(compose(lift, map(toJSON) findPost), param(‘id'))

JavaScript

Haskell

(lift . (fmap toJSON) . findPost) =<< param "id"

Typeclass

do post_id <- param "id" post <- lift $ findPost post_id return (toJSON post)

chain(compose(WebApp.lift, map(toJSON) findPost), param(‘id'))

JavaScript

Haskell

(lift . (fmap toJSON) . findPost) =<< param "id"

Typeclass

//+ savePhotos :: [Path] -> Future([Photo])var savePhotos = compose(traverse(uploadPhoto), map(toBlob))

//+ gift :: Future(User) -> Maybe(Future(Card))var gift = compose(traverse(sendCard, Maybe.of), map(_.get("birthday"))

Typeclass

//+ savePhotos :: [Path] -> Future([Photo])var savePhotos = compose(traverse(uploadPhoto), map(toBlob))

//+ gift :: Future(User) -> Maybe(Future(Card))var gift = compose(traverse(sendCard, Maybe.of), map(_.get("birthday"))

BAD

Typeclass

• Universal interface (across languages)

• Generic programs/libraries

• Safer programs

• Theory backed

• Intuition

• Missing polymorphic “point”

• Working ‘blind’ with stacked containers

• No syntactic sugar

Typeclass

Demos

Shortcut Fusion

// g :: forall b. (t -> b -> b) -> b -> b

reduce(c, n, build(g)) = g(c, n)

Shortcut Fusion

//build :: (forall b. (a -> b -> b) -> b -> b) -> [a]var build = function(g){ return g(concat, []);}

//+ map :: (a -> b) -> [a] -> [b]var map = curry(function(f, xs){ return build(function(c, n){ return reduce(function(acc, x){ return c(f(x), acc); }, n, xs); });});

Shortcut Fusion

var sum = reduce(add, 0);

var sqr = function(x) {return x * x }

var sumSqs = compose(sum, map(sqr)) // reduce(function(acc, x){ return add(sqr(x), acc) }, 0);

Compile while youcompose

//+ doorman :: [User] -> Uservar doorman = compose(first, filter(gte(21)), map(_.get('age')));

var addTwenty = memoize(function(x) {return x + 20;

})

Memoization

var addTwenty = memoize(function(x) {return x + 20;

})

addTwenty(10) // 30addTwenty(10) // 30 (didn't run)addTwenty(11) // 31

Memoization

Memoization

var getPosts = memoize(function(id) { return new Future(function(rej, res) { $.getJSON('/posts/'+id, res); });});

Memoization

var getPosts = memoize(function(id) { return new Future(function(rej, res) { $.getJSON('/posts/'+id, res); });});

getPosts(2) // FuturegetPosts(2) // Future (didn't run)getPosts(3) // Future

Memoization

var pQuery = $.toIO()

pQuery(".troll") // IO(function(){ return $(".troll") })

pQuery.runIO() // [Dom, Dom]pQuery.runIO() // [Dom, Dom, Dom]

Parallel code

foldMap(Sum, [2, 3, 5])

Parallel code

liftA3(fn, A1, A2, A3)

Parallel code

var longCalc // Int -> Future(Int)

var collectResults = curry(function(rslt1, rslt2, rslt3){})

liftA3(collectResults, longCalc(20), longCalc(30), longCalc(10))

Parallel code

var hasName // Attrs -> Validation

liftA3(save, hasName, hasEmail, hasPhone)

THANKS!@drboolean

top related