Description
Hello World
Before starting...
- "$input", hmmm confused about this
- "program" or "module"? Should I be exporting a function or writing a
program that looks atprocess.argv
? - META: there should probably be more explanation as to why I'm
doing this. I don't yet understand what this has to do with
functional javascript. NOTE that it doesn't necessarily have to
do with functional js, but some text saying "hey, this is just to
get your feet wet, make sure you got the basics, etc" would be great!
During...
- Okay, I'm trying
process.argv[2]
. I'm getting back a string, but
that string is wrapped in quotes. That's weird, but maybe okay? - Verify doesn't like it. It looks like I'm getting the right
string, but verify wants it without the quotes. - I guess I'll just
.replace(/"/g, '')
the string, even though I
know that's not not correct, just so I can see what the solution is.
After finishing...
- AH okay, so
$input
is a global that gets injected into the
script. That is weird to me – I think it'd be better if either
A) it used (and expected) process.argv[2] OR
B) it expected a module with exactly one function that does what is expected.- But to counter my own point, this is functional javascript not
functional node.js. So maybe the global injection is the right
move? Not sure...
- But to counter my own point, this is functional javascript not
Higher Order Functions
Before starting...
- Ah ha, so I see the global injection is going to be a common
theme. I don't really like that – it feels to magical, I like the
method used inlevelmeup
of building modules or taking things
on argv. This isn't a dealbreaker by any means, but I think it
does encourage better real-world practices.
After finishing...
-
Oh, I solved it totally differently:
function repeat(fn, n) { if (n <= 0) return; fn() return repeat(fn, --n) } repeat($operation, 5)
I do feel like this might be slightly less "clever" – and it
teaches good recursion principles (always check your end
condition first, always make sure your input converges on the end
condition).
Basics: Map
Before starting...
-
The introduction could use a little more explanatory text as to
what a map is and why it's useful – something like the following:"We often have to take a set of data and modify it before we
output it. For example, say we get this back from our database:
[{first: 'Brian', last: 'Brennan'}, {first: 'Alex', last:
'Sexton'}]And we want a list of full names. We can do this with a for loop,
but we have to manually keep track of indices and push to a new
array on our own. A better way to handle this is by using a
map
. Amap
is a higher order function that operates each
element of an array and returns a new array with each element
transformedSo to get our list of full names, our mapping function would be:
function fullName(item) { return item.first + ' ' + item.last }
And we'd call it like so:
var fullNames = entries.map(fullName)
Notice that the map function returns a new array – it doesn't
modify the array in place. This is a very important principle in
functional programming: it's always better to take input and
return new output rather than modify things in place.Your exercise is to take an array of numbers and return a new
array with each number replaced by itself * 2..."
After finishing...
-
I think this might be cleaner and help illustrate the power of
passing function references aroundfunction double(n) { return n * 2 } console.log($input.map(double))
I think the solution could also then explain the power of
creating small, generically useful functions and then using them
in maps to transform sets of data.
Basics: Filter
Before starting...
- [workshopper] – is there a way to get code fences working in
workshopper so that when the instructions are printed out the
things fenced in ```js are syntax highlighted? (That'd be a rad
module, and there's like a 90% chance substack has already
written it).
After finishing
-
Again, I like seperating out the functions rather than using
anonymous functions:function isShort(txt) { return txt.length < 50 } function getMessage(obj) { return obj.message } function getShortMessages(arr) { return arr.map(getMessage).filter(isShort) } console.log(getShortMessages($input))
I think it better illustrates composition.
Basics: Reduce
Before starting...
-
Some illumination for why a reduce is useful would be
good. Perhaps even something that explains it in terms of mapping:"A
reduce
(also known as a "fold left") is useful when you want
to take a bunch of values and turn it into just one. A good
example of this is taking the sum of a series:[1,2,3,4,5].reduce(function sum(accum, value){ return accum + value }, 0) // 0 + 1 + 2 + 3 + 4 + 5 = 15
The first argument to the
reduce
functor is known as the
accumulator
– this keeps track of a value as the functor
processes values. The second argument to.reduce
is known as
the seed value. This is useful for priming theaccumulator
so
the functor doesn't have to do conditional checking against it to
see whether or not it's the first value.It's important to choose the correct seed value for the operation
you are performing! For example,0
works well for addition, but
if we wanted to get the factorial rather than the sum, we would
want to use1
instead:[1,2,3,4,5].reduce(function factorial(accum, value){ return accum * value }, 1) // 1 * 1 * 2 * 3 * 4 * 5 = 120
(Fun fact: this has to do with 0 being the identity value for the
addition operation and 1 being the identity value for the
multiplication operation [citation needed])" -
Woah, the boilerplate is confusing to me! I think to properly
explainreduce
, the operation needs to be transparent – I think
part of doingreduce
properly involves selecting the proper
seed
. That's an important lesson to teach, and one that gets
obscured if the operation is opaque. It's also a lesson about
reduce
but the boilerplate function saysmap
.- AH I missed the part "i.e. Use Array#reduce to implement
Array#map." – I think this is should be after a basic reduce
lesson. This is more advancedreduce
, I think.
- AH I missed the part "i.e. Use Array#reduce to implement
After finishing...
- I felt cheated by the "extra credit". None of the previous
lessons taught the optional arguments, and the lesson instructions
didn't say that we were supposed to implement
Array.prototype.map
exactly. The function signature in the
"boilerplate" is also indicating that we should only implement
something with 2-arity. - Again I think the solution is too clever – I'm not sure we should
expect people to understandfn.call
andfn.apply
unless we
make a lesson about it earlier on.
Partial Application without Bind
Before starting...
- Should probably explain partial application a little bit
first. Remind people about higher-order functions and tell them
that in this case, we want to make a function that takes input and
returns a new function. - If people have to do that argument variable "turn to a real array"
hacking, that might be A) be a bad thing, or B) need to be
explained.
After finishing
-
Yeah, doing the arguments trick without explanation isn't
great. This should either be explained in an earlier lesson (along
with call and apply), or there should be a solution that doesn't
require it. I vote for the former. Here is my solution for
reference, though I think the published one is probably better
becauseArray.prototype.concat
is rad and I don't use it nearly
enough.function logger(namespace) { return function logFn() { var args = [].slice.call(arguments) args.unshift(namespace) return console.log.apply(console, args) } } $test(logger)
Partial Application with Bind
Before starting...
- Function arity should be explained (in an earlier lesson)
After finishing
- Pretty good!
Function spies
Before starting...
- I think the purpose of spies – testing, debugging – should be
explained upfront. Also that spies should probably only be used
for testing, debugging because the behavior is opaque to anyone
down the line. (I am very open to debate on this issue!)
After finishing
-
Ah, I was confused by the naming –
Spy
with an uppercase S – and
I was expecting to build a constructor. I guess in a way it is?
I'm torn on this one. My solution was this:function Spy(object, methodName) { if (!(this instanceof Spy)) return new Spy(object, methodName) this.count = 0 var originalFn = object[methodName] object[methodName] = function secretAgent() { this.count++ return originalFn.apply(object, arguments) }.bind(this) } $test(Spy)
Conclusion
This is awesome. I think with some interstitial lessons about call
and apply
, maybe one about reduce
before implementing map
with
reduce
, this can be a super rad workshopper! I'm definitely willing
to help with creating/editing the intro text to add more
theory/reasons behind why someone would want to use FP techniques in
JS – I think that's important. Thank you so much for getting this
started!