├── .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 | --------------------------------------------------------------------------------