Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FP function generation (closes #253) #393

Merged
merged 12 commits into from Feb 27, 2017
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
86 changes: 60 additions & 26 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,66 @@ This change log follows the format documented in [Keep a CHANGELOG].

## [Unreleased]

### Added

- FP functions like those in [lodash](https://github.com/lodash/lodash/wiki/FP-Guide),
that support [currying](https://en.wikipedia.org/wiki/Currying), and, as a consequence,
functional-style [function composing](https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba)).

Each non-FP function has two FP counterparts: one that has [Options](docs/Options) object as its first argument
and one that hasn't. The name of the former has `WithOptions` added to the end of its name.

In FP functions, the order of arguments is reversed.

See [FP Guide](docs/fp) for more information.

```javascript
import addYears from 'date-fns/fp/addYears'
import formatWithOptions from 'date-fns/fp/formatWithOptions'
import eoLocale from 'date-fns/locale/eo'

// If FP function has not recieved enough arguments, it returns another function
const addFiveYears = addYears(5)

// Several arguments can be curried at once
const dateToString = formatWithOptions({locale: eoLocale}, 'D MMMM YYYY')

const dates = [
new Date(2017, 0 /* Jan */, 1),
new Date(2017, 1 /* Feb */, 11),
new Date(2017, 6 /* Jul */, 2)
]

const formattedDates = dates.map((date) => dateToString(addFiveYears(date)))
//=> ['1 januaro 2022', '11 februaro 2022', '2 julio 2022']
```

- Added support for [ECMAScript Modules](http://www.ecma-international.org/ecma-262/6.0/#sec-modules)
via `'date-fns/esm'` subpackage.

It allows usage with bundlers that support tree-shaking,
like [rollup.js](http://rollupjs.org) and [webpack](https://webpack.js.org):

```javascript
// Without tree-shaking:
import format from 'date-fns/format'
import parse from 'date-fns/parse'

// With tree-shaking:
import {format, parse} from 'date-fns/esm'
```

Also, as `'date-fns/esm'` function submodules provide default export,
they can be used with TypeScript to import functions in more idiomatic way:

```typescript
// In TypeScript,
import * as format from 'date-fns/format'

// is same as:
import format from 'date-fns/esm/format'
```

### Changed

- **BREAKING**: min and max functions now accept an array of dates
Expand Down Expand Up @@ -239,32 +299,6 @@ rather than spread arguments.
for consistency and future features.
See [docs/Options.js](https://github.com/date-fns/date-fns/blob/master/docs/Options.js)

- Added support for [ECMAScript Modules](http://www.ecma-international.org/ecma-262/6.0/#sec-modules)
via `'date-fns/esm'` subpackage.

It allows usage with bundlers that support tree-shaking,
like [rollup.js](http://rollupjs.org) and [webpack](https://webpack.js.org):

```javascript
// Without tree-shaking:
import format from 'date-fns/format'
import parse from 'date-fns/parse'

// With tree-shaking:
import {format, parse} from 'date-fns/esm'
```

Also, as `'date-fns/esm'` function submodules provide default export,
they can be used with TypeScript to import functions in more idiomatic way:

```typescript
// In TypeScript,
import * as format from 'date-fns/format'

// is same as:
import format from 'date-fns/esm/format'
```

## [1.27.2] - 2017-02-01

### Fixed
Expand Down
70 changes: 70 additions & 0 deletions docs/fp.md
@@ -0,0 +1,70 @@
# FP Guide

**date-fns** v2.x provides [functional programming](https://en.wikipedia.org/wiki/Functional_programming) (FP)
friendly functions, like those in [lodash](https://github.com/lodash/lodash/wiki/FP-Guide),
that support [currying](https://en.wikipedia.org/wiki/Currying).

## Table of Contents

- [Usage](#usage)

- [Using Function Composition](#using-function-composition)

## Usage

FP functions are provided via `'date-fns/fp'` submodule.

Each non-FP function has two FP counterparts: one that has [Options](docs/Options) object as its first argument
and one that hasn't. The name of the former has `WithOptions` added to the end of its name.

In **date-fns'** FP functions, the order of arguments is reversed.

```javascript
import addYears from 'date-fns/fp/addYears'
import formatWithOptions from 'date-fns/fp/formatWithOptions'
import eoLocale from 'date-fns/locale/eo'
import toUpper from 'lodash/fp/toUpper' // 'date-fns/fp' is compatible with 'lodash/fp'!

// If FP function has not recieved enough arguments, it returns another function
const addFiveYears = addYears(5)

// Several arguments can be curried at once
const dateToString = formatWithOptions({locale: eoLocale}, 'D MMMM YYYY')

const dates = [
new Date(2017, 0 /* Jan */, 1),
new Date(2017, 1 /* Feb */, 11),
new Date(2017, 6 /* Jul */, 2)
]

const formattedDates = dates.map(addFiveYears).map(dateToString).map(toUpper)
//=> ['1 JANUARO 2022', '11 FEBRUARO 2022', '2 JULIO 2022']
```

## Using Function Composition

The main advantage of FP functions is support of functional-style
[function composing](https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba).

In the example above, you can compose `addFiveYears`, `dateToString` and `toUpper` into a single function:

```javascript
const formattedDates = dates.map((date) => toUpper(dateToString(addFiveYears(date))))
```

Or you can use `compose` function provided by [lodash](https://lodash.com) to do the same in more idiomatic way:

```javascript
import compose from 'lodash/fp/compose'

const formattedDates = dates.map(compose(toUpper, dateToString, addFiveYears))
```

Or if you prefer natural direction of composing (as opposed to the computationally correct order),
you can use lodash' `flow` instead:

```javascript
import flow from 'lodash/fp/flow'

const formattedDates = dates.map(flow(addFiveYears, dateToString, toUpper))
```
8 changes: 8 additions & 0 deletions docs/index.js
Expand Up @@ -54,6 +54,14 @@ export default {
description: 'Internationalization',
path: path.join(__dirname, 'i18n.md')
},
{
type: 'markdown',
urlId: 'FP-Guide',
category: 'General',
title: 'FP Guide',
description: 'Curried functions',
path: path.join(__dirname, 'fp.md')
},
{
type: 'markdown',
urlId: 'License',
Expand Down
2 changes: 1 addition & 1 deletion examples/browserify/README.md
@@ -1,6 +1,6 @@
# Usage With Browserify

See [example.js](./example.js) and [misc.js](./misc.js) for source code examples.
See [example.js](./example.js), [fp.js](./fp.js) and [misc.js](./misc.js) for source code examples.

See [package.json scripts](./package.json) for CLI usage.

Expand Down
19 changes: 19 additions & 0 deletions examples/browserify/fp.js
@@ -0,0 +1,19 @@
var addYears = require('date-fns/fp/addYears')
var dateFns = require('date-fns/fp')
var formatWithOptions = dateFns.formatWithOptions
var eoLocale = require('date-fns/locale/eo')

var addFiveYears = addYears(5)
var dateToString = formatWithOptions({locale: eoLocale}, 'D MMMM YYYY')

var dates = [
new Date(2017, 0 /* Jan */, 1),
new Date(2017, 1 /* Feb */, 11),
new Date(2017, 6 /* Jul */, 2)
]

var formattedDates = dates
.map((date) => dateToString(addFiveYears(date)))
.join(', ')

console.log(formattedDates === '1 januaro 2022, 11 februaro 2022, 2 julio 2022')
9 changes: 6 additions & 3 deletions examples/browserify/package.json
Expand Up @@ -14,15 +14,18 @@
"scripts": {
"build": "yarn run build-date-fns && yarn run build-browserify && yarn run build-babili",
"build-date-fns": "env PACKAGE_OUTPUT_PATH=\"$(pwd)/node_modules/date-fns\" ../../scripts/build_package.sh",
"build-browserify": "yarn run build-browserify-example && yarn run build-browserify-misc",
"build-browserify": "yarn run build-browserify-example && yarn run build-browserify-fp && yarn run build-browserify-misc",
"build-browserify-example": "mkdir -p dist && browserify example.js -o dist/example.js",
"build-browserify-fp": "mkdir -p dist && browserify fp.js -o dist/fp.js",
"build-browserify-misc": "mkdir -p dist && browserify misc.js -o dist/misc.js",
"build-babili": "yarn run build-babili-example && yarn run build-babili-misc && yarn run stats-size",
"build-babili": "yarn run build-babili-example && yarn run build-babili-fp && yarn run build-babili-misc && yarn run stats-size",
"build-babili-example": "babili dist/example.js --out-file dist/example.min.js --minified --no-comments",
"build-babili-fp": "babili dist/fp.js --out-file dist/fp.min.js --minified --no-comments",
"build-babili-misc": "babili dist/misc.js --out-file dist/misc.min.js --minified --no-comments",
"stats-size": "gzip-size dist/example.min.js | pretty-bytes",
"test": "yarn run test-example && yarn run test-misc",
"test": "yarn run test-example && yarn run test-fp && yarn run test-misc",
"test-example": "test $(env TZ=UTC node ./dist/example.min.js) = true",
"test-fp": "test $(env TZ=UTC node ./dist/fp.min.js) = true",
"test-misc": "test $(env TZ=UTC node ./dist/misc.min.js) = true"
}
}
2 changes: 1 addition & 1 deletion examples/flow/README.md
@@ -1,6 +1,6 @@
# Usage With webpack 1.x

See [example.js.flow](./example.js.flow) and [misc.js.flow](./misc.js.flow) for source code examples.
See [example.js.flow](./example.js.flow), [fp.js.flow](./fp.js.flow) and [misc.js.flow](./misc.js.flow) for source code examples.

See [package.json scripts](./package.json) for CLI usage.

Expand Down
19 changes: 19 additions & 0 deletions examples/flow/fp.js.flow
@@ -0,0 +1,19 @@
var addYears = require('date-fns/fp/addYears')
var dateFns = require('date-fns/fp')
var formatWithOptions = dateFns.formatWithOptions
var eoLocale = require('date-fns/locale/eo')

const addFiveYears = addYears(5)
const dateToString = formatWithOptions({locale: eoLocale}, 'D MMMM YYYY')

const dates : Date[] = [
new Date(2017, 0 /* Jan */, 1),
new Date(2017, 1 /* Feb */, 11),
new Date(2017, 6 /* Jul */, 2)
]

const formattedDates : string = dates
.map((date) => dateToString(addFiveYears(date)))
.join(', ')

console.log(formattedDates === '1 januaro 2022, 11 februaro 2022, 2 julio 2022')
6 changes: 4 additions & 2 deletions examples/flow/package.json
Expand Up @@ -14,11 +14,13 @@
"build": "yarn run build-date-fns && yarn run flow-check && yarn run build-babel",
"flow-check": "flow check",
"build-date-fns": "env PACKAGE_OUTPUT_PATH=\"$(pwd)/node_modules/date-fns\" ../../scripts/build_package.sh",
"build-babel": "yarn run build-babel-example && yarn run build-babel-misc",
"build-babel": "yarn run build-babel-example && yarn run build-babel-fp && yarn run build-babel-misc",
"build-babel-example": "mkdir -p dist && babel example.js.flow --out-file dist/example.js",
"build-babel-fp": "mkdir -p dist && babel fp.js.flow --out-file dist/fp.js",
"build-babel-misc": "mkdir -p dist && babel misc.js.flow --out-file dist/misc.js",
"test": "yarn run test-example && yarn run test-misc",
"test": "yarn run test-example && yarn run test-fp && yarn run test-misc",
"test-example": "test $(env TZ=UTC node ./dist/example.js) = true",
"test-fp": "test $(env TZ=UTC node ./dist/fp.js) = true",
"test-misc": "test $(env TZ=UTC node ./dist/misc.js) = true"
}
}
3 changes: 3 additions & 0 deletions examples/lodash-fp/.babelrc
@@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}
23 changes: 23 additions & 0 deletions examples/lodash-fp/README.md
@@ -0,0 +1,23 @@
# Usage With lodash FP

See [example.js](./example.js) for source code example.

See [package.json scripts](./package.json) for CLI usage.

## Build Example

```sh
yarn
yarn run build
```

See ./dist for output.

## Minimal Build Size

You can see the build size:

```sh
gzip-size dist/example.min.js | pretty-bytes
#=> 20.1 kB
```
16 changes: 16 additions & 0 deletions examples/lodash-fp/example.js
@@ -0,0 +1,16 @@
import addYears from 'date-fns/fp/addYears'
import formatWithOptions from 'date-fns/fp/formatWithOptions'
import eoLocale from 'date-fns/locale/eo'

import compose from 'lodash/fp/compose'
import toUpper from 'lodash/fp/toUpper'
import isEqual from 'lodash/isEqual'

const addFiveYears = addYears(5)
const dateToString = formatWithOptions({locale: eoLocale}, 'D MMMM YYYY')

const dates = ['2017-01-01', '2017-02-11', '2017-07-02']

const formattedDates = dates.map(compose(toUpper, dateToString, addFiveYears))

console.log(isEqual(formattedDates, ['1 JANUARO 2022', '11 FEBRUARO 2022', '2 JULIO 2022']))
26 changes: 26 additions & 0 deletions examples/lodash-fp/package.json
@@ -0,0 +1,26 @@
{
"name": "date-fns-example-babel",
"version": "0.1.0",
"description": "Example of date-fns usage with webpack 1.x, babel and lodash",
"main": "example.js",
"author": "Sasha Koss <koss@nocorp.me>",
"license": "MIT",
"dependencies": {
"babel-core": "^6.22.1",
"babel-preset-babili": "^0.0.11",
"babel-preset-es2015": "^6.22.0",
"babili": "^0.0.11",
"gzip-size-cli": "^1.0.0",
"lodash": "^4.17.4",
"pretty-bytes-cli": "^2.0.0",
"webpack": "1"
},
"scripts": {
"build": "yarn run build-date-fns && yarn run build-webpack && yarn run build-babili",
"build-date-fns": "env PACKAGE_OUTPUT_PATH=\"$(pwd)/node_modules/date-fns\" ../../scripts/build_package.sh",
"build-webpack": "webpack",
"build-babili": "babili dist/example.js --out-file dist/example.min.js --minified --no-comments && yarn run stats-size",
"stats-size": "gzip-size dist/example.min.js | pretty-bytes",
"test": "test $(env TZ=UTC node ./dist/example.min.js) = true"
}
}
14 changes: 14 additions & 0 deletions examples/lodash-fp/webpack.config.js
@@ -0,0 +1,14 @@
module.exports = {
entry: './example.js',
output: {
path: './dist',
filename: 'example.js'
},
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}]
}
}