Placeholder Image

字幕表 動画を再生する

  • Thank you for that wonderful introduction, that once expected.

  • Yes, hello, I'm James, I'm going to talk to you today about practical functional programming,

  • to make sure I get through this in plenty of time, and the clock's not ticking down,

  • so I don't know how much time I've got, that's very confusing.

  • What do I mean by functional programming? Well, initially I didn't even know, itches

  • looking for ideas to talk about on Twitter a while ago, and Jan gave this idea, practical

  • function fallal programming, and I brushed it off with a little juke.

  • Got me thinking.

  • It has first class functions, and we have venttion and call backs and we have all the

  • array operations like map, filter, reduce and stuff like that.

  • But, there's a lot more to functional programming than we often acknowledge.

  • And what I would like to do is sort of demystify that a little bit.

  • Because one criticism of functional programming, it's not very practical and you need a PhD

  • to understand it.

  • What I'd like to do is sort of talk about the ways in which we are already using functional

  • ideas in mainstream JavaScript and how we can pay better attention to those and get

  • better value out of them.

  • Also promise the last occurrence of the word Monad in this talk, there's not going to be

  • a lot of type theory stuff going on.

  • What do we mean by functions? Functional programming is about programming without side effects,

  • if you're in the Parallel JavaScript talk earlier you would have heard that side effects

  • are one of the biggest barriers to parallelizing a program because you can't reason about changes

  • to a global state, makes it hard to parial rise things.

  • What does it look like to program functionally.

  • You want to calculate the length of a list, length takes a list and returns an Int that's

  • what that little type signature means.

  • So, what you could do is set the counter to 0 and increment the counter for every iment

  • and return the last value of the counter.

  • How does that run out in practice, want to get the value of the list, set the count to

  • zero, first element, increment the index, there's a second element, we increment, there's

  • a third, we increment, there's no fourth element we break the loop and induction is 3.

  • The values of the index comments ‑‑ those aren't statements that appear anywhere in

  • the program that state you have to keep in your head while you figure out what the program

  • dugs, that's what makes imperative programs hard to reason about.

  • A functional version would look like this, cough free script has three dot syntax that

  • breaks a list into the first elements and the rest of the elements.

  • If the first element is undefined the list is empty, right, so it has length 0 otherwise

  • the length one plus the length of the rest of the list, we're doing a recuresive break

  • down.

  • What does that look like in practice, so, there is a first element, so we'll pick a

  • second bunch.

  • We're just going to replace the definition, we're using it as a rewrite rule, no implicit

  • state to keep track of.

  • We do that a couple more times then we get length of the empty list, which we pick the

  • first branch of the conditional, which is 0 and result falls out.

  • So here rather than writing comments with sate in them, I've written "is" before each

  • expression, that's because they're identical.

  • Doesn't mean they have the same value at a particular point in time, they always have

  • the same value you can replace any of these expression with any of the other and the program

  • will do the same thing.

  • Substituting bitting of source code for each other makes it easy to think about, you don't

  • have to track state somewhere else in your mind or on paper.

  • Let's take another function, map, the signature means map takes a function from A to B and

  • a list of As and returns a list of Bs, the imperative version of that would be to take

  • an empty list and push ever VEX for every element in the input and return the list.

  • The functional version would be to say in is no first element, the effect is undefined

  • then return an empty list otherwise afully F to the first element and then combine that

  • with map over the rest of the list.

  • Same structure as we're doing the went, we're using list instead of numbers, say we want

  • to square the first three numbers, we go, there is a first element, so we'll take a

  • second branch, so square of one is one, we pull that out of the front and map over what's

  • left, map over what's left, square two and then, we have anmentty list, and so, the ‑‑

  • an empty list and then the result falls out.

  • These are our functional solutions to the problem, theye they work by not my stating

  • the state, they work by giving you an expression you're trying to calculatend that you can

  • replace and you do that recursively.

  • You can program by substitution by using these things.

  • The imperative solutions work by making some state and then like changing that state until

  • some condition becomes true and then handing that state off to you at the end.

  • But even though those have internal state and they're not internally functional, they

  • do exactly the same thing as these versions, because, ‑‑ they don't Mutate ‑‑

  • you can treat them as if they're the same functions.

  • That's useful, it means their state is completely ensuelated the sidefects continue leek out

  • into the rest of the program.

  • We use that in promises.

  • Promises have internal state, they are pending and they can become fulfilled or rejected

  • with a value or an error.

  • But, every time you call then it will yield the same value to you, you never get to see

  • what's actually going on inside of the promise, Jo us call then and value pops out and it's

  • always the same value you can treat a promise as an immutable value, which is useful.

  • A couple weeks ago too much ash ford had this really good essay, vents are bad primitive

  • for data flow, they require distribution of mutable state around your code and it's not

  • idiomatic or plea Saint to flow through data through events.

  • We can use types to answer that question, if we consider the FS.read file, that takes

  • a path name, an encoding, a call back and it returns nothing, and the call back is itself

  • a function that takes an error, value and alts returns nothing.

  • Now function that returns nothing must have side effects because if a function has no

  • return value and no sidefects what why you calling it? It's not doing anything.

  • The thing that you think of not having side effects is reading a file we work with these

  • things using side effects functions, completely.

  • When we're dealing with all these asynchronous things all the time we have to make sure all

  • the side effects happen in the right order so the program doesn't get into a bizarre

  • state, and trying to did that on concurrent programs is very, very difficult, which you

  • doubt know.

  • It gets even harder when you try odo a lot of things at same time, if you want to read

  • a file and request a URL and get something out of a data business at the same time, you

  • use Async.parallel, and do operation, when they complete you get a cull back and you

  • get value for those things, what would I do if I wanted to get the value of the file before

  • the other things are completed and do something with it.

  • Because this, if any of the things fail, I won't get any of the values, I only work on

  • the file even if the HDP request fails, I could pull the file operation out top.

  • I have an FS.read file ‑‑ but now I've made the program slower because the second

  • thing are blocked on the first thing completing, so I deparol liesed it.

  • That kills the performance, it's convenient, but, you've traded convenience for performance.

  • What you actually have do is keep the parallel construct to make sure the IO happens at the

  • same time but plug your processing into the bit where the file is requested, the more

  • you do these things the data processing, what you're forced do by the way that we schedule

  • things in asynchronous programs your programs get very messy, edge tangled you get into

  • what we roll call

  • back hell, it's not a sin tackic thing about your code creeping across the page, it's the

  • inability to reason able when things are happening in your program and make sure they happen

  • as efficiently as possible, to do this you is to construct your program in a very specific

  • way with call backs in all the right places.

  • So, last year I read this article called call backs are imperative, promises are functional,

  • which had this quote "the future of promises is that they remain immune to change circumstances."

  • I've already mentioned that ‑‑ I got some laughs ‑‑ I already mention how

  • promises look like immutable values because then always gives you the same result out

  • once the tasks are completed, but this is also true in a second way that I department

  • realize at the same, the promises that deal in changing promises much more easily.

  • Before when I was using the asing module I had to change my program quite radically,

  • I could make a big structural change to it to make quite a small requirement change.

  • But say those functions return promises my FS.read and database dot get always return

  • promises of strings, I can call all those functions and put the results in an array

  • and all the Io will just happen in parallel, but now I have an array of promises of strings.

  • And if I want just the first ‑‑ if I just want the file out of that, then I can

  • get the first promise out of the list and do something with the result.

  • If I want to wait for all of the things to finish, then I can call promise.all with the

  • documents and I get a promise that will give me all of the results when they're ready,

  • if I don't want to deal with the file on it's own, I just delete the documents.zero line,

  • I can just add lines for those, I don't have to make bige structural changes to my program.

  • You keep the ability to keep your IO happening in parallel, but you keep the convenience

  • of being able to work with it easily, that's really important for dealing with programs

  • that change a lot over time.

  • And the reason this works is that I can ask for the value out of the file promise and

  • promise to all can also answer the value out of that file promise, and it'll work both

  • times because you can keep asking for the value over and over again, and you won't repeat

  • any work, what it means to be immutable, it's rejuiceble, right.

  • So let's talk ‑‑ it's reusable.

  • We talk about I in very imperative terms, we say then takes a function and that function

  • will be invoked when the task completes with the value of the task, if it's completed it

  • will be invoked with the value of the task it returns a promise and that promise will

  • be invoked with the return of the call back, we talk about then, do this, then do this,

  • and then do that.

  • But if you think about the types of things that are involved, what then really does it

  • takes a promise of A and function there A to B and returns a promise of B, if we have

  • a promise of a string and we call then with a function that counts the word in a string,

  • string.split on spaces.length what we get is a promise of an I Int, we might not have

  • the Int, but that's the type of the value we have, we continue to do more processing

  • on it, transform it into another thing, transform it into another thing, and use the promise

  • itself as a value, not the thing that's inside of it.

  • This is exactly the same thing a ray.map works it takes list of something and a function

  • from A to B around returns a list of B, right, so if you have a list of strings and you map

  • a word counting function over the list, you now have a list of word counts just as if

  • you called then with a word counting function you turn a promise of a string into a promise

  • of a word count, a promise is just a container, it's a list, they're the same thing, the operations

  • are the same thing, a container of up type, container of another type with a mapping function

  • between them, and because they're just containertion you can compos containers, they're just another

  • type of data structure, if you have a list of promise of strings you turn that into a

  • list of promises of Int by mapping over the array this is no different than mapping over

  • nested array you have two maps inside of one another.

  • Promise to all is the same, we talk at promise to all as in you give it a bunch of promises

  • as input then it will give you one promise when it resolve, we talk about it in time

  • terms, if you talk about it in type terms, it turns a container inside out you give it

  • a list of promises and it gives you a promise of a list, you get one promise back that will

  • give you a list of all of the things.

  • This also solves the Zaga ‑‑ Zalg oproblem, personification of the problem with writing

  • call back APIs that will execute a call back synchronously or asynchronously.

  • A equals Null promise.then X equals data equals X on statement before line three otherwise

  • your program won't work, if it ran asin concurrent resolution noselite would break.

  • The only reason it's a problem is because you have side effect, you care about things

  • happening in a certain order something that's been changed into another state before another

  • statement runs, if instead you do your process inside of the promise construct you don't

  • think of a promise as a thing to get value out of you think of it as a value on it's

  • own that you can do commutation inside of, then that problem goes away.

  • Want to talk about laciness, another big topic in functional programming (Lazi iness) I want

  • to show you a little bit of Hasckell code ‑‑ Haskell code.

  • Cough free script is written as a colon, first element colon, the rest of the elements.

  • So map we've already seen, map of an Ety list is an empty list,and map in the general case,

  • you apply F to X and combine that to map over the rest of the list, it looks weird but we

  • saw it working in coffee script, filter works pretty much the same way, filter of an empty

  • list is an empty list, where peer Cex is true you keep X you you combine with the filter

  • of the rest of the list otherwise you throw X away and keep the rest of the list, we keep

  • X braced on whether the predicate is true for the value.

  • Take, first and end elements of allis, take where N is less than 0 you don't want to take

  • any more data, take of an empty list because there's no more day the to take, so empty,

  • then take in a general case, you pop the first element and take N minus one of what's left

  • and apply that recuresively the value will fall out.

  • This lets you do somethinger, very powerful.

  • Haskell has infinite data structure, list of ..., is to infinity, the filter would never

  • return, it tries to process the whole array at once, looks what happen in Haskel, I'm

  • going to program by replacing functioncal with the definition, so let's see what happens,

  • so, even if one is false, so we drop the one.

  • Even if 2 is true, so pull the 2 out front and filter over what's left.

  • Now, we've destructured the umer end to map so we can pull that 2 through squaring functions,

  • we structured the upper end to take, can pull that expression out front, three minus what's

  • left, levering with two, one value through the list all the way through the pipeline.

  • We do more time.

  • We come down to take one, and one more time again, we come down to take zero and we have

  • three elements out front.

  • We know by definition, take of zero is an empty list and the result pops out.

  • We didn't have to look at the rest of the infinite list, we didn't have to care about

  • it.

  • That's really really powerful, skipping work you don't need to do to get the result you

  • want.

  • Again, Tom ash worth summed it up nicely there are two ways to combine transformations you

  • perform the first transformation on the whole collection before moving on to the second

  • or perform all the transformations on the first element of the collection before moving

  • on to the second.

  • Now the first version is how JavaScript deals with array, filter the whole array and hand

  • that off to the map and the map maps over the whole array, reiterates it over how ever

  • many operations you have, the second way is the way Haskell deal was arrays

  • 's nothing inherent about the data structure that says you is to process them this way,

  • you can use either strategy.

  • Just as Haskll deals with lists as if they were streams question can deal with streams

  • as if they're lists.

  • Streams they're big part of node programming right.

  • Huge part of node programming, really node's core feature, these streams.

  • So, do something a little Funky.

  • So, I'm going to make a classical map that inherit from stream.platform, and transform

  • method is going to replay that function to incoming chunk and push it.

  • Going to add a map method to stream.prototype that pipes the stream through map transform,

  • it's a similar lair toy array.map, we're taking a stream of As, and a function from A to B

  • and a stream of A to B.