Skip to content

Commit

Permalink
functorfied
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Lonsdorf committed Jun 6, 2015
1 parent 0f8e45e commit aba16d0
Show file tree
Hide file tree
Showing 19 changed files with 1,126 additions and 1 deletion.
1 change: 1 addition & 0 deletions ch6.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,4 @@ var images = _.compose(_.map(mediaToImg), _.prop('items'));

We have seen how to put our new skills into use with a small, but real world app. We've used our mathematical framework to reason about and refactor our code. But what about error handling and code branching? How can we make the whole application pure instead of merely namespacing destructive functions? How can we make our app safer and more expressive? These are the questions we will tackle in part 2.

[Chapter 7: Hindley-Milner and Me](ch7.md)
12 changes: 11 additions & 1 deletion ch7.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,23 @@ var match = curry(function(reg, s){

Ah yes, grouping the last part in parenthesis reveals more information. Now it is seen a function that takes a `Regex` and returns us a function from `String` to `[String]`. Because of currying, this is indeed the case: give it a `Regex` and we get a function back waiting for it's `String` argument. Of course, we don't have to think of it this way, but it is good to understand why the last type is the one returned.

```js
// match :: Regex -> (String -> [String])

// onHoliday :: String -> String
var onHoliday = match(/holiday/ig);
```

Each argument pops one type off the front of the signature. `onHoliday` is `match` that already has a `Regex`.

```js
// replace :: Regex -> (String -> (String -> String))
var replace = curry(function(reg, sub, s){
return s.replace(reg, sub);
});
```

As you can see, the parenthesis can get a little noisy and redundant so we simply omit them. We can give all the arguments at once if we choose so it's easier to just think of it as: `replace` takes a `Regex`, a `String`, another `String` and returns you a `String`.
As you can see with the full parenthesis on `replace`, the extra notation can get a little noisy and redundant so we simply omit them. We can give all the arguments at once if we choose so it's easier to just think of it as: `replace` takes a `Regex`, a `String`, another `String` and returns you a `String`.

A few last things here:

Expand Down Expand Up @@ -159,3 +168,4 @@ These are just two examples, but you can apply this reasoning to any polymorphic

Hindley-Milner type signatures are ubiquitous in the functional world. Though they are simple to read and write, it takes time to master the technique of understanding programs through signatures alone. We will add type signatures to each line of code from here on out.

[Chapter 8: Tupperware](ch8.md)
681 changes: 681 additions & 0 deletions ch8.md

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions code/lib/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "MAG_lib",
"version": "0.0.1",
"description": "Support libs for the book",
"main": "index.js",
"dependencies": {
"ramda": "^0.13.0"
},
"repository": {
"type": "git",
"url": "https://github.com/DrBoolean/mostly-adequate-guide"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/DrBoolean/mostly-adequate-guide/issues"
},
"homepage": "https://github.com/DrBoolean/mostly-adequate-guide"
}
15 changes: 15 additions & 0 deletions code/part2_exercises/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Part 1 Exercises
==================

**Installation**:
`npm install`

**Example of running tests**:
Tests are all located in their corresponding folder so you should cd into the folder to run.

```
cd exercises/curry
mocha
```

Some will fail and some will pass. The passing ones expect to be refactored.
98 changes: 98 additions & 0 deletions code/part2_exercises/answers/functors/functor_exercises.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
require('../../support');
var Task = require('data.task');
var _ = require('ramda');

// Exercise 1
// ==========
// Use _.add(x,y) and map(f,x) to make a function that increments a value inside a functor

var ex1 = _.map(_.add(1));



// Exercise 2
// ==========
// Use _.head to get the first element of the list
var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);

var ex2 = _.map(_.head);



// Exercise 3
// ==========
// Use safeProp and _.head to find the first initial of the user
var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });

var user = { id: 2, name: "Albert" };

var ex3 = _.compose(_.map(_.head), safeProp('name'));


// Exercise 4
// ==========
// Use Maybe to rewrite ex4 without an if statement

var ex4 = function (n) {
if (n) { return parseInt(n); }
};

var ex4 = _.compose(_.map(parseInt), Maybe.of);


// Exercise 5
// ==========
// Write a function that will getPost then toUpperCase the post's title

// getPost :: Int -> Task({id: Int, title: String})
var getPost = function (i) {
return new Task(function(rej, res) {
setTimeout(function(){
res({id: i, title: 'Love them futures'})
}, 300)
});
}

var upperTitle = _.compose(toUpperCase, _.prop('title'))
var ex5 = _.compose(_.map(upperTitle), getPost)



// Exercise 6
// ==========
// Write a function that uses checkActive() and showWelcome() to grant access or return the error

var showWelcome = _.compose(_.add( "Welcome "), _.prop('name'))

var checkActive = function(user) {
return user.active ? Right.of(user) : Left.of('Your account is not active')
}

var ex6 = _.compose(_.map(showWelcome), checkActive)



// Exercise 7
// ==========
// Write a validation function that checks for a length > 3. It should return Right(x) if it is greater than 3 and Left("You need > 3") otherwise

var ex7 = function(x) {
return x.length > 3 ? Right.of(x) : Left.of("You need > 3");
}



// Exercise 8
// ==========
// Use ex7 above and Either as a functor to save the user if they are valid or return the error message string. Remember either's two arguments must return the same type.

var save = function(x){
return new IO(function(){
console.log("SAVED USER!");
return x + '-saved';
});
}

var ex8 = _.compose(either(IO.of, save), ex7)

module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, ex5: ex5, ex6: ex6, ex7: ex7, ex8: ex8}
46 changes: 46 additions & 0 deletions code/part2_exercises/answers/functors/functor_exercises_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require('../../support');
var E = require('./functor_exercises');
var assert = require("chai").assert

describe("Functor Exercises", function(){

it('Exercise 1', function(){
assert.deepEqual(Identity.of(3), E.ex1(Identity.of(2)));
});

it('Exercise 2', function(){
var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
assert.deepEqual(Identity.of('do'), E.ex2(xs));
});

it('Exercise 3', function(){
var user = { id: 2, name: "Albert" };
assert.deepEqual(Maybe.of('A'), E.ex3(user));
});

it('Exercise 4', function(){
assert.deepEqual(Maybe.of(4), E.ex4("4"));
});

it('Exercise 5', function(done){
E.ex5(13).fork(console.log, function(res){
assert.deepEqual('LOVE THEM FUTURES', res)
done();
})
});

it('Exercise 6', function(){
assert.deepEqual(Left.of('Your account is not active'), E.ex6({active: false, name: 'Gary'}))
assert.deepEqual(Right.of('Welcome Theresa'), E.ex6({active: true, name: 'Theresa'}))
});

it('Exercise 7', function(){
assert.deepEqual(Right.of("fpguy99"), E.ex7("fpguy99"))
assert.deepEqual(Left.of("You need > 3"), E.ex7("..."))
});

it('Exercise 8', function(){
assert.deepEqual("fpguy99-saved", E.ex8("fpguy99").unsafePerformIO())
assert.deepEqual("You need > 3", E.ex8("...").unsafePerformIO())
});
});
98 changes: 98 additions & 0 deletions code/part2_exercises/exercises/functors/functor_exercises.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
require('../../support');
var Task = require('data.task');
var _ = require('ramda');

// Exercise 1
// ==========
// Use _.add(x,y) and _.map(f,x) to make a function that increments a value inside a functor

var ex1 = undefined



//Exercise 2
// ==========
// Use _.head to get the first element of the list
var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);

var ex2 = undefined



// Exercise 3
// ==========
// Use safeProp and _.head to find the first initial of the user
var safeProp = _.curry(function (x, o) { return Maybe.of(o[x]); });

var user = { id: 2, name: "Albert" };

var ex3 = undefined


// Exercise 4
// ==========
// Use Maybe to rewrite ex4 without an if statement

var ex4 = function (n) {
if (n) { return parseInt(n); }
};

var ex4 = undefined



// Exercise 5
// ==========
// Write a function that will getPost then toUpperCase the post's title

// getPost :: Int -> Future({id: Int, title: String})
var getPost = function (i) {
return new Task(function(rej, res) {
setTimeout(function(){
res({id: i, title: 'Love them futures'})
}, 300)
});
}

var ex5 = undefined



// Exercise 6
// ==========
// Write a function that uses checkActive() and showWelcome() to grant access or return the error

var showWelcome = _.compose(_.add( "Welcome "), _.prop('name'))

var checkActive = function(user) {
return user.active ? Right.of(user) : Left.of('Your account is not active')
}

var ex6 = undefined



// Exercise 7
// ==========
// Write a validation function that checks for a length > 3. It should return Right(x) if it is greater than 3 and Left("You need > 3") otherwise

var ex7 = function(x) {
return undefined // <--- write me. (don't be pointfree)
}



// Exercise 8
// ==========
// Use ex7 above and Either as a functor to save the user if they are valid or return the error message string. Remember either's two arguments must return the same type.

var save = function(x){
return new IO(function(){
console.log("SAVED USER!");
return x + '-saved';
});
}

var ex8 = undefined

module.exports = {ex1: ex1, ex2: ex2, ex3: ex3, ex4: ex4, ex5: ex5, ex6: ex6, ex7: ex7, ex8: ex8}
46 changes: 46 additions & 0 deletions code/part2_exercises/exercises/functors/functor_exercises_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require('../../support');
var E = require('./functor_exercises');
var assert = require("chai").assert

describe("Functor Exercises", function(){

it('Exercise 1', function(){
assert.deepEqual(Identity.of(3), E.ex1(Identity.of(2)));
});

it('Exercise 2', function(){
var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);
assert.deepEqual(Identity.of('do'), E.ex2(xs));
});

it('Exercise 3', function(){
var user = { id: 2, name: "Albert" };
assert.deepEqual(Maybe.of('A'), E.ex3(user));
});

it('Exercise 4', function(){
assert.deepEqual(Maybe.of(4), E.ex4("4"));
});

it('Exercise 5', function(done){
E.ex5(13).fork(console.log, function(res){
assert.deepEqual('LOVE THEM FUTURES', res)
done();
})
});

it('Exercise 6', function(){
assert.deepEqual(Left.of('Your account is not active'), E.ex6({active: false, name: 'Gary'}))
assert.deepEqual(Right.of('Welcome Theresa'), E.ex6({active: true, name: 'Theresa'}))
});

it('Exercise 7', function(){
assert.deepEqual(Right.of("fpguy99"), E.ex7("fpguy99"))
assert.deepEqual(Left.of("You need > 3"), E.ex7("..."))
});

it('Exercise 8', function(){
assert.deepEqual("fpguy99-saved", E.ex8("fpguy99").unsafePerformIO())
assert.deepEqual("You need > 3", E.ex8("...").unsafePerformIO())
});
});
27 changes: 27 additions & 0 deletions code/part2_exercises/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "MAG_Part2_Exercises",
"version": "0.0.1",
"description": "Exercises for Part 2 of the Book",
"main": "index.js",
"dependencies": {
"chai": "^1.9.1",
"data.task": "^3.0.0",
"ramda": "^0.13.0"
},
"devDependencies": {
"mocha": "^1.17.1"
},
"scripts": {
"test": "mocha exercises/**/*_spec.js"
},
"repository": {
"type": "git",
"url": "https://github.com/DrBoolean/mostly-adequate-guide"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/DrBoolean/mostly-adequate-guide/issues"
},
"homepage": "https://github.com/DrBoolean/mostly-adequate-guide"
}
Loading

0 comments on commit aba16d0

Please sign in to comment.