Stylish JavaScript with point-free style

Miล‚osz Piechocki

@miloszpp

Why?

  • You're already using it (probably)
  • Functional Programming best-practice
  • Pipeline operator is coming

What?


          const mountains = [
            'Mount Everest',
            'Mont Blanc',
            'Kazbek',
          ];
        

          mountains.map(mountain => toLower(mountain));
        

Welcome to a point-free world!


          mountains.map(toLower);
        

Point-free style

Never mention the data!

          mountains.map(mountain => toLower(mountain));
        

Point-free toolbox

๐Ÿ› ๏ธ

Partial application

โš™๏ธ

Example


            const numbers = [ 1, 2, 3, 4, 5];

            function multiply(a, b) { return a * b; }
        

            numbers.map(x => multiply(2, x));
        

            numbers.map(multiply(2));
        

Every function is
a single-argument function!

Haskell Curry

Currying


            const multiply = (a) => {
              return (b) => {
                return a * b;
              }
            };
        

            const multiply = a => b => a * b;
        

            numbers.map(multiply(2));
        

Currying with Ramda


            import {curry} from 'ramda';

            const multiply = curry((a, b) => a * b);

            numbers.map(multiply(2));
        

Curried fetch


            const postIds = [ 1, 2, 3, 4, 5 ];

            function fetchPost(postId, secret) {
              return fetch(`https://website.com/posts/${postId}`, {
                headers: {
                  'secret-key': secret
                }
              });
            }
        

Order of parameters matters!


              const fetchPostCurried = curry(fetchPost);
              postIds.map(fetchPostCurried(user.secretKey));
          

              function fetchPost(postId, secret) {
                // ...
              }
          

              function fetchPost(secret, postId) {
                // ...
              }
          

Partial application in react-redux


            const FilterLink = connect(
              mapStateToProps,
              mapDispatchToProps
            )(Link);
          
https://redux.js.org/api/compose

Function composition

๐Ÿงฉ

Composing with Ramda


            const getIntValue = pipe(getValue, parseInt);
        

Composing data flows


            <div id="app">
                <input class="post-id" /> 
                <input class="post-id" />
                <input class="post-id" />
              </div>
            

            function limit(x) {
              return Math.max(Math.min(x, 100), 0);
            }

            function fetchPost(id) { /* ... */ }
          

            const inputs = Array.from(
              document.getElementsByClassName('post-id'));

            inputs.map(input => fetchPost(limit(parseInt(input.value))));
        

            inputs.map(input => fetchPost(limit(parseInt(input.value))));
        

vs


              inputs.map(pipe(
                prop('value'),
                parseInt,
                clamp(0, 100),
                fetchPost,
              ));
        

Pipeline operator


                inputs.map(
                  prop('value')
                  |> parseInt
                  |> limit
                  |> fetchPost
                )
					

ESNext Proposal: The Pipeline Operator

Composing operators in RxJS


            const image$ = nameInput$.pipe(
              map(() => nameInputEl.value),
              filter(name => name.length >= 3),
              debounceTime(500),
              mergeMap(name => 
                fetch(`https://dog.ceo/api/breed/${name}/images/random`)),
              mergeMap(response => response.json()),
              map(result => result.message)
            );
          

Composing middleware in Redux


            import { createStore, applyMiddleware, compose } from 'redux'
            import thunk from 'redux-thunk'
            import DevTools from './containers/DevTools'
            import reducer from '../reducers'
            โ€‹
            const store = createStore(
              reducer,
              compose(
                applyMiddleware(thunk),
                DevTools.instrument()
              )
            );
        
https://redux.js.org/api/compose

Point-free in real world

๐ŸŒŽ

Donโ€™t confuse simplicity with familiarity

Old Chineese proverb

Redux Reducers

Redux Reducers: state


          const state = {
            posts: [
              { title: 'A', content: '' },
              { title: 'B', content: '' },
              { title: 'C', content: '' }
            ],
            isLoading: false,
            paging: {
              pageNumber: 5
            }
          };
        

Regular Reducer


            const postsRequestFinished = (state, payload) => {
              return {
                ...state,
                posts: state.posts.concat(payload.posts),
                isLoading: false,
                paging: {
                  ...state.paging,
                  pageNumber: state.paging.pageNumber + 1
                }
              }
            };
        

Point-free Reducer


            const postsRequestFinished = (state, { posts }) =>
              evolve(
                {
                  posts: concat(posts),
                  isLoading: always(false),
                  paging: evolve({ pageNumber: inc })
                },
                state
              );
        

RxJS

Composing operators in RxJS


            const image$ = nameInput$.pipe(
              map(() => nameInputEl.value),
              filter(name => name.length >= 3),
              debounceTime(500),
              mergeMap(name => 
                getJSON(`https://dog.ceo/api/breed/${name}/images/random`)),
              map(result => result.message)
            );
          

Point-free RxJS


            const nameInputElValue = () => nameInputEl.value;
            const dogImageUrl = name => 
              `https://dog.ceo/api/breed/${name}/images/random`;
        

            const image$ = nameInput$.pipe(
              map(nameInputElValue),
              filter(pipe(length, gt(__, 3))),
              debounceTime(500),
              mergeMap(pipe(dogImageUrl, getJSON)),
              map(prop('message'))
            );
        

Redux Reselect

Redux Reselect: state


          const state = {
            shop: {
              items: [
                { name: 'A', value: 100 },
                { name: 'B', value: 200 },
              ],
              taxPercent: 10,
            }
          };
        

Redux Reselect: selectors


          const shopItemsSelector = state => state.shop.items;
          const taxPercentSelector = state => state.shop.taxPercent;
        

          const subtotalSelector = createSelector(
            shopItemsSelector,
            items => items.reduce((acc, item) => acc + item.value, 0)
          );
        

          const taxSelector = createSelector(
            subtotalSelector,
            taxPercentSelector,
            (subtotal, taxPercent) => subtotal * (taxPercent / 100)
          );
        

Based on code from: https://github.com/reduxjs/reselect


          const shopItemsSelector = path(['shop', 'items']);
          const taxPercentSelector = path(['shop', 'taxPercent']);
        

          const subtotalSelector = createSelector(
            shopItemsSelector,
            reduce(useWith(add, [identity, prop('value')]), 0),
          );
        

          const taxSelector = createSelector(
            subtotalSelector,
            taxPercentSelector,
            useWith(multiply, [identity, divide(__, 100)]),
            );
        

Point-less programming


            const format = (actual, expected) => {
              const variants = expected.join(โ€˜, โ€˜);
              return `Value ${actual} is not expected here. 
                Possible variants are: ${variants}`;
            }
          

          const format = converge(
            unapply(join(' ')),
            [
              always('Value'),
              nthArg(0),
              always('is not expected here. Possible variants are:'),
              compose(join(', '), nthArg(1))
            ]
          );
        

Example from: Two Years of Functional Programming in JavaScript: Lessons Learned

Tools

  • ramda
  • lodash - curry, partial
  • lodash/fp

lodash vs lodash/fp

lodash


          map(mountains, toLower);
        

lodash/fp


          fp.map(toLower)(mountains);
        
https://github.com/lodash/lodash/wiki/FP-Guide

Wrapping up...

๐ŸŒฏ

To point-free or not to point-free?

  • Use when it simplifies code
  • Many opportunities to use in modern JS
  • Pipeline operator will be a game-changer

Thank you

@miloszpp

๐Ÿ™

Want more?

codewithstyle.info/pointfree