├── .gitattributes
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── currying-workshopper.js
├── exercises
├── binary
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── call_and_apply
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── curry_function
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── delay_invocation
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── identity
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
├── long_delay_invocation
│ ├── exercise.js
│ ├── problem.md
│ └── solution
│ │ └── solution.js
└── menu.json
├── package.json
└── test
├── binary
└── solution.js
└── identity
├── identity
└── solution.js
└── solution.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly declare text files you want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.js text
7 |
8 | # Declare files that will always have CRLF line endings on checkout.
9 | *.sln text eol=crlf
10 |
11 | # Denote all files that are truly binary and should not be modified.
12 | *.png binary
13 | *.jpg binary
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 | cmnd.bat
29 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at kishorsharma7@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 kishorsharma
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # currying-workshopper
2 |
3 | ### Teaching currying features of Javascript.
4 |
5 | #### No libraries required (i.e. no underscore), Pure JavaScript.
6 |
7 |
8 |
9 |
10 |
11 | ## Why Currying
12 |
13 | The goal of this workshop is to learn concept and create a function to apply curry in JavaScript.
14 |
15 | Currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.
16 | The number of arguments or operands that the function takes is also known as arity /ˈærᵻti/ of a function.
17 |
18 | JavaScript has functional capabilities, but currying isn’t built in by default (at least not till the time of writing.. :)).
19 |
20 | ## Installation & Update
21 |
22 | ```
23 | $ npm install -g currying-workshopper
24 | ```
25 |
26 | Some npm installations require use of `sudo` in the above command. Recommend to instead [reinstall node/npm so you don't need sudo](https://gist.github.com/isaacs/579814).
27 |
28 | ## Usage Instructions
29 |
30 | #### 1. Selecting a problem to work on
31 |
32 | Once the workshop is installed, run `curry` to print a menu
33 | where you can select a problem to work on.
34 |
35 | ```
36 | $ curry
37 | ```
38 |
39 | You are advised to complete them in order, as later problems will build on skills developed by solving previous problems.
40 |
41 | #### 2. Writing your solution
42 |
43 | Once you have selected a problem, the workshop will remember which problem you are working on.
44 | Using your preferred editor, simply create a file to write your solution in.
45 |
46 | #### 3. Testing your solution
47 |
48 | Use the workshop's `run` command to point the workshop at your solution file. Your solution will loaded
49 | and passed the problem input. This usually won't perform any validation, it will only show the program output.
50 |
51 | ```
52 | $ curry run mysolution.js
53 | ```
54 |
55 | #### 4. Verifying your solution
56 |
57 | Your solution will be verified against the output of the 'official' solution.
58 | If all of the output matches, then you have successfully solved the problem!
59 |
60 | ```
61 | $ curry verify mysolution.js
62 | ```
63 |
64 | ## Stuck?
65 |
66 | Feedback and criticism is welcome, please log your troubles in [issues](https://github.com/kishorsharma/currying-workshopper/issues).
67 |
68 | Full curriculum reviews [like this one](https://github.com/kishorsharma/currying-workshopper/issues/1) are incredibly helpful. More feedback like this please!
69 |
70 | We're looking for more practical problems, so if you come across a problem in your day-to-day work which was solved simply and elegantly with currying techniques, please help us create an exercise out of it.
71 |
72 | ## Thanks rvagg
73 |
74 | This tutorial was built using rvagg's [workshopper](https://github.com/rvagg/workshopper) framework.
75 |
76 | ## License
77 |
78 | MIT
79 |
--------------------------------------------------------------------------------
/currying-workshopper.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const workshopper = require('workshopper')
4 | , path = require('path')
5 |
6 |
7 | function fpath (f) {
8 | return path.join(__dirname, f)
9 | }
10 |
11 | workshopper({
12 | name : 'curry'
13 | , title : 'Currying in JavaScript'
14 | , appDir : __dirname
15 | })
16 |
--------------------------------------------------------------------------------
/exercises/binary/exercise.js:
--------------------------------------------------------------------------------
1 | var exercise = require('workshopper-exercise')()
2 | , filecheck = require('workshopper-exercise/filecheck')
3 | , execute = require('workshopper-exercise/execute')
4 | , comparestdout = require('workshopper-exercise/comparestdout')
5 | , path = require('path')
6 | , fs = require('fs')
7 |
8 |
9 | // checks that the submission file actually exists
10 | exercise = filecheck(exercise)
11 |
12 | // add setup.
13 | exercise.addSetup(function (mode, callback) {
14 |
15 | this.solutionModule = require(getSolutionPath() + 'solution.js');
16 | this.submissionModule = require([process.cwd(), this.args[0]].join('/'));
17 | process.nextTick(callback);
18 | });
19 |
20 | // add a processor.
21 | exercise.addProcessor(function (mode, callback) {
22 | var pass = true;
23 | var randomInput1 = 1 + Math.random() * 100;
24 | var randomInput2 = 1 + Math.random() * 100;
25 | var solutionResult = this.solutionModule(randomInput1, randomInput2);
26 | var submissionResult = this.submissionModule(randomInput1, randomInput2);
27 | if (solutionResult !== submissionResult) {
28 | exercise.emit('fail', 'Expected result: ' + solutionResult + ' \nActual result: '+ submissionResult);
29 | pass = false;
30 | }
31 | process.nextTick(function () {
32 | callback(null, pass)
33 | });
34 | });
35 |
36 | // Print out the suggested solution when the student passes. This is copied from
37 | // workshopper-exercise/execute because the rest of execute is not relevant to
38 | // the way this is tested.
39 | exercise.getSolutionFiles = function (callback) {
40 | var solutionDir = getSolutionPath();
41 |
42 | fs.readdir(solutionDir, function (err, list) {
43 | if (err) {
44 | return callback(err);
45 | }
46 |
47 | list = list
48 | .filter(function (f) { return (/\.js$/).test(f) })
49 | .map(function (f) { return path.join(solutionDir, f)});
50 |
51 | callback(null, list);
52 | });
53 | };
54 |
55 | function getSolutionPath() {
56 | return path.join(exercise.dir, './solution/');
57 | }
58 |
59 | module.exports = exercise
60 |
--------------------------------------------------------------------------------
/exercises/binary/problem.md:
--------------------------------------------------------------------------------
1 | ## Task
2 |
3 | Write a function that takes two arguments and returns their sum.
4 |
5 | ----------------------------------------------------------------------
6 | ## HINTS
7 |
8 | By now you know how to write a function and expose it as a Node module.
9 |
10 | Binary functions are functions that accept two arguments.
11 |
12 | For example:
13 | ```js
14 | var binary = function (firstArg, secArg) {
15 | // your logic
16 | }
17 | ```
18 |
19 | When you are done, you must run:
20 | ```sh
21 | $ {appname} verify program.js
22 | ```
23 |
24 | to proceed. Your program will be tested, a report will be generated, and the
25 | lesson will be marked 'completed' if you are successful.
26 |
27 | ----------------------------------------------------------------------
28 |
--------------------------------------------------------------------------------
/exercises/binary/solution/solution.js:
--------------------------------------------------------------------------------
1 | var binaryAdd = function (a, b) {
2 | return a+b;
3 | }
4 |
5 | module.exports = binaryAdd;
6 |
--------------------------------------------------------------------------------
/exercises/call_and_apply/exercise.js:
--------------------------------------------------------------------------------
1 | var exercise = require('workshopper-exercise')()
2 | , filecheck = require('workshopper-exercise/filecheck')
3 | , execute = require('workshopper-exercise/execute')
4 | , comparestdout = require('workshopper-exercise/comparestdout')
5 | , path = require('path')
6 | , fs = require('fs')
7 | , util = require('util')
8 |
9 |
10 | // checks that the submission file actually exists
11 | exercise = filecheck(exercise)
12 |
13 | // add setup.
14 | exercise.addSetup(function (mode, callback) {
15 |
16 | this.solutionModule = require(getSolutionPath() + 'solution.js');
17 | this.submissionModule = require([process.cwd(), this.args[0]].join('/'));
18 | process.nextTick(callback);
19 | });
20 |
21 | // add a processor.
22 | exercise.addProcessor(function (mode, callback) {
23 | var pass = true;
24 | var update = function(name, age, tShirtSize){
25 | this.name = name;
26 | this.age = age;
27 | this.tShirtSize = tShirtSize;
28 | };
29 |
30 | var personForSolutionCall = {name: "Brij", age: 28, tShirtSize: 'L'};
31 | var personForSubmissionCall = {name: "Brij", age: 28, tShirtSize: 'L'};
32 |
33 | // invoke `update` and mutate the test subjects
34 | this.solutionModule.caller(personForSolutionCall, update, 'Kishor', 29, 'XL');
35 | this.submissionModule.caller(personForSubmissionCall, update, 'Kishor', 29, 'XL');
36 |
37 | if (personForSubmissionCall.name !== personForSolutionCall.name || personForSubmissionCall.age !== personForSolutionCall.age || personForSubmissionCall.tShirtSize !== personForSolutionCall.tShirtSize) {
38 | exercise.emit('fail', 'Call method result in error. \nExpected result: ' + showObjectInLog(personForSolutionCall) + ' \nActual result: '+ showObjectInLog(personForSubmissionCall));
39 | pass = false;
40 | }
41 |
42 | var personForSolutionApply = {name: "Kishor", age: 24, tShirtSize: 'S'};
43 | var personForSubmissionApply = {name: "Kishor", age: 24, tShirtSize: 'S'};
44 |
45 | // invoke `update` and mutate the test subjects
46 | this.solutionModule.applier(personForSolutionApply, update, ['Brij', 26, 'M']);
47 | this.submissionModule.applier(personForSubmissionApply, update, ['Brij', 26, 'M']);
48 |
49 | if (personForSubmissionApply.name !== personForSolutionApply.name || personForSubmissionApply.age !== personForSolutionApply.age || personForSubmissionApply.tShirtSize !== personForSolutionApply.tShirtSize) {
50 | exercise.emit('fail', 'Apply method result in error. \nExpected result: ' + showObjectInLog(personForSolutionApply) + ' \nActual result: '+ showObjectInLog(personForSubmissionApply));
51 | pass = false;
52 | }
53 | process.nextTick(function () {
54 | callback(null, pass)
55 | });
56 | });
57 |
58 | // Print out the suggested solution when the student passes. This is copied from
59 | // workshopper-exercise/execute because the rest of execute is not relevant to
60 | // the way this is tested.
61 | exercise.getSolutionFiles = function (callback) {
62 | var solutionDir = getSolutionPath();
63 |
64 | fs.readdir(solutionDir, function (err, list) {
65 | if (err) {
66 | return callback(err);
67 | }
68 |
69 | list = list
70 | .filter(function (f) { return (/\.js$/).test(f) })
71 | .map(function (f) { return path.join(solutionDir, f)});
72 |
73 | callback(null, list);
74 | });
75 | };
76 |
77 | function getSolutionPath() {
78 | return path.join(exercise.dir, './solution/');
79 | }
80 |
81 | // Print out an object's key value pairs when that object is used in `console.log`.
82 | // Normally, would output: `[object Object]`.
83 | function showObjectInLog(obj) {
84 | return util.inspect(obj);
85 | }
86 |
87 | module.exports = exercise
88 |
--------------------------------------------------------------------------------
/exercises/call_and_apply/problem.md:
--------------------------------------------------------------------------------
1 | ## Good going!!
2 |
3 | Simple currying is good. However delaying invocation for only the second call is
4 | not extremely advantageous. Also, if all the arguments are already provided,
5 | then delaying invocation for second or subsequent calls will be unnecessary
6 | overhead. We want to call our function as:
7 |
8 | ```js
9 | sum(3,4); // 7
10 | sum(3)(4); // 7
11 | ```
12 |
13 | To dive deep into this we first need to understand the following functions:
14 |
15 | * call
16 | * apply
17 |
18 | For this we have to create two methods, caller (for call) and applier (for
19 | apply).
20 |
21 | ## Task
22 |
23 | Write a function which will accept a method, an object, and additional
24 | parameters. Then invoke the method on the object, passing the parameters.
25 |
26 | Suppose we have a method:
27 |
28 | ```js
29 | var update = function(name, age, tShirtSize) {
30 | this.name = name;
31 | this.age = age;
32 | this.tShirtSize = tShirtSize;
33 | };
34 | ```
35 |
36 | and a person object:
37 |
38 | ```js
39 | var person = { name: 'Kishor', age: 28, tShirtSize: 'L' };
40 | ```
41 |
42 | You need to provide the implementation for the method:
43 |
44 | ```js
45 | var caller = function (person,
46 | update,
47 | name, //'Sharma'
48 | age, // 29
49 | tShirtSize) { // 'XL'
50 | // your code here
51 | };
52 | ```
53 |
54 | ```sh
55 | console.log(person) // => person.name = Sharma, person.age = 29 and person.tShirtSize = XL
56 | ```
57 |
58 | ----------------------------------------------------------------------
59 | ## HINTS
60 |
61 | The call() method is inherited from Function.prototype. It calls a function with
62 | a given 'this' value and arguments provided individually. The apply() method is
63 | similar to call(), except the arguments are provided as an array.
64 |
65 | To know more about call:
66 | https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Function/call
67 |
68 | To know more about apply:
69 | https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
70 |
71 |
72 | When you are done, you must run:
73 | ```sh
74 | $ {appname} verify program.js
75 | ```
76 |
77 | to proceed. Your program will be tested, a report will be generated, and the
78 | lesson will be marked 'completed' if you are successful.
79 |
80 | ----------------------------------------------------------------------
81 | ## Boilerplate
82 |
83 | ```js
84 | var callAndApply = {
85 | caller: function (object, method, nameArg, ageArg, tShirtSizeArg) {
86 | //your code
87 | },
88 | applier: function (object, method, argumentsArr) {
89 | // your code
90 | }
91 | };
92 | module.exports = callAndApply;
93 | ```
94 |
--------------------------------------------------------------------------------
/exercises/call_and_apply/solution/solution.js:
--------------------------------------------------------------------------------
1 | var callAndApply = {
2 | caller: function (object, method, nameArg, ageArg, tShirtSizeArg) {
3 | method.call(object, nameArg, ageArg, tShirtSizeArg)
4 | },
5 | applier: function (object, method, argumentsArr) {
6 | method.apply(object, argumentsArr);
7 | }
8 | };
9 | module.exports = callAndApply;
10 |
--------------------------------------------------------------------------------
/exercises/curry_function/exercise.js:
--------------------------------------------------------------------------------
1 | var exercise = require('workshopper-exercise')()
2 | , filecheck = require('workshopper-exercise/filecheck')
3 | , execute = require('workshopper-exercise/execute')
4 | , comparestdout = require('workshopper-exercise/comparestdout')
5 | , path = require('path')
6 | , fs = require('fs')
7 |
8 |
9 | // checks that the submission file actually exists
10 | exercise = filecheck(exercise)
11 |
12 | // add setup.
13 | exercise.addSetup(function (mode, callback) {
14 |
15 | this.solutionModule = require(getSolutionPath() + 'solution.js');
16 | this.submissionModule = require([process.cwd(), this.args[0]].join('/'));
17 | process.nextTick(callback);
18 | });
19 |
20 | // add a processor.
21 | exercise.addProcessor(function (mode, callback) {
22 | var pass = true;
23 | var inputArr = [];
24 | for (var i =0; i < 5; i++ ) {
25 | inputArr.push(Math.floor(1 + Math.random() * 100));
26 | }
27 | var addFunction = function (a,b,c,d) {
28 | return a+b+c+d;
29 | };
30 | var solutionResult = this.solutionModule(addFunction)(inputArr[0],inputArr[1])(inputArr[2])(inputArr[3]);
31 | var submissionResult = this.submissionModule(addFunction)(inputArr[0])(inputArr[1])(inputArr[2],inputArr[3]);
32 | if (solutionResult !== submissionResult) {
33 | exercise.emit('fail', 'Expected result: ' + solutionResult + ' \nActual result: '+ submissionResult);
34 | pass = false;
35 | }
36 | process.nextTick(function () {
37 | callback(null, pass);
38 | });
39 | });
40 |
41 | // Print out the suggested solution when the student passes. This is copied from
42 | // workshopper-exercise/execute because the rest of execute is not relevant to
43 | // the way this is tested.
44 | exercise.getSolutionFiles = function (callback) {
45 | var solutionDir = getSolutionPath();
46 |
47 | fs.readdir(solutionDir, function (err, list) {
48 | if (err) {
49 | return callback(err);
50 | }
51 |
52 | list = list
53 | .filter(function (f) { return (/\.js$/).test(f) })
54 | .map(function (f) { return path.join(solutionDir, f)});
55 |
56 | callback(null, list);
57 | });
58 | };
59 |
60 | function getSolutionPath() {
61 | return path.join(exercise.dir, './solution/');
62 | }
63 |
64 | module.exports = exercise
65 |
--------------------------------------------------------------------------------
/exercises/curry_function/problem.md:
--------------------------------------------------------------------------------
1 | ## Recap
2 |
3 | Great, if you've made it this far you have learned following:
4 |
5 | * Closures, their advantages and how to use them.
6 | * Function call and apply.
7 | * How to make a basic curry function that adds.
8 |
9 | What's next?
10 |
11 | ## Task
12 |
13 | Now, using this knowledge, we will create a function that will take another
14 | function as argument (any function that we want to be curried) and convert it
15 | into a curried function.
16 |
17 | For example, suppose we have a function:
18 |
19 | ```js
20 | function abc(a,b,c) {
21 | }
22 | ```
23 |
24 | You need to write a function that curries that function, so it can be used as
25 | follows:
26 |
27 | ```js
28 | var curriedAbc = curryFunc(abc);
29 | curriedAbc(a)(b)(c); // Now we can call original function like this...
30 | curriedAbc(a,b)(c); //or this
31 | curriedAbc(a)(b,c); //or this
32 | curriedAbc(a,b,c); //or this
33 | ```
34 |
35 | ----------------------------------------------------------------------
36 | ## HINTS
37 |
38 | Implementation is taken from an article written by Alex Cruikshank:
39 | http://blog.carbonfive.com/2015/01/14/gettin-freaky-functional-wcurried-javascript/
40 |
41 | I suggest you wait to read the article until you put some significant effort
42 | into figuring out a solution.
43 |
44 | Special thanks to Mr. Cruikshank for such a good article.
45 |
46 | When you are done, you must run:
47 | ```sh
48 | $ {appname} verify program.js
49 | ```
50 |
51 | to proceed. Your program will be tested, a report will be generated, and the
52 | lesson will be marked 'completed' if you are successful.
53 |
54 | ----------------------------------------------------------------------
55 |
--------------------------------------------------------------------------------
/exercises/curry_function/solution/solution.js:
--------------------------------------------------------------------------------
1 | function curry(fx) {
2 | var arity = fx.length;
3 | return function f1() {
4 | var args = Array.prototype.slice.call(arguments, 0);
5 | if (args.length >= arity) {
6 | return fx.apply(null, args);
7 | } else {
8 | return function f2() {
9 | var args2 = Array.prototype.slice.call(arguments, 0);
10 | return f1.apply(null, args.concat(args2));
11 | };
12 | }
13 | };
14 | }
15 |
16 | module.exports = curry;
17 |
--------------------------------------------------------------------------------
/exercises/delay_invocation/exercise.js:
--------------------------------------------------------------------------------
1 | var exercise = require('workshopper-exercise')()
2 | , filecheck = require('workshopper-exercise/filecheck')
3 | , execute = require('workshopper-exercise/execute')
4 | , comparestdout = require('workshopper-exercise/comparestdout')
5 | , path = require('path')
6 | , fs = require('fs')
7 |
8 |
9 | // checks that the submission file actually exists
10 | exercise = filecheck(exercise)
11 |
12 | // add setup.
13 | exercise.addSetup(function (mode, callback) {
14 |
15 | this.solutionModule = require(getSolutionPath() + 'solution.js');
16 | this.submissionModule = require([process.cwd(), this.args[0]].join('/'));
17 | process.nextTick(callback);
18 | });
19 |
20 | // add a processor.
21 | exercise.addProcessor(function (mode, callback) {
22 | var pass = true;
23 | var randomInput1 = 1 + Math.random() * 100;
24 | var randomInput2 = 1 + Math.random() * 100;
25 | var solutionResult = this.solutionModule(randomInput1)(randomInput2);
26 | var submissionResult = this.submissionModule(randomInput1)(randomInput2);
27 | if (solutionResult !== submissionResult) {
28 | exercise.emit('fail', 'Expected result: ' + solutionResult + ' \nActual result: '+ submissionResult);
29 | pass = false;
30 | }
31 | process.nextTick(function () {
32 | callback(null, pass)
33 | });
34 | });
35 |
36 | // Print out the suggested solution when the student passes. This is copied from
37 | // workshopper-exercise/execute because the rest of execute is not relevant to
38 | // the way this is tested.
39 | exercise.getSolutionFiles = function (callback) {
40 | var solutionDir = getSolutionPath();
41 |
42 | fs.readdir(solutionDir, function (err, list) {
43 | if (err) {
44 | return callback(err);
45 | }
46 |
47 | list = list
48 | .filter(function (f) { return (/\.js$/).test(f) })
49 | .map(function (f) { return path.join(solutionDir, f)});
50 |
51 | callback(null, list);
52 | });
53 | };
54 |
55 | function getSolutionPath() {
56 | return path.join(exercise.dir, './solution/');
57 | }
58 |
59 | module.exports = exercise
60 |
--------------------------------------------------------------------------------
/exercises/delay_invocation/problem.md:
--------------------------------------------------------------------------------
1 | ## Task
2 |
3 | Write a unary function that takes single input and returns another unary
4 | function. On calling the second function, it should return the sum of the two
5 | inputs.
6 |
7 | ----------------------------------------------------------------------
8 | ## HINTS
9 |
10 | So far so good!! You are doing great. Now it is time to explore some important
11 | concepts essential for currying:
12 |
13 | * Lexical Scope
14 | * Closures
15 |
16 | The solution to this puzzle is creating a closure. A closure is a special kind
17 | of object that combines two things: a function, and the environment in which
18 | that function was created. The environment consists of any local variables that
19 | were in scope at the time that the closure was created.
20 |
21 | To learn more about these concepts, read:
22 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
23 |
24 | When you are done, you must run:
25 | ```sh
26 | $ {appname} verify program.js
27 | ```
28 |
29 | to proceed. Your program will be tested, a report will be generated, and the
30 | lesson will be marked 'completed' if you are successful.
31 |
32 | ----------------------------------------------------------------------
33 |
--------------------------------------------------------------------------------
/exercises/delay_invocation/solution/solution.js:
--------------------------------------------------------------------------------
1 | var secondInvoc = function (a) {
2 | return function(b) {
3 | return a + b;
4 | }
5 | }
6 |
7 | module.exports = secondInvoc;
8 |
--------------------------------------------------------------------------------
/exercises/identity/exercise.js:
--------------------------------------------------------------------------------
1 | var exercise = require('workshopper-exercise')()
2 | , filecheck = require('workshopper-exercise/filecheck')
3 | , execute = require('workshopper-exercise/execute')
4 | , comparestdout = require('workshopper-exercise/comparestdout')
5 | , path = require('path')
6 | , fs = require('fs')
7 |
8 |
9 | // checks that the submission file actually exists
10 | exercise = filecheck(exercise)
11 |
12 | // add setup.
13 | exercise.addSetup(function (mode, callback) {
14 |
15 | this.solutionModule = require(getSolutionPath() + 'solution.js');
16 | this.submissionModule = require([process.cwd(), this.args[0]].join('/'));
17 | process.nextTick(callback);
18 | });
19 |
20 | // add a processor.
21 | exercise.addProcessor(function (mode, callback) {
22 | var pass = true;
23 | var random = Math.random();
24 | var idx = this.submissionModule(random);
25 | if (idx !== random) {
26 | exercise.emit('fail', 'this.ready was not set to true.');
27 | pass = false;
28 | }
29 | process.nextTick(function () {
30 | callback(null, pass)
31 | });
32 | });
33 |
34 | // Print out the suggested solution when the student passes. This is copied from
35 | // workshopper-exercise/execute because the rest of execute is not relevant to
36 | // the way this is tested.
37 | exercise.getSolutionFiles = function (callback) {
38 | var solutionDir = getSolutionPath();
39 |
40 | fs.readdir(solutionDir, function (err, list) {
41 | if (err) {
42 | return callback(err);
43 | }
44 |
45 | list = list
46 | .filter(function (f) { return (/\.js$/).test(f) })
47 | .map(function (f) { return path.join(solutionDir, f)});
48 |
49 | callback(null, list);
50 | });
51 | };
52 |
53 | function getSolutionPath() {
54 | return path.join(exercise.dir, './solution/');
55 | }
56 | // compare stdout of solution and submission
57 | //exercise = comparestdout(exercise)
58 |
59 | module.exports = exercise
60 |
--------------------------------------------------------------------------------
/exercises/identity/problem.md:
--------------------------------------------------------------------------------
1 | ## Task
2 |
3 | Write a function that takes an argument and returns that argument.
4 |
5 | ----------------------------------------------------------------------
6 | ## HINTS
7 |
8 | To make a Node.js program, create a new file with a `.js` extension and start
9 | writing JavaScript! Execute your program by running it with the `node` command.
10 |
11 | For example:
12 |
13 | ```sh
14 | $ node program.js
15 | ```
16 |
17 | Writing functions that are available to the outside world is different in Node.
18 | You need to export your function as follows:
19 |
20 | ```js
21 | var identity = function (args) {
22 | //your code
23 | };
24 | module.exports = identity;
25 | ```
26 |
27 | Node modules are beyond the scope of this exercise. However, if you are
28 | interested, there is great blog post on exporting Node modules:
29 | http://www.sitepoint.com/understanding-module-exports-exports-node-js
30 |
31 | When you are done, you must run:
32 |
33 | ```sh
34 | $ {appname} verify program.js
35 | ```
36 |
37 | to proceed. Your program will be tested, a report will be generated, and the
38 | lesson will be marked 'completed' if you are successful.
39 |
40 | ----------------------------------------------------------------------
41 |
--------------------------------------------------------------------------------
/exercises/identity/solution/solution.js:
--------------------------------------------------------------------------------
1 | var identity = function (a) {
2 | return a;
3 | }
4 |
5 | module.exports = identity;
6 |
--------------------------------------------------------------------------------
/exercises/long_delay_invocation/exercise.js:
--------------------------------------------------------------------------------
1 | var exercise = require('workshopper-exercise')()
2 | , filecheck = require('workshopper-exercise/filecheck')
3 | , execute = require('workshopper-exercise/execute')
4 | , comparestdout = require('workshopper-exercise/comparestdout')
5 | , path = require('path')
6 | , fs = require('fs')
7 |
8 |
9 | // checks that the submission file actually exists
10 | exercise = filecheck(exercise)
11 |
12 | // add setup.
13 | exercise.addSetup(function (mode, callback) {
14 |
15 | this.solutionModule = require(getSolutionPath() + 'solution.js');
16 | this.submissionModule = require([process.cwd(), this.args[0]].join('/'));
17 | process.nextTick(callback);
18 | });
19 |
20 | // add a processor.
21 | exercise.addProcessor(function (mode, callback) {
22 | var pass = true;
23 | var inputArr = [];
24 | for (var i =0; i < 5; i++ ) {
25 | inputArr.push(Math.floor(1 + Math.random() * 100));
26 | }
27 |
28 | var solutionResult = this.solutionModule(inputArr[0])(inputArr[1])(inputArr[2])(inputArr[3])(inputArr[4])();
29 | var submissionResult = this.submissionModule(inputArr[0])(inputArr[1])(inputArr[2])(inputArr[3])(inputArr[4])();
30 | if (solutionResult !== submissionResult) {
31 | exercise.emit('fail', 'Expected result: ' + solutionResult + ' \nActual result: '+ submissionResult);
32 | pass = false;
33 | }
34 | process.nextTick(function () {
35 | callback(null, pass);
36 | });
37 | });
38 |
39 | // Print out the suggested solution when the student passes. This is copied from
40 | // workshopper-exercise/execute because the rest of execute is not relevant to
41 | // the way this is tested.
42 | exercise.getSolutionFiles = function (callback) {
43 | var solutionDir = getSolutionPath();
44 |
45 | fs.readdir(solutionDir, function (err, list) {
46 | if (err) {
47 | return callback(err);
48 | }
49 |
50 | list = list
51 | .filter(function (f) { return (/\.js$/).test(f) })
52 | .map(function (f) { return path.join(solutionDir, f)});
53 |
54 | callback(null, list);
55 | });
56 | };
57 |
58 | function getSolutionPath() {
59 | return path.join(exercise.dir, './solution/');
60 | }
61 |
62 | module.exports = exercise
63 |
--------------------------------------------------------------------------------
/exercises/long_delay_invocation/problem.md:
--------------------------------------------------------------------------------
1 | ## It's a nice feeling
2 |
3 | Wow!! You have just created a very basic curry function. Isn't it amazing how
4 | simple it is? Let's make it a little more complex. Are you ready?
5 |
6 | ## Task
7 |
8 | Write a function that takes one argument for each invocation. Each time it is
9 | called, it should add its argument to a running total and return itself. If it is called with no
10 | arguments, it should return the sum of all the arguments passed.
11 |
12 | ----------------------------------------------------------------------
13 | ## HINTS
14 |
15 | So far so good!! You are doing great. Now it is time to explore some important
16 | concepts essential for currying:
17 |
18 | * Lexical Scope
19 | * Closures
20 |
21 | The solution to this puzzle is creating a closure. A closure is a special kind
22 | of object that combines two things: a function, and the environment in which
23 | that function was created. The environment consists of any local variables that
24 | were in scope at the time that the closure was created.
25 |
26 | To learn more about these concepts, read:
27 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
28 |
29 | When you are done, you must run:
30 | ```sh
31 | $ {appname} verify program.js
32 | ```
33 |
34 | to proceed. Your program will be tested, a report will be generated, and the
35 | lesson will be marked 'completed' if you are successful.
36 |
37 | ----------------------------------------------------------------------
38 |
--------------------------------------------------------------------------------
/exercises/long_delay_invocation/solution/solution.js:
--------------------------------------------------------------------------------
1 | var total = 0;
2 | var delayInvoc = function (a) {
3 | if (a === undefined) {
4 | var result = total;
5 | total = null;
6 | return result;
7 | } else {
8 | total = total + a;
9 | return delayInvoc;
10 | }
11 | };
12 |
13 | module.exports = delayInvoc;
14 |
--------------------------------------------------------------------------------
/exercises/menu.json:
--------------------------------------------------------------------------------
1 | [
2 | "IDENTITY",
3 | "BINARY",
4 | "DELAY INVOCATION",
5 | "LONG_DELAY_INVOCATION",
6 | "CALL AND APPLY",
7 | "CURRY FUNCTION"
8 | ]
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "currying-workshopper",
3 | "version": "1.0.17",
4 | "description": "Learn how to write a curry function in JavaScript and use it.",
5 | "title": "Currying in JavaScript",
6 | "author": "Brij Kishor (https://github.com/kishorsharma)",
7 | "repository": {
8 | "type": "git",
9 | "url": "git@github.com:kishorsharma/currying-workshopper"
10 | },
11 | "license": "MIT",
12 | "dependencies": {
13 | "path": "^0.11.14",
14 | "workshopper": "^2.7.0",
15 | "workshopper-exercise": "^2.4.0"
16 | },
17 | "bin": {
18 | "curry": "./currying-workshopper.js"
19 | },
20 | "main": "./currying-workshopper.js",
21 | "preferGlobal": true
22 | }
23 |
--------------------------------------------------------------------------------
/test/binary/solution.js:
--------------------------------------------------------------------------------
1 | module.exports = function(firstArg, secArg) {
2 | return firstArg + secArg;
3 | }
4 |
--------------------------------------------------------------------------------
/test/identity/identity/solution.js:
--------------------------------------------------------------------------------
1 | module.exports = function(input) {
2 | return input;
3 | }
4 |
--------------------------------------------------------------------------------
/test/identity/solution.js:
--------------------------------------------------------------------------------
1 | module.exports = function(input) {
2 | return input;
3 | }
4 |
--------------------------------------------------------------------------------