├── .gitignore
├── README.md
├── _config.yml
├── _includes
├── footer.html
└── head.html
├── _layouts
└── workshop.html
├── _sass
├── _columns.scss
├── _content.scss
├── _forms.scss
├── _graph.scss
├── _header.scss
├── _next-nav.scss
├── _syntax-highlighting.scss
├── _window.scss
├── font
│ ├── _animation.scss
│ └── _fontello.scss
├── globals
│ ├── _colors.scss
│ ├── _mixins.scss
│ ├── _reset.scss
│ └── _size.scss
└── main.scss
├── _workshop
├── 00-introduction.js
├── 01-1-context.js
├── 01-2-arguments.js
├── 01-3-prototype.js
├── 02-1-applicative.js
├── 02-2-map.js
├── 02-3-filter.js
├── 02-4-reduce.js
├── 02-5-lodash.js
├── 02-6-point-free.js
├── 02-7-context.js
├── 03-1-factories.js
├── 03-2-higher-order.js
├── 03-3-functions-with-functions.js
├── 03-4-fluent.js
├── 03-5-arity.js
├── 04-1-partial-application.js
├── 04-2-functions-from-functions.js
├── 05-1-currying.js
├── 05-2-compose.js
├── 05-3-pipelines.js
├── 05-4-parse-url.js
├── 06-shared-state.js
├── 07-collections.js
└── 08-done.js
├── assets
├── component-tree.svg
├── font
│ ├── LICENSE.txt
│ ├── fontello.eot
│ ├── fontello.svg
│ ├── fontello.ttf
│ └── fontello.woff
├── github.png
├── logo.svg
└── omniscient.svg
├── css
├── main.css
└── vendor
│ └── base16-mocha-dark.css
├── gulpfile.js
├── index.html
├── notes.md
├── package.json
├── previous_presentations
├── .gitignore
├── dag1
│ ├── 1_intro.js
│ ├── 2_applicative.js
│ ├── 3_functions.js
│ ├── 4_partially.js
│ ├── 5_curry_compose.js
│ ├── 6_immutability.js
│ ├── 7_collections.js
│ ├── README.md
│ ├── agenda.md
│ ├── books.json
│ ├── package.json
│ ├── slides
│ │ ├── font
│ │ │ ├── 0AKsP294HTD-nvJgucYTaIbN6UDyHWBl620a-IRfuBk.woff
│ │ │ └── YDAoLskQQ5MOAgvHUQCcLRTHiN2BPBirwIkMLKUspj4.woff
│ │ ├── img
│ │ │ ├── functional-map.png
│ │ │ ├── iterative-map.png
│ │ │ ├── plot-languages.png
│ │ │ └── plot.svg
│ │ ├── index.html
│ │ ├── remark-styling.css
│ │ └── remark.min.js
│ └── solutions
│ │ ├── 1_intro.js
│ │ ├── 2_applicative.js
│ │ ├── 3_functions.js
│ │ ├── 4_partially.js
│ │ ├── 5_curry_compose.js
│ │ ├── 6_immutability.js
│ │ └── 7_collections.js
└── dag2
│ ├── agenda.md
│ ├── big-case
│ ├── .gitignore
│ ├── README.md
│ ├── baconchat.png
│ ├── browser
│ │ ├── _solution.js
│ │ ├── config.js
│ │ ├── lib
│ │ │ ├── chat.js
│ │ │ ├── helpers.js
│ │ │ └── webrtc.js
│ │ └── main.js
│ ├── gulpfile.js
│ ├── lib
│ │ └── users.js
│ ├── package.json
│ ├── server.js
│ ├── static-server.js
│ ├── static
│ │ ├── assets
│ │ │ └── bacon.png
│ │ └── index.html
│ └── style
│ │ ├── chat.less
│ │ ├── colors.less
│ │ ├── main.less
│ │ ├── mixins.less
│ │ └── reset.less
│ ├── slides
│ ├── css
│ │ ├── presentation.css
│ │ ├── print
│ │ │ ├── paper.css
│ │ │ └── pdf.css
│ │ ├── reveal.css
│ │ ├── reveal.min.css
│ │ └── theme
│ │ │ ├── README.md
│ │ │ ├── beige.css
│ │ │ ├── default.css
│ │ │ ├── moon.css
│ │ │ ├── night.css
│ │ │ ├── serif.css
│ │ │ ├── simple.css
│ │ │ ├── sky.css
│ │ │ ├── solarized.css
│ │ │ ├── source
│ │ │ ├── beige.scss
│ │ │ ├── default.scss
│ │ │ ├── moon.scss
│ │ │ ├── night.scss
│ │ │ ├── serif.scss
│ │ │ ├── simple.scss
│ │ │ ├── sky.scss
│ │ │ └── solarized.scss
│ │ │ └── template
│ │ │ ├── mixins.scss
│ │ │ ├── settings.scss
│ │ │ └── theme.scss
│ ├── img
│ │ ├── avengers2.gif
│ │ ├── batman.gif
│ │ ├── data_flow.svg
│ │ └── kevin_bacon.gif
│ ├── index.html
│ ├── js
│ │ ├── reveal.js
│ │ └── reveal.min.js
│ ├── lib
│ │ ├── css
│ │ │ └── zenburn.css
│ │ ├── font
│ │ │ ├── league_gothic-webfont.eot
│ │ │ ├── league_gothic-webfont.svg
│ │ │ ├── league_gothic-webfont.ttf
│ │ │ ├── league_gothic-webfont.woff
│ │ │ └── league_gothic_license
│ │ └── js
│ │ │ ├── bacon.js
│ │ │ ├── classList.js
│ │ │ ├── head.min.js
│ │ │ ├── html5shiv.js
│ │ │ └── jquery.js
│ └── plugin
│ │ ├── highlight
│ │ └── highlight.js
│ │ ├── markdown
│ │ ├── example.html
│ │ ├── example.md
│ │ ├── markdown.js
│ │ └── marked.js
│ │ ├── math
│ │ └── math.js
│ │ ├── notes-server
│ │ ├── client.js
│ │ ├── index.js
│ │ └── notes.html
│ │ ├── notes
│ │ ├── notes.html
│ │ └── notes.js
│ │ ├── postmessage
│ │ ├── example.html
│ │ └── postmessage.js
│ │ ├── print-pdf
│ │ └── print-pdf.js
│ │ ├── remotes
│ │ └── remotes.js
│ │ ├── search
│ │ └── search.js
│ │ └── zoom-js
│ │ └── zoom.js
│ └── small-case
│ ├── _app.js
│ ├── api.js
│ ├── app.js
│ ├── index.html
│ ├── package.json
│ ├── readme.md
│ ├── recordapp.png
│ └── style.css
├── scripts
├── components
│ ├── books.js
│ ├── codemirror-editor.js
│ ├── component.js
│ ├── editor.js
│ ├── run-code.js
│ └── run-result.js
├── entry.build.js
├── entry.js
├── playground-reporter.js
└── vendor
│ ├── browser.js
│ ├── codemirror-base16-mocha-dark.css
│ ├── codemirror-kimbie-dark.css
│ └── codemirror.css
└── webpack.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | _site
6 | .sass-cache
7 |
8 |
9 | .DS_Store
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # Compiled binary addons (http://nodejs.org/api/addons.html)
26 | build/Release
27 |
28 | # Dependency directory
29 | # Commenting this out is preferred by some people, see
30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
31 | node_modules
32 |
33 | # Users Environment Variables
34 | .lock-wscript
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [Workshop](http://bekk.github.io/functional-js/workshop/)
2 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | # Site settings
2 | title: Functional JavaScript Workshop
3 | description: Workshop for teaching Functional Programming with JavaScript
4 | baseurl: "/functional-js" # the subpath of your site, e.g. /blog/
5 | url: "http://bekkopen.github.io/" # the base hostname & protocol for your site
6 |
7 | version: 3.1.0
8 |
9 | # Build settings
10 | markdown: kramdown
11 |
12 | exclude:
13 | - node_modules
14 |
15 | collections:
16 | workshop:
17 | output: true
18 | output_ext: html
19 | collection: workshop
20 | permalink: /workshop/:path/
21 |
22 | permalink: pretty
23 |
24 | kramdown:
25 | input: GFM
26 | hard_wrap: false
27 |
--------------------------------------------------------------------------------
/_includes/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/_includes/head.html:
--------------------------------------------------------------------------------
1 |
2 |
6 | Task: {{page.title}}
7 |
8 | {% if page.slides %}
9 | See related slides for this part: {{page.slides}}
10 | {% endif %}
11 |
12 |
20 |
21 | {% if page.info %}
22 | Information
23 | {{page.info | markdownify}}
24 | {% endif %}
25 |
26 |
27 |
28 | Tasks
29 |
30 | {% for item in site.workshop %}
31 | {% if item.hide != true %}
32 | {% assign pageID = page.name %}
33 | {% assign itemID = item.name %}
34 | {% assign className = pageID == itemID ? ' class="active"' : '' %}
35 | -
36 | {% if item.start == true %}
37 |
38 | {% else %}
39 |
40 | {% endif %}
41 | {% if item["section"] %}Part {{item["section"]}}: {% endif %}
42 | {{item["title"]}}
43 |
44 | {% if pageID == itemID %}[Active]{% endif %}
45 |
46 | {% endif %}
47 | {% endfor %}
48 |
49 |
50 |
58 |
59 | Help
60 |
61 | Available globals
62 |
63 | _
: Lo-dash.js
64 | log
: log-command that logs to view and console
65 |
66 |
67 | Testing
68 |
69 | describe
: new test suite (mocha)
70 | it
: new test (mocha)
71 | xit
: pending test (mocha)
72 | xdescribe
: pending scenario (mocha)
73 | before/after
: run code before or after all tests (mocha)
74 | beforeEach/afterEach
: run code before or after each tests (mocha)
75 | chai
: chai.js assertion library
76 |
77 |
78 |
79 | {% include footer.html %}
80 |
81 |
82 |
--------------------------------------------------------------------------------
/_sass/_columns.scss:
--------------------------------------------------------------------------------
1 | @import "globals/size";
2 |
3 | .column {
4 | margin-left: calc(#{$column-sidebar-width} + #{$column-sidebar-margin + 1});
5 | }
6 |
7 | .nav-left {
8 | float: left;
9 | width: $column-sidebar-width;
10 | margin-right: $column-sidebar-margin;
11 |
12 | font-size: 0.9rem;
13 | }
14 | .nav-left ul,
15 | .nav-left ol {
16 | padding: 0;
17 | padding-left: 10px;
18 | list-style-type: none;
19 | }
20 | .nav-left h3 {
21 | margin: 0;
22 | }
23 | .nav-left > :not(:first-child) {
24 | margin-top: 30px;
25 | }
26 | .nav-left h3 a {
27 | font-size: 1.2rem;
28 | font-weight: 100;
29 | color: $color-leftNav-header;
30 |
31 | text-transform: uppercase;
32 | display: block;
33 | }
34 |
35 | .nav-left {
36 | ul, ol {
37 | a {
38 | display: block;
39 | padding: 5px 0;
40 |
41 | color: $color-leftNav;
42 |
43 | transition: all 300ms;
44 |
45 | &:hover {
46 | color: lighten($color-leftNav, 40%);
47 | }
48 | }
49 | }
50 | }
51 |
52 | @media (max-width: $first-breakpoint-width) {
53 | .nav-left {
54 | display: none;
55 | }
56 | .column {
57 | margin-left: 0;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/_sass/_content.scss:
--------------------------------------------------------------------------------
1 | @import "./globals/mixins";
2 | @import "./globals/colors";
3 |
4 | .mainContent {
5 | @include width-constraint;
6 | margin-top: 30px;
7 | display: block;
8 |
9 | &.playground {
10 | @include width-constraint-large;
11 | }
12 | }
13 |
14 | .mainContent h1,
15 | .mainContent h2,
16 | .mainContent h3 {
17 | font-size: 2rem;
18 | margin-bottom: 10px;
19 | font-weight: 100;
20 | position: relative;
21 | word-wrap: break-word;
22 |
23 | color: $color-organge-normal;
24 | }
25 | .mainContent h1 {
26 | margin-top: 50px;
27 | margin-top: 0;
28 | }
29 | .mainContent h2 {
30 | font-size: 1.5rem;
31 | }
32 | .mainContent h3 {
33 | font-size: 1.4rem;
34 | }
35 |
36 | .mainContent p {
37 | line-height: 1.7rem;
38 | }
39 |
40 | .mainContent table {
41 | width: 100%;
42 | margin: 10px 0;
43 | background: $color-table-white;
44 |
45 | td, th {
46 | padding: 5px;
47 | }
48 |
49 | tr:nth-child(odd) td {
50 | background: $color-table-zebra;
51 | }
52 | }
53 |
54 |
55 | .playground h1,
56 | .playground h2,
57 | .playground h3 {
58 | margin-top: 1rem;
59 | }
60 |
61 | .mainContent p {
62 | margin-bottom: 20px;
63 | }
64 |
65 | .mainContent img {
66 | max-width: 100%;
67 | }
68 |
69 | .edit-page-link {
70 | position: absolute;
71 | top: -0.8rem;
72 | right: 0;
73 | font-size: 0.7rem;
74 | }
75 |
76 | .fromMarkdown pre code {
77 | margin-bottom: 30px;
78 | }
79 |
80 | p img {
81 | background: #fff;
82 | }
83 |
84 | .column ul,
85 | .column ol {
86 | margin: 20px 0;
87 | }
88 |
--------------------------------------------------------------------------------
/_sass/_forms.scss:
--------------------------------------------------------------------------------
1 | form { margin: 0; }
2 | // prevent ios zoom on focus
3 | input[type=text], textarea { font-size: 1rem; }
4 |
--------------------------------------------------------------------------------
/_sass/_graph.scss:
--------------------------------------------------------------------------------
1 | @import "./globals/colors";
2 |
3 | .graphNode--blue {
4 | fill: $color-topNode;
5 |
6 | -webkit-animation-name: 'pulse_animation';
7 | -webkit-animation-duration: 2500ms;
8 | -webkit-animation-iteration-count: infinite;
9 | -webkit-animation-timing-function: linear;
10 | -webkit-transform-origin:70% 70%;
11 | }
12 |
13 |
14 | .graphEdge--fade,
15 | .graphNode--fade {
16 | -webkit-animation-name: 'fade_animation';
17 | -webkit-animation-duration: 5000ms;
18 | -webkit-animation-iteration-count: infinite;
19 | -webkit-animation-timing-function: linear;
20 | }
21 |
22 | @-webkit-keyframes pulse_animation {
23 | 0% { -webkit-transform: scale(1); }
24 | 50% { -webkit-transform: scale(.95); }
25 | 100% { -webkit-transform: scale(1); }
26 | }
27 |
28 | @-webkit-keyframes fade_animation {
29 | 0% { opacity: 1; }
30 | 20% { opacity: .9; }
31 | 30% { opacity: .2; }
32 | 50% { opacity: .2; }
33 | 60% { opacity: .2; }
34 | 70% { opacity: .2; }
35 | 100% { opacity: 1; }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/_sass/_header.scss:
--------------------------------------------------------------------------------
1 | @import "./globals/colors";
2 | @import "./globals/mixins";
3 | @import "./globals/size";
4 |
5 | .mainHeader {
6 | background: linear-gradient(to bottom, $color-header-background 0%, $color-header-background--bottom 100%);
7 | }
8 |
9 | .mainHeader-illustration {
10 | @include width-constraint;
11 |
12 | display: flex;
13 | align-items: center;
14 | flex-wrap: wrap;
15 |
16 | svg {
17 | min-width: 400px;
18 | margin: auto;
19 | flex: 3 0 30%;
20 | }
21 | }
22 |
23 | .mainHeader-illustration .illustrationText {
24 | flex: 1 1 30%;
25 | color: $color-infoBoxes;
26 | text-shadow: 1px 1px 1px rgba(0,0,0,.4);
27 |
28 | .github-badges {
29 | margin-top: 15px;
30 | text-align: center;
31 | }
32 |
33 | h1 {
34 | font-size: 2.5rem;
35 | }
36 | }
37 |
38 | @media (max-width: $remove-graphics-width) {
39 | .mainHeader-illustration svg {
40 | display: none;
41 | }
42 | }
43 |
44 | .mainNavigation {
45 | z-index: 3; // .CodeMirror pre is 2
46 |
47 | height: $header-height;
48 | position: fixed;
49 | min-height: initial;
50 | top: 0;
51 | left: 0;
52 | width: 100%;
53 | background: $color-header-background--small;
54 | }
55 |
56 | .mainNavigation-inner {
57 | @include width-constraint;
58 |
59 | display: flex;
60 | align-items: center;
61 | padding: 15px 0;
62 |
63 | justify-content: space-between;
64 |
65 | ul {
66 | // display: flex;
67 | list-style-type: none;
68 | margin-top: 5px;
69 | font-size: 1.2rem;
70 |
71 | li {
72 | float: left;
73 |
74 | &:last-child a {
75 | margin-right: 0;
76 | }
77 | }
78 |
79 | li a {
80 | display: block;
81 | margin-left: 20px;
82 |
83 | color: $color-header-linkColor;
84 | transition: all 300ms;
85 |
86 | text-transform: uppercase;
87 | text-decoration: none;
88 | font-weight: 300;
89 |
90 |
91 | &:hover {
92 | text-shadow: 1px 1px 3px rgba(0,0,0, .7);
93 | }
94 | }
95 |
96 | li:first-child a {
97 | margin-left: 0;
98 | }
99 | }
100 | }
101 |
102 | @media (max-width: $minify-header-width) {
103 | .mainNavigation-inner ul {
104 | li:nth-last-child(1),
105 | li:nth-last-child(2) {
106 | display: none;
107 | }
108 | }
109 | }
110 |
111 | .mainNavigation-inner h1 a {
112 | display: block;
113 |
114 | img {
115 | height: 33px;
116 | }
117 | }
118 |
119 | .chevronDown {
120 | height: 25px;
121 | display: block;
122 | text-align: center;
123 | transition: all 400ms;
124 |
125 | i {
126 | transition: all 400ms;
127 | @include chevron($color-blue-light, 5px, 50px);
128 | top: 10px;
129 | opacity: 0.5;
130 | }
131 |
132 | &:hover i {
133 | opacity: 1;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/_sass/_next-nav.scss:
--------------------------------------------------------------------------------
1 | @import "globals/mixins";
2 |
3 | .docs-prevnext {
4 | margin: 20px 0;
5 | @include clearfix;
6 | }
7 | .docs-prev {
8 | float: left;
9 | }
10 | .docs-next {
11 | float: right;
12 | }
--------------------------------------------------------------------------------
/_sass/_syntax-highlighting.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Syntax highlighting styles
3 | */
4 | .highlight {
5 | background: #fff;
6 | @extend %vertical-rhythm;
7 |
8 | .c { color: #998; font-style: italic } // Comment
9 | .err { color: #a61717; background-color: #e3d2d2 } // Error
10 | .k { font-weight: bold } // Keyword
11 | .o { font-weight: bold } // Operator
12 | .cm { color: #998; font-style: italic } // Comment.Multiline
13 | .cp { color: #999; font-weight: bold } // Comment.Preproc
14 | .c1 { color: #998; font-style: italic } // Comment.Single
15 | .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special
16 | .gd { color: #000; background-color: #fdd } // Generic.Deleted
17 | .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific
18 | .ge { font-style: italic } // Generic.Emph
19 | .gr { color: #a00 } // Generic.Error
20 | .gh { color: #999 } // Generic.Heading
21 | .gi { color: #000; background-color: #dfd } // Generic.Inserted
22 | .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific
23 | .go { color: #888 } // Generic.Output
24 | .gp { color: #555 } // Generic.Prompt
25 | .gs { font-weight: bold } // Generic.Strong
26 | .gu { color: #aaa } // Generic.Subheading
27 | .gt { color: #a00 } // Generic.Traceback
28 | .kc { font-weight: bold } // Keyword.Constant
29 | .kd { font-weight: bold } // Keyword.Declaration
30 | .kp { font-weight: bold } // Keyword.Pseudo
31 | .kr { font-weight: bold } // Keyword.Reserved
32 | .kt { color: #458; font-weight: bold } // Keyword.Type
33 | .m { color: #099 } // Literal.Number
34 | .s { color: #d14 } // Literal.String
35 | .na { color: #008080 } // Name.Attribute
36 | .nb { color: #0086B3 } // Name.Builtin
37 | .nc { color: #458; font-weight: bold } // Name.Class
38 | .no { color: #008080 } // Name.Constant
39 | .ni { color: #800080 } // Name.Entity
40 | .ne { color: #900; font-weight: bold } // Name.Exception
41 | .nf { color: #900; font-weight: bold } // Name.Function
42 | .nn { color: #555 } // Name.Namespace
43 | .nt { color: #000080 } // Name.Tag
44 | .nv { color: #008080 } // Name.Variable
45 | .ow { font-weight: bold } // Operator.Word
46 | .w { color: #bbb } // Text.Whitespace
47 | .mf { color: #099 } // Literal.Number.Float
48 | .mh { color: #099 } // Literal.Number.Hex
49 | .mi { color: #099 } // Literal.Number.Integer
50 | .mo { color: #099 } // Literal.Number.Oct
51 | .sb { color: #d14 } // Literal.String.Backtick
52 | .sc { color: #d14 } // Literal.String.Char
53 | .sd { color: #d14 } // Literal.String.Doc
54 | .s2 { color: #d14 } // Literal.String.Double
55 | .se { color: #d14 } // Literal.String.Escape
56 | .sh { color: #d14 } // Literal.String.Heredoc
57 | .si { color: #d14 } // Literal.String.Interpol
58 | .sx { color: #d14 } // Literal.String.Other
59 | .sr { color: #009926 } // Literal.String.Regex
60 | .s1 { color: #d14 } // Literal.String.Single
61 | .ss { color: #990073 } // Literal.String.Symbol
62 | .bp { color: #999 } // Name.Builtin.Pseudo
63 | .vc { color: #008080 } // Name.Variable.Class
64 | .vg { color: #008080 } // Name.Variable.Global
65 | .vi { color: #008080 } // Name.Variable.Instance
66 | .il { color: #099 } // Literal.Number.Integer.Long
67 | }
68 |
--------------------------------------------------------------------------------
/_sass/_window.scss:
--------------------------------------------------------------------------------
1 | .CodeMirror {
2 | z-index: 0;
3 | padding-bottom: 0.5rem;
4 | border-top-left-radius: 3px;
5 | border-top-right-radius: 3px;
6 | }
7 | .CodeMirror,
8 | .inner--result {
9 | font-size: 1rem;
10 | }
11 | .window .CodeMirror-lines {
12 | padding: 0;
13 | padding-left: 0.4rem;
14 | }
15 |
16 | .playground {
17 |
18 | .window .CodeMirror-lines {
19 | padding: 0;
20 | }
21 |
22 | .CodeMirror {
23 | border-top-left-radius: 3px;
24 | border-top-right-radius: 0;
25 | border-bottom-left-radius: 3px;
26 | }
27 |
28 | .CodeMirror-line-numbers {
29 | font-size: 0.2rem;
30 | }
31 |
32 | .CodeMirror,
33 | .inner--result {
34 | font-size: 1.15rem;
35 | }
36 |
37 | .window {
38 | display: flex;
39 | flex-flow: row wrap;
40 | }
41 |
42 | .control {
43 | flex: 1 100%;
44 | }
45 | .inner--code {
46 | flex: 1 65%;
47 | }
48 | .inner--result {
49 | flex: 1 35%;
50 | }
51 | }
52 |
53 | $color-error: #f25156;
54 | $color-success: #4ca73c;
55 | $color-pending: #f7ac3c;
56 |
57 | .test-summary {
58 | font-size: 0.9rem;
59 | text-align: center;
60 | }
61 |
62 | .editor-pending {
63 | color: $color-pending;
64 | }
65 | .editor-success {
66 | color: $color-success;
67 | }
68 |
69 | .editor > .inner > textarea {
70 | display: none;
71 | }
72 | .editor-error {
73 | color: $color-error;
74 | pre {
75 | font-size: 12px;
76 | white-space: pre-wrap; /* CSS 3 */
77 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
78 | white-space: -pre-wrap; /* Opera 4-6 */
79 | white-space: -o-pre-wrap; /* Opera 7 */
80 | word-wrap: break-word; /* Internet Explorer 5.5+ */
81 | }
82 | }
83 |
84 | .window {
85 | background: #fafafa;
86 | width: 100%;
87 | border-radius: 3px;
88 | margin: 20px auto;
89 | box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.2);
90 | }
91 |
92 | .window .inner {
93 | padding: 0.5rem 0.75rem 0.1rem 0.75rem;
94 | }
95 | .window .inner--result {
96 | border-bottom-left-radius: 3px;
97 | border-bottom-right-radius: 3px;
98 | }
99 | .window .inner--code--withoutResult {
100 | border-bottom-left-radius: 3px;
101 | border-bottom-right-radius: 3px;
102 | }
103 |
104 | .window.window--code {
105 | background: none;
106 | }
107 | .window .inner--code {
108 | padding: 0;
109 | }
110 |
111 | .inner--result {
112 | color: #474747;
113 | font-weight: 300;
114 | }
115 |
116 | pre code {
117 | padding: 0 10px;
118 | background: #141414;
119 | display: block;
120 | padding: 10px;
121 | border-radius: 3px;
122 | overflow: auto;
123 | }
124 |
125 | p code, ul code, li code {
126 | background: darken($color-table-zebra, 1%);
127 | border: 1px solid darken($color-table-zebra, 10%);
128 | line-height: 1.4rem;
129 | padding: 0 5px;
130 | display: inline-block;
131 | border-radius: 2px;
132 | }
133 |
--------------------------------------------------------------------------------
/_sass/font/_animation.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Animation example, for spinners
3 | */
4 | .animate-spin {
5 | -moz-animation: spin 2s infinite linear;
6 | -o-animation: spin 2s infinite linear;
7 | -webkit-animation: spin 2s infinite linear;
8 | animation: spin 2s infinite linear;
9 | display: inline-block;
10 | }
11 | @-moz-keyframes spin {
12 | 0% {
13 | -moz-transform: rotate(0deg);
14 | -o-transform: rotate(0deg);
15 | -webkit-transform: rotate(0deg);
16 | transform: rotate(0deg);
17 | }
18 |
19 | 100% {
20 | -moz-transform: rotate(359deg);
21 | -o-transform: rotate(359deg);
22 | -webkit-transform: rotate(359deg);
23 | transform: rotate(359deg);
24 | }
25 | }
26 | @-webkit-keyframes spin {
27 | 0% {
28 | -moz-transform: rotate(0deg);
29 | -o-transform: rotate(0deg);
30 | -webkit-transform: rotate(0deg);
31 | transform: rotate(0deg);
32 | }
33 |
34 | 100% {
35 | -moz-transform: rotate(359deg);
36 | -o-transform: rotate(359deg);
37 | -webkit-transform: rotate(359deg);
38 | transform: rotate(359deg);
39 | }
40 | }
41 | @-o-keyframes spin {
42 | 0% {
43 | -moz-transform: rotate(0deg);
44 | -o-transform: rotate(0deg);
45 | -webkit-transform: rotate(0deg);
46 | transform: rotate(0deg);
47 | }
48 |
49 | 100% {
50 | -moz-transform: rotate(359deg);
51 | -o-transform: rotate(359deg);
52 | -webkit-transform: rotate(359deg);
53 | transform: rotate(359deg);
54 | }
55 | }
56 | @-ms-keyframes spin {
57 | 0% {
58 | -moz-transform: rotate(0deg);
59 | -o-transform: rotate(0deg);
60 | -webkit-transform: rotate(0deg);
61 | transform: rotate(0deg);
62 | }
63 |
64 | 100% {
65 | -moz-transform: rotate(359deg);
66 | -o-transform: rotate(359deg);
67 | -webkit-transform: rotate(359deg);
68 | transform: rotate(359deg);
69 | }
70 | }
71 | @keyframes spin {
72 | 0% {
73 | -moz-transform: rotate(0deg);
74 | -o-transform: rotate(0deg);
75 | -webkit-transform: rotate(0deg);
76 | transform: rotate(0deg);
77 | }
78 |
79 | 100% {
80 | -moz-transform: rotate(359deg);
81 | -o-transform: rotate(359deg);
82 | -webkit-transform: rotate(359deg);
83 | transform: rotate(359deg);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/_sass/font/_fontello.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'fontello';
3 | src: url('../assets/font/fontello.eot?22358951');
4 | src: url('../assets/font/fontello.eot?22358951#iefix') format('embedded-opentype'),
5 | url('../assets/font/fontello.woff?22358951') format('woff'),
6 | url('../assets/font/fontello.ttf?22358951') format('truetype'),
7 | url('../assets/font/fontello.svg?22358951#fontello') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
12 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
13 | /*
14 | @media screen and (-webkit-min-device-pixel-ratio:0) {
15 | @font-face {
16 | font-family: 'fontello';
17 | src: url('../font/fontello.svg?22358951#fontello') format('svg');
18 | }
19 | }
20 | */
21 |
22 | [class^="icon-"]:before, [class*=" icon-"]:before {
23 | font-family: "fontello";
24 | font-style: normal;
25 | font-weight: normal;
26 | speak: none;
27 |
28 | display: inline-block;
29 | text-decoration: inherit;
30 | width: 1em;
31 | margin-right: .2em;
32 | text-align: center;
33 | /* opacity: .8; */
34 |
35 | /* For safety - reset parent styles, that can break glyph codes*/
36 | font-variant: normal;
37 | text-transform: none;
38 |
39 | /* fix buttons height, for twitter bootstrap */
40 | line-height: 1em;
41 |
42 | /* Animation center compensation - margins should be symmetric */
43 | /* remove if not needed */
44 | margin-left: .2em;
45 |
46 | /* you can be more comfortable with increased icons size */
47 | /* font-size: 120%; */
48 |
49 | /* Uncomment for 3D effect */
50 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
51 | }
52 |
53 | .icon-twitter:before { content: '\e800'; } /* '' */
54 | .icon-fork:before { content: '\e801'; } /* '' */
55 | .icon-spin2:before { content: '\e802'; } /* '' */
56 | .icon-github-circled:before { content: '\e803'; } /* '' */
--------------------------------------------------------------------------------
/_sass/globals/_colors.scss:
--------------------------------------------------------------------------------
1 | // Color palette
2 | $color-blue-darkest: #2C3E50;
3 | $color-blue-dark: #2980B9;
4 | $color-blue-normal: #3498DB;
5 | $color-blue-light: #0EC2DB;
6 | $color-organge-normal: #E74C3C;
7 |
8 | $color-omniscient-blue: #0098C6;
9 |
10 | $color-white-bg: #fafafa;
11 | $color-darkGray-background: $color-white-bg;
12 | $color-white-fontColor: #2d2d2d;
13 |
14 | $color-header-border: $color-blue-light;
15 | $color-header-background: $color-omniscient-blue;
16 | $color-header-background--bottom: lighten($color-blue-darkest, 10%);
17 | $color-header-background--small: $color-omniscient-blue;
18 |
19 | $color-header-linkColor: $color-white-bg;
20 |
21 |
22 | // Graph colors:
23 |
24 | $color-topNode: $color-blue-darkest;
25 |
26 | $color-infoBoxes: $color-white-bg;
27 |
28 | $color-leftNav: $color-blue-darkest;
29 | $color-leftNav-header: $color-blue-darkest;
30 |
31 | $color-table-white: #fff;
32 | $color-table-zebra: lighten($color-organge-normal, 40%);
33 |
34 | // Links
35 |
36 | $color-links: $color-blue-normal;
37 | $color-links--hover: darken($color-links, 15%);
38 | $color-links--active: darken($color-links, 25%);
39 |
--------------------------------------------------------------------------------
/_sass/globals/_mixins.scss:
--------------------------------------------------------------------------------
1 |
2 | @mixin width-constraint () {
3 | width: 93%;
4 | max-width: 1100px;
5 | margin: 0 auto;
6 | }
7 |
8 | @mixin width-constraint-large() {
9 | width: auto;
10 | max-width: none;
11 | margin: 3rem $header-height;
12 | }
13 |
14 | @mixin chevron ($color, $height: 30px, $width: 200px) {
15 | position: relative;
16 | display: inline-block;
17 | text-align: center;
18 | margin-bottom: 6px;
19 | height: $height;
20 | width: $width;
21 |
22 | &:before {
23 | content: '';
24 | position: absolute;
25 | top: 0;
26 | left: 0;
27 | height: 100%;
28 | width: 51%;
29 | background: $color;
30 | -webkit-transform: skew(0deg, 6deg);
31 | -moz-transform: skew(0deg, 6deg);
32 | -ms-transform: skew(0deg, 6deg);
33 | -o-transform: skew(0deg, 6deg);
34 | transform: skew(0deg, 6deg);
35 | }
36 |
37 | &:after {
38 | content: '';
39 | position: absolute;
40 | top: 0;
41 | right: 0;
42 | height: 100%;
43 | width: 50%;
44 | background: $color;
45 | -webkit-transform: skew(0deg, -6deg);
46 | -moz-transform: skew(0deg, -6deg);
47 | -ms-transform: skew(0deg, -6deg);
48 | -o-transform: skew(0deg, -6deg);
49 | transform: skew(0deg, -6deg);
50 | }
51 | }
52 |
53 | @mixin triangle ($color, $width: 100px, $height: 30px) {
54 | width: 0;
55 | height: 0;
56 | border-left: $width / 2 solid transparent;
57 | border-right: $width / 2 solid transparent;
58 | border-top: $height solid $color;
59 | }
60 |
61 | @mixin clearfix {
62 | &:after {
63 | content: "";
64 | display: table;
65 | clear: both;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/_sass/globals/_reset.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Global Reset of all HTML Elements
3 | *
4 | * Resetting all of our HTML Elements ensures a smoother
5 | * visual transition between browsers. If you don't believe me,
6 | * try temporarily commenting out this block of code, then go
7 | * and look at Mozilla versus Safari, both good browsers with
8 | * a good implementation of CSS. The thing is, all browser CSS
9 | * defaults are different and at the end of the day if visual
10 | * consistency is what we're shooting for, then we need to
11 | * make sure we're resetting all spacing elements.
12 | *
13 | */
14 | html, body {
15 | border: 0;
16 | font-family: "Helvetica-Neue", "Helvetica", Arial, sans-serif;
17 | line-height: 1.5;
18 | margin: 0;
19 | padding: 0;
20 | }
21 |
22 | div, span, object, iframe, img, table, caption, thead, tbody,
23 | tfoot, tr, tr, td, article, aside, canvas, details, figure, hgroup, menu,
24 | nav, footer, header, section, summary, mark, audio, video {
25 | border: 0;
26 | margin: 0;
27 | padding: 0;
28 | }
29 |
30 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, address, cit, code,
31 | del, dfn, em, ins, q, samp, small, strong, sub, sup, b, i, hr, dl, dt, dd,
32 | ol, ul, li, fieldset, legend, label {
33 | border: 0;
34 | font-size: 100%;
35 | vertical-align: baseline;
36 | margin: 0;
37 | padding: 0;
38 | }
39 |
40 | article, aside, canvas, figure, figure img, figcaption, hgroup,
41 | footer, header, nav, section, audio, video {
42 | display: block;
43 | }
44 |
45 | table {
46 | border-collapse: separate;
47 | border-spacing: 0;
48 | caption, th, td {
49 | text-align: left;
50 | vertical-align: middle;
51 | }
52 | }
53 |
54 | a img {
55 | border: 0;
56 | }
57 |
58 | :focus {
59 | outline: 0;
60 | }
61 |
--------------------------------------------------------------------------------
/_sass/globals/_size.scss:
--------------------------------------------------------------------------------
1 | $column-sidebar-width: 31%;
2 | $column-sidebar-margin: 1rem;
3 |
4 | $first-breakpoint-width: 650px;
5 | $remove-graphics-width: 870px;
6 | $minify-header-width: 630px;
7 | $min-width: 350px;
8 |
9 | $header-height: 0px;
10 |
--------------------------------------------------------------------------------
/_sass/main.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | @import "globals/reset";
4 | @import "globals/colors";
5 | @import "globals/size";
6 |
7 | @import url(http://fonts.googleapis.com/css?family=Khula:400,700,600,300);
8 |
9 | body {
10 | font-family: 'Khula', sans-serif;
11 | background: $color-darkGray-background;
12 | color: $color-white-fontColor;
13 | padding: 0 5%;
14 | }
15 |
16 | * {
17 | box-sizing: border-box;
18 | }
19 |
20 | @media (max-width: $first-breakpoint-width) {
21 | .link--github {
22 | display: none;
23 | }
24 | }
25 |
26 | .anchor-link {
27 | display: block;
28 | position: relative;
29 | top: -$header-height;
30 | visibility: hidden;
31 | }
32 |
33 | a:link, a:visited {
34 | color: $color-links;
35 | text-decoration: none;
36 | }
37 |
38 | a:hover {
39 | color: $color-links--hover;
40 | }
41 |
42 | a:active {
43 | color: $color-links--active;
44 | }
45 |
46 |
47 | ul, ol {
48 | list-style-position: inside;
49 | padding-left: 20px;
50 | }
51 |
52 | .info {
53 | max-width: 65%;
54 | }
55 |
56 | @import "header";
57 | @import "graph";
58 | @import "window";
59 | @import "content";
60 | @import "columns";
61 | @import "next-nav";
62 | @import "forms";
63 |
64 | // Fonts
65 | @import "font/animation";
66 | @import "font/fontello";
67 |
--------------------------------------------------------------------------------
/_workshop/00-introduction.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Introduction to the workshop
5 | permalink: /workshop/
6 | section: 0
7 | start: true
8 | name: 00-introduction
9 | next: 01-1-context
10 | slides:
11 | info: |
12 | Welcome to a workshop for learning functional programming in JavaScript.
13 |
14 | This workshop runs in your browser. Assignments are written as tests that
15 | you need to make pass. When you have all passing test you can move on to the
16 | next task. There will be additional information in the comments.
17 |
18 | If you at any time need to freshen up your JS-fu, start any Google
19 | search with "mdn" and you will always end up at the amazing Mozilla
20 | Developer Network, which has the best JS explanations around.
21 | ---
22 |
23 | // You can create functions like this:
24 | function hello(arg) {
25 | return 'result of function called with ' + arg;
26 | }
27 |
28 | // You can call the function and save the result:
29 | var result = hello('some argument');
30 |
31 | // A handy way of printing has been added to the environment
32 | log(result);
33 | // And will display in the sidebar to the right of the editor
34 |
35 | // Assignments will be written as tests that you need to make pass
36 | // You will either need to modify the tests themselves or the code that the
37 | // tests execute.
38 | describe('mocha inside the workshop', function () {
39 |
40 | it('shows tests', function () {
41 |
42 | expect(2).to.equal(2);
43 |
44 | });
45 |
46 | it('shows failing tests', function () {
47 |
48 | expect(hello('some argument')).to.equal(
49 | 'Hello, some argument',
50 | 'hello should greet argument'
51 | );
52 |
53 | });
54 |
55 | });
56 |
--------------------------------------------------------------------------------
/_workshop/01-1-context.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: A functions context
5 | section: 1
6 | prev: ''
7 | name: 01-1-context
8 | next: 01-2-arguments
9 | slides:
10 | info: |
11 | It is time to get started. This workshop will start with some of the basics of
12 | JavaScript, with scope and context, to ensure that you fully understand how
13 | functions work in JavaScript.
14 |
15 | We start with how context works when using functions in JavaScript.
16 | ---
17 | //A couple of functions that we will work with during this exercise
18 |
19 | function hello() {
20 | return this + ' says hello';
21 | }
22 |
23 | describe('a functions context', function() {
24 | // In JavaScript we can dynamically choose the value of `this`
25 | // using several techniques. Today we will focus on some of these,
26 | // starting with `call` and `apply`.
27 |
28 | it('is undefined by default', function() {
29 | //call function to observe default behavior
30 | var result = null;
31 | expect(result).to.equal('undefined says hello');
32 | });
33 |
34 | // `call` calls a function with a given `this` value as the first
35 | // parameter, then arguments provided individually:
36 | //
37 | // func.call(this, arg1, arg2, ...)
38 | it('can change when using call', function() {
39 | //change context of `hello` by using `call`
40 | var result = hello();
41 | expect(result).to.equal('kim says hello');
42 | });
43 |
44 | // `apply` calls a function with a given `this` value as the first
45 | // parameter, then arguments provided as an array
46 | //
47 | // func.apply(this, [arg1, arg2, ...])
48 | it('can change when using apply', function() {
49 | //change context of `hello` by using `apply`
50 | var result = hello();
51 | expect(result).to.equal('kim says hello');
52 | });
53 |
54 | });
55 | // If you want to learn more about function invocation in
56 | // JavaScript, I recommend reading:
57 | // http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/
58 |
--------------------------------------------------------------------------------
/_workshop/01-2-arguments.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: A functions arguments
5 | section: 1
6 | prev: 01-1-context
7 | name: 01-2-arguments
8 | next: 01-3-prototype
9 | slides:
10 | info: |
11 | Arguments are what makes functions interesting. Being able to pass data and
12 | information to a function in order to change its result is important.
13 | ---
14 | function helloTo(thing) {
15 | return this + ' says hello ' + thing;
16 | }
17 |
18 | function argCount() {
19 | return arguments.length;
20 | }
21 | describe('a functions arguments', function() {
22 | // Inside a function you will always have access to `arguments`,
23 | // which is an array-like object that contains all the arguments
24 | // passed to the function.
25 |
26 | it('can be counted', function() {
27 | var result = argCount(1, 2, 3);
28 | expect(result).to.equal(3);
29 | });
30 |
31 | // JavaScript is not strict about the number of
32 | // arguments, so you can for example include more parameters than
33 | // the function expects:
34 |
35 | it('are not strict in number', function() {
36 | var result = helloTo.call('kim', 'world', 'or', 'maybe', 'bekk');
37 | expect(result).to.equal('kim says hello world');
38 | });
39 |
40 | // But JavaScript is strict with the count of arguments regardless of
41 | // wether the argument was used or not.
42 |
43 | it('has a strict count', function() {
44 | var result = argCount(null, 2, 3);
45 | expect(result).to.equal(2);
46 | });
47 |
48 | });
49 |
50 | function multiplyFirst() {
51 | // PROBLEM:
52 | // Multiply `this` and the first argument,
53 | // without specifying any parameters
54 | }
55 |
56 | describe('multiplyFirst', function() {
57 |
58 | it('1*1=1', function() {
59 | var result = multiplyFirst.call(1,1);
60 | expect(result).to.equal(1);
61 | });
62 |
63 | it('2*5=10', function() {
64 | var result = multiplyFirst.call(2,5);
65 | expect(result).to.equal(10);
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/_workshop/01-3-prototype.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Functions and prototypes
5 | section: 1
6 | prev: 01-2-arguments
7 | name: 01-3-prototype
8 | next: 02-1-applicative
9 | slides:
10 | info: |
11 | Prototypes are another core concept in JavaScript. Functions and prototypes
12 | interact in many interesting ways.
13 |
14 | There will not be much about prototypes in this workshop, but
15 | there is one thing it is important to understand, especially when
16 | working with `arguments`: In JavaScript you can borrow existing
17 | functions that live on a prototype.
18 | ---
19 | var User = function(age) {
20 | this.age = age;
21 | };
22 |
23 | User.prototype.getAge = function() {
24 | return this.age;
25 | };
26 |
27 | describe('prototype-functions', function() {
28 |
29 | it('are usually bound to the context of an object', function() {
30 | var user = new User(25);
31 | var age = user.getAge();
32 |
33 | expect(age).to.equal(25);
34 | });
35 |
36 | // However, in JavaScript we can actually invoke `getAge` without
37 | // creating an instance of `User`.
38 |
39 | // We can say that we are "borrowing" the getAge function
40 | // from the User prototype.
41 | it('can be borrowed and have their context bound at will', function() {
42 | var age = User.prototype.getAge.call({ age: 35 });
43 | expect(age).to.equal(35);
44 | });
45 |
46 | });
47 |
48 | // PROBLEM:
49 | // Create a function that "borrow" `Array.prototype.slice`
50 | // and returns all the arguments given, except the first.
51 |
52 | // NOTE: This has changed in ES2015 (aka ES6), but it is
53 | // still a useful exercise.
54 |
55 | function rest() {
56 | }
57 |
58 | describe('rest', function() {
59 |
60 | it('should return all arguments except the first one', function() {
61 | var result = rest(1);
62 | expect(result).to.deep.equal([]);
63 | });
64 |
65 | it('should return all arguments except the first one', function() {
66 | var result = rest(1, 2, 3);
67 | expect(result).to.deep.equal([2, 3]);
68 | });
69 |
70 | });
71 |
--------------------------------------------------------------------------------
/_workshop/02-1-applicative.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Applicative programming
5 | section: 2
6 | prev: 01-3-prototype
7 | name: 02-1-applicative
8 | next: 02-2-map
9 | slides:
10 | info: |
11 | Instead of discussing the theory on lexical scoping and closures,
12 | we will just go straight to more code. If you want to read the theory,
13 | we recommend checking out this link after the workshop:
14 | [Eloquent JavaScript chapter 3](http://eloquentjavascript.net/03_functions.html)
15 |
16 | First up is applicative programming. In general, applicative
17 | programming is the pattern of defining a function that takes a
18 | function and then invokes that function for each element in a
19 | collection. We will look at three central functions: map, filter and
20 | reduce. These are central building blocks of what we are learning
21 | today.
22 |
23 | Applicative programming works best with pure functions, i.e.
24 | functions that have no side effects: it references no other mutable
25 | state, does not set any values other than the return value,
26 | and relies only on the parameters given as input.
27 | ---
28 | function square(n) {
29 | return n*n;
30 | }
31 |
32 | describe('applicative programming', function() {
33 |
34 | it('is about applying functions to elements in collections', function() {
35 | var result = [1,2,3].map(function(n) {
36 | return n+1;
37 | });
38 |
39 | expect(result).to.deep.equal([2,3,4]);
40 | });
41 |
42 | it('is about reusing already defined functions by passing them as parameters', function() {
43 | var result = [1,2,3].map(square);
44 |
45 | expect(result).to.deep.equal([1,4,9]);
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/_workshop/02-2-map.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Make new collections with map
5 | section: 2
6 | prev: 02-1-applicative
7 | name: 02-2-map
8 | next: 02-3-filter
9 | slides:
10 | info: |
11 | A map function accepts a higher-order function and a collection,
12 | then applies the passed function to each element and returns a
13 | new collection.
14 | ---
15 |
16 | describe('map', function() {
17 | // To understand what we're talking about, this is an example of
18 | // using a for loop to go through a collection, performing a
19 | // calculation on each item:
20 |
21 | function squareFor(arr) {
22 | var newArr = [];
23 | for (var i = 0; i < arr.length; i++) {
24 | newArr.push(arr[i] * arr[i]);
25 | }
26 | return newArr;
27 | }
28 |
29 | it('can be implemented as a for-loop', function() {
30 | var result = squareFor([1,2,3]);
31 | expect(result).to.deep.equal([1,4,9]);
32 | });
33 |
34 | // For-loops involve alot of manual steps, which makes it error prone.
35 | // Thankfully we now have a `map` functions on `Array.prototype`.
36 |
37 | // For each iteration of map it invokes the function with three
38 | // arguments: the current value, the current index, the entire
39 | // collection. So we could have written this instead:
40 |
41 | function square(arr) {
42 | return arr.map(function(value, index, collection) {
43 | return value * value;
44 | });
45 | }
46 |
47 | it('exists as a method on arrays in JavaScript', function() {
48 | var result = square([1,2,3]);
49 | expect(result).to.deep.equal([1,4,9]);
50 | });
51 |
52 | // An important characteristic of these kinds of higher order functions is
53 | // that they do not modify the original collection.
54 | it('does not modify the original collection', function() {
55 | var numbers = [1,2,3];
56 | var result = square(numbers);
57 |
58 | expect(numbers).to.not.equal(result);
59 | });
60 |
61 | });
62 |
63 | // PROBLEM: Implement the following function using map, i.e. a
64 | // function that adds the current index to the current value:
65 | //
66 | // for (var i = 0; i < arr.length; i++) {
67 | // arr[i] = arr[i] + i
68 | // }
69 |
70 | function addIndex(arr) {
71 | }
72 |
73 | describe('addIndex', function() {
74 | it('should add the index to the current element', function() {
75 | var result = addIndex([1,2,3]);
76 | expect(result).to.deep.equal([1,3,5]);
77 | });
78 |
79 | it('should not modify the original collection', function() {
80 | var numbers = [1,2,3];
81 | var result = addIndex(numbers);
82 |
83 | expect(result).to.deep.equal([1,3,5]);
84 | expect(numbers).to.not.equal(result);
85 | });
86 | });
87 |
--------------------------------------------------------------------------------
/_workshop/02-3-filter.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Make new collections with filter
5 | section: 2
6 | prev: 02-2-map
7 | name: 02-3-filter
8 | next: 02-4-reduce
9 | slides:
10 | info: |
11 | A filter function accepts a higher-order function and a collection,
12 | then applies the passed function to each element and returns a
13 | new collection which includes a subset of the original collection.
14 |
15 | An element is included in the new collection if the filter-function returns
16 | true and is excluded if the filter-function returns false.
17 | ---
18 | describe('filter', function() {
19 | // A filter-function includes the value for all function invocations that
20 | // return `true`. Here's an example, again using `for`:
21 |
22 | function removeOddIndicesFor(arr) {
23 | var newArr = [];
24 | for (var i = 0; i < arr.length; i++) {
25 | if (i % 2 == 0) {
26 | newArr.push(arr[i]);
27 | }
28 | }
29 | return newArr;
30 | }
31 |
32 | it('can be implemented as a for-loop', function() {
33 | var result = removeOddIndicesFor([1,2,3,4,5]);
34 | expect(result).to.deep.equal([1,3,5]);
35 | });
36 |
37 | // PROBLEM: Implement this using a `Array.prototype.filter`
38 | // instead:
39 |
40 | function removeOddIndices(arr) {
41 | }
42 |
43 | it('exists as a method on arrays in JavaScript', function() {
44 | var result = removeOddIndices([1,2,3,4,5]);
45 | expect(result).to.deep.equal([1,3,5]);
46 | });
47 |
48 | });
49 |
50 | // To be entirely sure that we properly understand how these
51 | // functions work, let's implement our own `map` and `filter`
52 | // functions. The implementation of these still have to concern
53 | // themselves with loops, counters and state.
54 |
55 | // (If you struggle, be sure to take a look at the examples we
56 | // saw earlier.)
57 |
58 | // PROBLEM: Implement `map`
59 |
60 | var map = function(items, fn) {
61 | }
62 |
63 | describe('implemented map', function() {
64 |
65 | it('mapfilter1', function() {
66 | var result = map([1,2,3,4], function(value, index) {
67 | return value * index;
68 | });
69 |
70 | expect(result).to.deep.equal([0, 2, 6, 12]);
71 | });
72 |
73 | });
74 |
75 | // PROBLEM: Implement `filter`
76 |
77 | var filter = function(items, fn) {
78 | }
79 |
80 | describe('implemented filter', function() {
81 |
82 | it('mapfilter2', function() {
83 | var result = ilter([1,2,3,4], function(value, index) {
84 | return value * index > 4;
85 | });
86 |
87 | expect(result).to.deep.equal([3,4]);
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/_workshop/02-4-reduce.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Make new collections with reduce
5 | section: 2
6 | prev: 02-3-filter
7 | name: 02-4-reduce
8 | next: 02-5-lodash
9 | slides:
10 | info: |
11 | The last applicative functions we will look at for now is reduce.
12 | Reduce applies a function against an accumulator and each value
13 | of the array to reduce it to a single value.
14 |
15 | Reduce is different from map and filter in that it returns a value rather than a
16 | collection. However, the value could also be a collection.
17 | ---
18 |
19 | describe('reduce', function() {
20 | // As always, let's start with an example of calculating the sum of
21 | // all items in an array using a `for` loop:
22 |
23 | function sumFor(arr) {
24 | var sum = 0;
25 | for (var i = 0; i < arr.length; i++) {
26 | sum += arr[i];
27 | }
28 | return sum;
29 | }
30 |
31 | it('can be implemented with a for-loop', function() {
32 | var result = sumFor([1,2,3]);
33 | expect(result).to.equal(6);
34 | });
35 |
36 | // Using reduce it can look like this:
37 |
38 | function sum(arr) {
39 | return arr.reduce(function(acc, value){
40 |
41 | // What is returned in this function is used as `acc` for
42 | // the next iteration
43 | return acc + value;
44 |
45 | }, 0);
46 | // 0 is the starting value for `acc`
47 |
48 | // What we return on the last iteration is the result of
49 | // the reduce.
50 | }
51 |
52 | // This illustrates how reduce works:
53 |
54 | // Operation Accumulator List
55 | // + 0 1, 2, 3, 4
56 | // 1 2, 3, 4
57 | // 3 3, 4
58 | // 6 4
59 | // 10
60 |
61 | it('exists as a method on arrays in JavaScript', function() {
62 | var result = sum([1,2,3,4]);
63 | expect(result).to.equal(10);
64 | });
65 |
66 | it('hands the values in the collection to the reducing-function', function() {
67 | var result = sum([0,0,-1]);
68 | expect(result).to.equal(-1);
69 | });
70 |
71 | });
72 |
73 | // PROBLEM: Implement multiplication using reduce
74 |
75 | function multiply(arr) {
76 | }
77 |
78 | describe('multiply', function() {
79 |
80 | it('does multiply all the numbers in the collection', function() {
81 | var result = multiply([1,2,3,4]);
82 | expect(result).to.equal(24);
83 | });
84 |
85 | it('does work as expected when 0 appears in the collection', function() {
86 | var result = multiply([0,1,2,3]);
87 | expect(result).to.equal(0);
88 | });
89 | });
90 |
91 | // We don't always need to specify the initial value, i.e. `0` in `sum`
92 | // above. If we don't specify an initial value, the first item is used as
93 | // the initial value, and the first round with `reduce` is therefore with
94 | // the first and second item.
95 |
96 | // PROBLEM: Implement join using reduce without specifying an initial value
97 |
98 | function join(arr, chr) {
99 | }
100 |
101 | describe('join', function() {
102 |
103 | it('works correctly with only one element', function() {
104 | var result = join(["a"], ":");
105 | expect(result).to.equal("a");
106 | });
107 |
108 | it('works correctly with multiple elements', function() {
109 | var result = join(["a","b","c"], ":");
110 | expect(result).to.equal("a:b:c");
111 | });
112 |
113 | });
114 |
115 | // PROBLEM: Implement find using reduce
116 | // (think about what we want to get back from such a function)
117 |
118 | function find(arr, el) {
119 | }
120 |
121 | describe('find', function() {
122 |
123 | it('should return true if element exists in the array', function() {
124 | var result = find([1,2,3], 1);
125 | expect(result).to.be.ok;
126 | });
127 |
128 | it('should return false if element does not exist in the array', function() {
129 | var result = find([1,2,3], 4);
130 | expect(result).to.not.be.ok;
131 | });
132 |
133 | it('should return false if given an empty collection', function() {
134 | var result = find([], 1);
135 | expect(result).to.not.be.ok;
136 | });
137 | });
138 |
--------------------------------------------------------------------------------
/_workshop/02-5-lodash.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Using helper liberaries
5 | section: 2
6 | prev: 02-4-reduce
7 | name: 02-5-lodash
8 | next: 02-6-point-free
9 | slides:
10 | info: |
11 | In the examples we have seen so far, we have used methods that live
12 | on an array. We are now going to switch tactics and focus more on
13 | functions instead of methods. For example, in addition to
14 | `Array.prototype.reduce`, there exists a `reduce` functions in
15 | libraries such as Lo-Dash and Underscore. One of the primary values
16 | of using these functions instead is composability. We are going to
17 | look more on what exactly that means later. Another great thing is
18 | that some of these functions are more powerful that the methods on
19 | arrays.
20 |
21 | In this course we will use Lo-Dash, which will be accessible through the `_`
22 | global (however, we could also have chosen Underscore instead).
23 | ---
24 |
25 | describe('_.reduce', function() {
26 | // We'll start by going back to reduce. As for the built-in reduce,
27 | // _.reduce does not need a starting value. (Reduce without an initial
28 | // value is actually a fold, and in fact, in Lo-Dash _.reduce is aliased
29 | // to _.foldl, i.e. fold from the left.)
30 |
31 | function join(arr, chr) {
32 | return _.reduce(arr, function(memo, str) {
33 | return memo + chr + str;
34 | });
35 | }
36 |
37 | it('works the same way as the array method', function() {
38 | var result = join(['a','b','c'], ':');
39 | expect(result).to.equal('a:b:c');
40 | });
41 |
42 | });
43 |
44 | // PROBLEM: Determine the longest word using _.reduce
45 |
46 | function longest(arr) {
47 | }
48 |
49 | describe('longest', function() {
50 |
51 | it('finds the longest word in a collection', function() {
52 |
53 | var result = longest(['test', 'kim', 'winning', 'lol']);
54 | expect(result).to.equal('winning');
55 |
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/_workshop/02-6-point-free.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Point-free style
5 | section: 2
6 | prev: 02-5-lodash
7 | name: 02-6-point-free
8 | next: 02-7-context
9 | slides:
10 | info: |
11 | Every time we see the arguments "match up" and are passed
12 | through to the function in the callback, as with the `x`
13 | below, we do not need to create the callback function at all.
14 | That means that this:
15 |
16 | ```
17 | _.filter(collection, function(x) {
18 | return exists(x);
19 | });
20 | ```
21 |
22 | is the same as:
23 |
24 | ```
25 | _.filter(collection, exists);
26 | ```
27 |
28 | This is called point-free style, we will see quite a bit of this today!
29 | (There are some things to think about. We will look at those later.)
30 | ---
31 | describe('point-free style', function() {
32 |
33 | function exists(x) {
34 | return x != null; // i.e. the x is neither `null` nor `undefined`
35 | }
36 |
37 | function truthy(x) {
38 | return exists(x) && x !== false;
39 | }
40 |
41 | var values = ['user', null, false, 0, 'test', 1];
42 |
43 | it('lets us pass functions as references if they have matching arguments', function() {
44 | var result = values.filter(exists);
45 | expect(result).to.deep.equal(['user', false, 0, 'test', 1]);
46 | });
47 |
48 | it('works with many different functions', function() {
49 | var result = values.filter(truthy);
50 | expect(result).to.deep.equal(['user', 0, 'test', 1]);
51 | });
52 |
53 | });
54 |
55 | // PROBLEM: Sum the array using `_.reduce`, following a point-free style.
56 | // (You might need to create a helper function)
57 |
58 | describe('point-free _.reduce', function() {
59 | it('works as normal _.reduce', function() {
60 | var nums = [1,2,3,4,5];
61 |
62 | var sum = null;
63 |
64 | expect(sum).to.equal(15);
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/_workshop/02-7-context.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Applicative functions with context
5 | section: 2
6 | prev: 02-6-point-free
7 | name: 02-7-context
8 | next: 03-1-factories
9 | slides:
10 | info: |
11 | Sometimes you want to use map, filter, reduce or other functions
12 | inside an object and you want want to access something on
13 | `this`. Thankfully this is very simple on _.filter and friends --
14 | just add the context, i.e. what you want `this` to be, as the
15 | last parameter.
16 | ---
17 | describe('filter with context', function() {
18 | var ages = [20, 30, 75, 156, 200];
19 |
20 | var people = {
21 | maxAge: 120,
22 | validAges: function(ages) {
23 |
24 | return _.filter(ages, function(age) {
25 | return age < this.maxAge;
26 | }, this);
27 |
28 | // here we specified that we want `this` inside the function in the
29 | // filter be the same as `this` is inside the current function
30 |
31 | }
32 | };
33 |
34 | it('works by passing a context to the lodash-function which it can bind to', function() {
35 | var result = people.validAges(ages);
36 |
37 | expect(result).to.deep.equal([20, 30, 75]);
38 | });
39 | });
40 |
41 | describe('our filter with context', function() {
42 |
43 | // PROBLEM: Fix our filter implementation below so it handles the
44 | // specified context
45 |
46 | var filter = function(items, fn, context) {
47 | var arr = [];
48 | for (var i = 0; i < items.length; i++) {
49 | if (fn(items[i], i)) {
50 | arr.push(items[i]);
51 | }
52 | }
53 | return arr;
54 | }
55 |
56 | var ages = [20, 30, 75, 156, 200];
57 |
58 | var people = {
59 | maxAge: 120,
60 | validAges: function(ages) {
61 | return filter(ages, function(age) {
62 | return age < this.maxAge;
63 | }, this);
64 | }
65 | };
66 |
67 | it('works when we pass a context that we bind the function to', function() {
68 | var result = people.validAges(ages);
69 | expect(result).to.deep.equal([20, 30, 75]);
70 | });
71 |
72 | });
73 |
--------------------------------------------------------------------------------
/_workshop/03-1-factories.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Function factories
5 | section: 3
6 | prev: 02-7-context
7 | name: 03-1-factories
8 | next: 03-2-higher-order
9 | slides:
10 | info: |
11 | Okey, it is time to take a break from map, filter and reduce, and
12 | start focusing more on higher-order functions and what we can do
13 | with JavaScript.
14 | ---
15 |
16 | // In JavaScript we love factories. Let's create a factory that
17 | // creates adder functions:
18 |
19 | function makeAdder(arg) {
20 | return function(arg2) {
21 | return arg + arg2;
22 | }
23 | }
24 |
25 | describe('makeAdder', function() {
26 | var addTwo = makeAdder(2)
27 |
28 | it('addTwo should add 2', function() {
29 | var result = addTwo(40);
30 | expect(result).to.equal(42);
31 | });
32 | });
33 |
34 | // Seems simple enough, but it's a powerful technique that we will
35 | // be having a lot of fun with.
36 |
37 | // Okey, it's your turn to create a factory.
38 |
39 | // PROBLEM: Create a counter that returns a function that increases a
40 | // number every time it's called.
41 |
42 | function counter() {
43 | }
44 |
45 | describe('counter', function() {
46 | var c1 = counter();
47 | var c2 = counter();
48 |
49 | it('should increase by one with each invocation', function() {
50 | var result = c1();
51 | expect(result).to.equal(0);
52 | });
53 |
54 | it('count = 1', function() {
55 | var result = c1();
56 | expect(result).to.equal(1);
57 | });
58 |
59 | it('count = 2', function() {
60 | var result = c1();
61 | expect(result).to.equal(2);
62 | });
63 |
64 | it('should not share state between counters', function() {
65 | var result = c2();
66 | expect(result).to.equal(0);
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/_workshop/03-2-higher-order.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Higher order functions
5 | section: 3
6 | prev: 03-1-factories
7 | name: 03-2-higher-order
8 | next: 03-3-functions-with-functions
9 | slides:
10 | info: |
11 | Sometimes we want to take a function as an argument. Functions that take
12 | other functions as arguments are called higher order functions.
13 | ---
14 | // PROBLEM: Create a once factory that receives a function that
15 | // will at most be called once. If already run, return the result
16 | // of the first invocation on every succeeding invocation.
17 |
18 | function once(fn) {
19 | }
20 |
21 | describe('once', function() {
22 | var win = once(function(name) {
23 | return name + ' is winning';
24 | });
25 |
26 | it('should declare a winner the first time', function() {
27 | var result = win('kim');
28 | expect(result).to.equal('kim is winning');
29 | });
30 |
31 | it('should not change winner if invoked multiple times', function() {
32 | var result = win('stian');
33 | expect(result).to.equal('kim is winning');
34 | });
35 |
36 | // And we have now decorated a function, i.e. wrapped an existing functions
37 | // with new functionality.
38 | });
39 |
--------------------------------------------------------------------------------
/_workshop/03-3-functions-with-functions.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Functions with functions
5 | section: 3
6 | prev: 03-2-higher-order
7 | name: 03-3-functions-with-functions
8 | next: 03-4-fluent
9 | slides:
10 | info: |
11 | In JavaScript you can actually add functions onto existing
12 | functions. This can create sweet APIs. Let us look at an example.
13 |
14 | I have seen this pattern in many code bases:
15 |
16 | ```
17 | if (debug) console.log('some text');
18 | ```
19 |
20 | When you want to show debug information you set debug to `true`. You of
21 | course need to always remember to write that `if` everywhere in your
22 | code. Let us create a create a better API.
23 | ---
24 | // PROBLEM: We want to guard a function, so that it only calls
25 | // through to the function we pass in once we have called `start`
26 | // on the guard. (See below for the tests to understand how this is
27 | // supposed to work.)
28 |
29 | function guard(fn) {
30 | var guard = function() {
31 | };
32 |
33 | // in JavaScript you can actually add
34 | // functions to existing functions
35 |
36 | guard.start = function() {
37 | };
38 |
39 | return guard;
40 | };
41 |
42 | describe('guard', function() {
43 | // Let's now guard a debug helper. To make it a little bit simpler
44 | // to test, we return a message instead of calling console.log or similar.
45 |
46 | var callCount = 0;
47 | var debug = guard(function(msg) {
48 | callCount += 1;
49 | return msg;
50 | });
51 |
52 | it('should not return anything if not started', function() {
53 | var result = debug('test 1');
54 | expect(callCount).to.equal(0);
55 | expect(result).to.equal(undefined);
56 | });
57 |
58 | it('should not return anything if not started even when invoked multiple times', function() {
59 | var result = debug('test 2');
60 | expect(callCount).to.equal(0);
61 | expect(result).to.equal(undefined);
62 | });
63 |
64 | it('should return if started', function() {
65 | // open up the guard
66 | debug.start();
67 |
68 | var result = debug('test 3');
69 | expect(callCount).to.equal(1);
70 | expect(result).to.equal('test 3');
71 | });
72 |
73 | });
74 |
--------------------------------------------------------------------------------
/_workshop/03-4-fluent.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Fluent interfaces
5 | section: 3
6 | prev: 03-3-functions-with-functions
7 | name: 03-4-fluent
8 | next: 03-5-arity
9 | slides:
10 | info: |
11 | Many of us are used to jQuery and its fluent apis, e.g.
12 |
13 | ```
14 | $('#test')
15 | .css('color','#333')
16 | .height(200)
17 | .on('click', function() {
18 | console.log('clicked!')
19 | });
20 | ```
21 |
22 | Fluent apis is often referred to as method chaining. and we use it to achieve
23 | code that is as fluently readable as possible and thus quicker to understand.
24 | In the jQuery example above, all functions return `this` so we can keep
25 | calling methods that exists on `$('#test')`.
26 |
27 | Let us create a small helper that simplifies the development of
28 | such fluent apis.
29 | ---
30 | // First, to get an understanding of how we can create a simple
31 | // helper for these fluent interfaces, we start with an entirely
32 | // different example, `maybe`:
33 |
34 | function maybe(fn) {
35 | return function(arg) {
36 | if (arg != null) {
37 | return fn(arg);
38 | }
39 | }
40 | }
41 |
42 | describe('maybe', function() {
43 | var exclamate = maybe(function(val) {
44 | return val + '!';
45 | });
46 |
47 | it('should return when called with an argument', function() {
48 | var result = exclamate('test');
49 | expect(result).to.equal('test!');
50 | });
51 |
52 | it('should not return when called with null argument', function() {
53 | var result = exclamate(null);
54 | expect(result).to.equal(undefined);
55 | });
56 |
57 | it('should not return when called without argument', function() {
58 | var result = exclamate();
59 | expect(result).to.equal(undefined);
60 | });
61 |
62 | // We have now created a helper than only calls the received function is
63 | // the input is neither null nor undefined. Yet another decorator, that is.
64 |
65 | // Btw, why doesn't this work?
66 |
67 | var user = {
68 | setName: maybe(function(name) {
69 | this.name = name;
70 | })
71 | };
72 |
73 | it('to throw', function() {
74 | expect(function() {
75 | user.setName('kim');
76 | }).to.throw(TypeError);
77 | });
78 |
79 | // It throws this error at us:
80 | //
81 | // TypeError: Cannot set property 'name' of undefined
82 | });
83 |
84 | // As is often the case in JavaScript, the problem is `this`. We
85 | // forgot to handle the context when we called `fn` in our `maybe`
86 | // decorator. In decorators we must always use `call` or `apply` --
87 | // make them context agnostic!
88 |
89 | // PROBLEM: Now it's time to create a `fluent` decorator
90 |
91 | function fluent(fn) {
92 | }
93 |
94 | describe('fluent', function() {
95 | // Here we have our example `$`, containing a field `value` that we
96 | // want to read out last. (You shouldn't change anything below here.)
97 |
98 | var $ = function(el) {
99 | return {
100 | value: 'somevalue',
101 |
102 | // using our fluent helper
103 | css: fluent(function(key, val) {}),
104 | height: fluent(function(height) {}),
105 | on: fluent(function(event, cb) {})
106 | }
107 | };
108 |
109 |
110 | it('should enable use of fluent interfaces', function() {
111 | var result = $('#test')
112 | .css('color','#333')
113 | .height(200)
114 | .on('click', function() {
115 | console.log('clicked!')
116 | })
117 | .value;
118 |
119 | expect(result).to.equal('somevalue');
120 | });
121 | });
122 |
--------------------------------------------------------------------------------
/_workshop/03-5-arity.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Function arity
5 | section: 3
6 | prev: 03-4-fluent
7 | name: 03-5-arity
8 | next: 04-1-partial-application
9 | slides:
10 | info: |
11 | The arity of a function is the number of arguments it takes.
12 |
13 | E.g. the arity for the following function is 2:
14 |
15 | ```
16 | function plus(a, b) {
17 | return a + b;
18 | }
19 | ```
20 |
21 | ---
22 | describe('arity of parseInt', function() {
23 | // Going from string to int we can use parseInt, e.g.
24 |
25 | it('normal usage', function() {
26 | var result = parseInt('123');
27 | expect(result).to.equal(123);
28 | });
29 |
30 | // However, trying to use this function together with map,
31 | // we see a problem:
32 |
33 | it('is funky when mapping over the function', function() {
34 | var parsed = ['1','2','3'].map(parseInt)
35 |
36 | // The first is 1, as expected
37 | expect(parsed[0]).to.equal(1);
38 |
39 | // ... but the two others are not even numbers:
40 | expect(isNaN(parsed[1])).to.be.true;
41 | expect(isNaN(parsed[2])).to.be.true;
42 | });
43 |
44 | // (we can't use `to.deep.equal` since NaN !== NaN in JavaScript)
45 | });
46 |
47 | describe('fixed parseInt', function() {
48 | // The problem is that map calls each function with three parameters -- the
49 | // value, the index, and the array. Instead of the above, we could write:
50 |
51 | var parsed = ['1','2','3'].map(function(value, index, arr) {
52 | return parseInt(value);
53 | })
54 |
55 | it('should work with map', function() {
56 | var result = parsed;
57 | expect(result).to.deep.equal([1,2,3]);
58 | });
59 |
60 | // However, there are a couple of other solutions. We can for example
61 | // create a helper to do the job for us, and that lets us write:
62 | function unary(fn) {
63 | return function(arg) {
64 | return fn.call(this, arg)
65 | }
66 | }
67 |
68 | var parsed2 = ['1', '2', '3'].map(unary(parseInt))
69 |
70 | // Here we have created unary, which ensures that a function is only ever
71 | // called with one argument, no matter how many you actually send to it.
72 |
73 | it('should work with unary decorator', function() {
74 | var result = parsed2;
75 | expect(result).to.deep.equal([1,2,3]);
76 | });
77 |
78 | // However, unary might be to simple in this case, as we actually
79 | // might want to call:
80 | //
81 | // parseInt(val, 10);
82 | //
83 | // To get there, however, we should first learn some things about
84 | // partial application.
85 | });
86 |
--------------------------------------------------------------------------------
/_workshop/04-2-functions-from-functions.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Create functions from functions
5 | section: 4
6 | prev: 04-1-partial-application
7 | name: 04-2-functions-from-functions
8 | next: 05-1-currying
9 | slides:
10 | info:
11 | ---
12 | // Okey, let's keep playing with partial application. The thing about map,
13 | // filter and reduce is that we often have a "static" function as the last
14 | // parameter, and a "dynamic" input that is applied to the function. Take
15 | // for example this function:
16 |
17 | function square(arr) {
18 | return _.map(arr, function(val) {
19 | return val * val;
20 | });
21 | }
22 |
23 | describe('square', function() {
24 | it('should square numbers', function() {
25 | var result = square([1,2,3]);
26 | expect(result).to.deep.equal([1,4,9]);
27 | });
28 | });
29 |
30 | // With partial application this can also be written like this:
31 | var partialSquare = _.partialRight(_.map, function(val) {
32 | return val * val;
33 | });
34 |
35 | describe('partialSquare', function() {
36 | it('should square numbers like square', function() {
37 | var result = partialSquare([1,2,3]);
38 | expect(result).to.deep.equal([1,4,9]);
39 | });
40 | });
41 |
42 | // Note that we never specify the array argument in `square2`, but we need to
43 | // specify it in `square` (where it's named `arr`). Again, point-free style.
44 |
45 | // PROBLEM: Use _.partialRight to create a function that filters
46 | // all odd values from an array
47 |
48 | var onlyEven = null;
49 |
50 | describe('onlyEven', function() {
51 | it('should only return even numbers', function() {
52 | var result = onlyEven([1,2,3,4,5]);
53 | expect(result).to.deep.equal([2,4]);
54 | });
55 | });
56 |
57 | // Above we needed to use _.partialRight to create our function based on
58 | // `map` and `filter`. As we touched upon you often want to work in the
59 | // opposite direction of the direction specified by Lo-Dash and
60 | // Underscore.js -- that is, we want to partially apply the last parameter,
61 | // the function, first. So, let's create a function that works the other way
62 | // around:
63 |
64 | function mapWith(fn, arr) {
65 | return _.map(arr, fn);
66 | };
67 |
68 | var mapWithSquare = _.partial(mapWith, function(val) {
69 | return val * val;
70 | });
71 |
72 | describe('mapWithSquare', function() {
73 | it('should square numbers', function() {
74 | var result = mapWithSquare([1,2,3]);
75 | expect(result).to.deep.equal([1,4,9]);
76 | });
77 | });
78 |
79 | // PROBLEM: Use `mapWith` and `_.partial` to create a function that adds
80 | // the current index to the current value:
81 |
82 | var plusIndex = null;
83 | describe('plusIndex', function() {
84 | it('should add index to number', function() {
85 | var result = plusIndex([1,2,3]);
86 | expect(result).to.deep.equal([1,3,5]);
87 | });
88 | });
89 |
90 | // But damn, it would be nice to get away from these call to _.partial! In
91 | // order to get there, let's first do a small generalization then we'll take a
92 | // look at a another powerful technique.
93 |
94 | // So, first the generalization: Instead of flipping the arguments ourselves as
95 | // we did in `mapWith` above we can create a flip method which flips the two
96 | // arguments on the passed function:
97 |
98 | function flip(fn) {
99 | return function(first, second) {
100 | return fn.call(this, second, first);
101 | };
102 | };
103 | // We can use this function to create a new `mapWith`:
104 |
105 | var flippedMapWith = flip(_.map);
106 |
107 | // And then, as above:
108 |
109 | var flippedMapWithSquare = _.partial(flippedMapWith, function(val) {
110 | return val * val;
111 | });
112 |
113 |
114 | describe('flippedMapWithSquare', function() {
115 | it('should square numbers', function() {
116 | var result = flippedMapWithSquare([1,2,3]);
117 | expect(result).to.deep.equal([1,4,9]);
118 | });
119 | });
120 |
121 | // PROBLEM: Use flip to create a parseInt that applies in the other
122 | // direction, i.e. the base first. Then partially apply it to create
123 | // parseDigit. (Because flip only works on two arguments, we don't need to
124 | // think about the unary stuff we did earlier.)
125 |
126 | var parseDigit = null;
127 |
128 | var parseDigits = _.partial(mapWith, parseDigit);
129 |
130 | describe('parseDigits', function() {
131 | it('should properly parse digits', function() {
132 | var result = parseDigits(['123', '08', '001']);
133 | expect(result).to.deep.equal([123, 8, 1]);
134 | });
135 | });
136 |
--------------------------------------------------------------------------------
/_workshop/08-done.js:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | collection: workshop
4 | title: Done
5 | section: 7
6 | prev: 07-collections
7 | name: 08-done
8 | slides:
9 | info: |
10 | We have now been on a ride through some of the interesting parts of
11 | functional programming. You have seen some code that doesn't look much like
12 | the JavaScript we normally write. We have also seen that the same code has
13 | interesting properties, such as less focus on state and mutability.
14 | Hopefully you have learned something along the way.
15 |
16 | To wrap up what we have seen today -- basically, functional programming
17 | achieve reuse at a coarser-grained level than object-oriented programming,
18 | extracting common machinery with parameterized behavior. Most applications
19 | do things with lists of elements, so a functional approach is to build reuse
20 | mechanisms around the idea of lists plus contextualized, portable code.
21 |
22 | Btw, want to learn more?
23 |
24 | [https://leanpub.com/javascript-allonge/read](https://leanpub.com/javascript-allonge/read)
25 | and
26 | [http://shop.oreilly.com/product/0636920028857.do](http://shop.oreilly.com/product/0636920028857.do)
27 | ---
28 | // THE END!
29 |
--------------------------------------------------------------------------------
/assets/component-tree.svg:
--------------------------------------------------------------------------------
1 |