Practical

Functional
Programming

 

  • Why FP

  • Benefit

Won't talk about

Difficulty

  • 看不懂
  • Some magic inside

 

Isomorphism

Mathematics
of Programming

同构——编程中的数学:  https://github.com/liuxinyu95/unplugged

Reading

  • Lambda calculuc

  • Combinatory logic

  • Category theory

$$(\lambda x. + x  y)$$

函数的自变量是x,将x与y相加

定义一个函数

形参

函数体 (前缀表达式)

x \mapsto x + y
// JS, TypeScript, Scala
(x, y) => x + y

// Java
(x, y) -> x + y

// Haskell
\x y -> x + y

// Python
lambda x, y : x + y

// Clojure
(fn [x y] (+ x y))

// Rust
|x, y| x + y

Lambda

in different

programming

languages

$$(\lambda x. + x  y)  2$$

将函数应用(apply)到参数2

自由变量(open binding)

实参

被绑定(bind)的变量

函数

Closure

A function "remembers" its lexical scope even when the function is executed outside that lexical scope.

A lambda whose open bindings (free variables) have been closed by (bound in) the lexical environment.

Lambda calculus

  • α-conversion

  • β-reduction

  • η-conversion

A curried function

is a function that

takes multiple arguments one at a time

// normal
const add = (a, b) => a + b;
const result = add(2, 3);

// curried
const add = a => b => a + b;
const result = add(2)(3);
const toSeconds = h => m => s => (h*60 + m)*60 + s;
// ===>
const toSeconds = 
        h =>        // return a function with single parameter m
          ( m =>    // return a function with single parameter s
            ( s => (h*60 + m)*60 + s )
          )


const [hour, min, sec] = [7, 23, 48];
const ss = toSeconds(hour)(min)(sec);
// ===>
const toSecWithBoundHour = toSeconds(hour);            // partial application
const toSecWithBoundHourMin = toSecWithBoundHour(min); // partial application
const ss = toSecWithBoundHourMin(sec);

A partial application

is a function which has been applied to some,

but not yet all of its arguments

Has some arguments fixed inside its closure scope

Encapsulation in FP

by partial application 

// curried function
const runCmd = root => cmd => api => {
    const cwd = path.resolve(root, api.apiPath, api.majorVersion);
    const result = shell.exec(cmd, { cwd, env });
    return result.code;
};

const rootPath = argv["root-path"];
const cmd = argv._.join(' ');

const run = runCmd(rootPath)(cmd);  // capture two variables

apiRepo.apiList(rootPath)
    .then(... process the api list ...)
    .then(_.map(run))               // apply the closure to each API
    .then(success => {
        if (success) {
            process.exit(0);
        } else {
            console.log('runforeach got at least one failure')
            process.exit(1);
        }
    })

Function composition

combination of two or more functions to create a new function

Function composition

function remoteGet(url){
  return httpGet(encode(preformat(trim(url))));
}

// becomes

const remoteGet = compose(
  httpGet,
  encode,
  preformat,
  trim
);

remoteGet(url1);
remoteGet(url2);
const remoteGet = pipe(
  trim,
  preformat,
  encode,
  httpGet,
);

remoteGet(url1);
remoteGet(url2);
  • flowRight, compose
  • flow, pipe

Compose

    const splitLines = 
      _.pipe([
        _.split('\n'),              // _.split(separater, string) => string[]
        _.map(_.trim),              // _.trim(string) => string
        _.filter(_.isEmpty),        // _.filter(predicate, string[])
      ]);

    const lines = splitLines(fileContent);

Point-free style

is a way of defining a function

without reference to its arguments

const sum = _.reduce(_.add, 0);

sum([1,2,3]);  ==> 1+2+3=6
function sum(arg1) {
  // ...
}

const foo = (arg1) => // ...

pointful

point-free

η-conversion

x \mapsto F \space x \quad \xleftrightarrow{\eta} \quad F
lines.map(line => trim(line));


lines.map(trim);

Point-free or pointful?

const getAdminEmail = 
  _.pipe(
    _.filter(_.pipe(
      _.property('role'),
      _.eq('admin')
    )),
    _.map(_.property('contact.email')),
  )
const mapName = 
  _.map(owner => 
    (owner.type === 'group')
      ? owner.name + owner.contact.name
      : 'user-' + owner.name
  )

pointful

point-free

Level

advanced

// {scheme}://{host}/foo/v1 ====> foo/v1
const convert = _.flow([
  _.split('/'),
  (segments) => {
    const idx = _.findLastIndex(containAny(['{', '}', ':']))(segments);
    return _.drop(idx+1, segments);
  },
  _.reject(_.isEmpty),
  _.join('/'),
]);

origin

const f =
  (segments: string[]) =>
    _.drop(
      _.findLastIndex(containAny(['{', '}', ':']))(segments) + 1, 
      segments
    );

step 1

const indexAfterLastFound = _.flow(
  _.findLastIndex(containAny(['{', '}', ':'])),
  _.add(1)
);

const f =
  (segments: string[]) =>
    _.drop(
      indexAfterLastFound(segments), 
      segments
    );

step 2

const indexAfterLastFound = _.flow(
  _.findLastIndex(containAny(['{', '}', ':'])),
  _.add(1)
);

const f =
  (segments: string[]) =>
    _.drop(
      indexAfterLastFound(segments), 
      segments
    );

step 2

const indexAfterLastFound = _.flow(
  _.findLastIndex(containAny(['{', '}', ':'])),
  _.add(1)
);

// chain combinator
const chain = f => g => x => f(g(x))(x);

const convert = _.flow([
  _.split('/'),
  chain(_.drop)(indexAfterLastFound),
  _.reject(_.isEmpty),
  _.join('/'),
]);

final

\begin{aligned} &g: a \rightarrow b \\ &f: b \rightarrow a \rightarrow c \end{aligned}
\begin{aligned} & g(a) \\ &\Downarrow \\ f&(b,a) \Rightarrow c \end{aligned}

A combinator

is a higher-order function that

combine 2 or more functions into

a more complex function

函数式编程中的组合子:  https://segmentfault.com/a/1190000016803036

Reading

Tap combinator

// (a → b) → a → a
const tap = 
  f => x => (f(x), x)

Converge combinator

// (b → c → d) → (a → b) → (a → c) → a → d
const S2 = 
  f => g => h => x => 
    f( g(x) )( h(x) )

fork & join

  • g: forker 1
  • h: forker 2
  • f: join

Level

advanced

Common used combinators

  • Common combinators:   https://gist.github.com/Avaq/1f0636ec5c8d6aed2e45
  • Combinator library:         https://github.com/fantasyland/fantasy-birds

Reading

Level

advanced

How to learn

functional programming?

Learn a pure FP language

For example:

  • Haskell language
  • 99 problems in Haskell

Pure functional

  • Immutable data structure
  • No side-effect

Writing code

in functional style

in your daily work

... practically

Step by Step

writing code in functional style

Be familiar with common

utility functions

&

high order functions

Level

basic

Utility functions for manipulating data set

  • concat
  • head
  • tail
  • join

Higher order functions

(combinators)

  • filter
  • forEach
  • sortBy
  • groupBy

JavaScript:     lodash, lodash/fp

Java:               jOOλ, Functional Java, vavr (?)

  • map
  • flatMap
  • reduce
  • ...
  • zip
  • union
  • ...

Level

basic

map

// (a → b) → a[] → b[]
const newArr =
  map(transformer)(arr)

Level

basic

fold / reduce

// ((b,a) → b) → b → a[] → b
const  =
  foldr(reducer)(init)(arr)

Level

basic

Make variable immutable

  let x = expression

Level

basic

const x = expression

Why do you modify a variable?

branching

Why do you modify a variable?

let data;

if (extSysIntegrated) {
  data = fetchRemote();
} else {
  data = loadFromDb();
}

process(data);

ternary operator

Why do you modify a variable?  > Branching

// JavaScript

let data;

if (extSysIntegrated) {
  data = fetchRemote();
} else {
  data = loadFromDb();
}

process(data);
// const data = 
//   if (extSysIntegrated) {
//     fetchRemote(); 
//   } else {
//     loadFromDb();
//   }

const data = 
  extSysIntegrated 
  ? fetchRemote() 
  : loadFromDb();

process(data);

Level

basic

switch expression

Why do you modify a variable?  > Branching

// Java 14

final int ndays = switch(month) {
    case JAN, MAR, MAY, JUL, AUG, OCT, DEC -> 31;
    case APR, JUN, SEP, NOV -> 30;
    case FEB -> {
        if (year % 400 ==0) yield 29;
        else if (year % 100 == 0) yield 28;
        else if (year % 4 ==0) yield 29;
        else yield 28; 
    }
};

Level

basic

Use immutable
data structures

Level

advanced

      const arr = [1,2];
      arr.push(3);
    const arr = [1,2];
    const newArr = _.concat(arr, 3);

loop

Why do you modify a variable?

const list2object = (list) => {
  const result = {};
  list.forEach(item => {
    result[item.id] = item;
  });
  return result;
};

Use a state variable inside the loop

to accumulate the result

forEach, map

Why do you modify a variable?  > Loop

Level

basic

Remove the loop variable

// run a function 100 times
_.forEach(doSomething)(_.range(100))

// convert a list
const output = _.map((item) => {
  // process the item and return the new value
})(input);

fold, reduce

Why do you modify a variable?  > Loop

Level

basic

Remove the state variable;

pass context between iterations via memo parameter

// reducer: (memo, value) => newMemo
const maxMin = ([max, min], value) => {
  const newMax = value > max ? value : max;
  const newMin = value < min ? value : min;
  return [newMax, newMin];
}

const input = [3, 5, 2, 2, 9, 8];

// reduce(reducer, init, list)
const [max, min] = 
  _.reduce(
    maxMin, 
    [input[0], input[0]],
    input
  );
// ===> [9, 2]
  function metrics2Lines(input: Metrics[]): string[] {
    const result: string[] = [];
    _.forEach(input, (metrics) => {
      // Measurement
      let oneLine = `${metrics.name}`;
      // Tag set
      _.forEach(metrics.tags, (value, key) => { oneLine += `,${key}=${value}`; });
      // Field set
      let tagStr = '';
      _.forEach(metrics.fields, (value, key) => { tagStr += `,${key}=${value}`; });
      oneLine += ` ` + tagStr.slice(1, tagStr.length);
      // Timestamp
      oneLine += ` ${metrics.timestamp ? metrics.timestamp * 1000000 : Date.now() * 1000000}`;
      // New line
      result.push(oneLine);
    });
    return result;
  }

Input array, output array map

Immutable: append multiple times  join at the end

function kvPairsToStr(pair) {
  return _.map(pair, (value, key) => `${key}=${value}`);
}

// name,tag1=value1,tag2=value1 field1="foo",field2="bar" 1465839830100400200
//                             |                         |
//                           1st space             2nd space
                           
function metrisToLine(input) {
  return _.map(
    input,
    (metrics: Metrics) => {
      const tagSet = kvPairsToStr(metrics.tags);
      const fieldSet = kvPairsToStr(metrics.fields);
      const timestamp = 1000000 * (metrics.timestamp ?? Date.now());
      const measurementAndTags = _.join(',', [metrics.name, tagSet]);
      return _.join(' ', [measurementAndTags, fieldSet, `${timestamp}`]);
    }
  );
};

Create generic function and reuse by

currying and partial application

$$ (a, b, c) \rightarrow d $$

may always be replaced with

$$ a \rightarrow b \rightarrow c \rightarrow d $$

  • Write function in currying
  • Auto-currying
    • JS: lodash/fp
  • Curry combintor
public <A, B, C> Function<A, Function<B, C>> curry(final BiFunction<A, B, C> f) {
  return (A a) -> (B b) -> f.apply(a, b);
}

Level

advanced

function removeUndefinedOrNull(source) {
    return _.pickBy(source,
        (val) => ((val !== undefined) && (val != null)));
}

function removeUndefinedOrNull(source) {
    return _.pickBy(source,
        (val) => !_.isNil(val);
}

// arrow function notation
const removeUndefinedOrNull = source => _.pickBy(source, (val) => !_.isNil(val));

const removeUndefinedOrNull = source => _.omitBy(source, (val) => _.isNil(val));

// beta reduction
const removeUndefinedOrNull = source => _.omitBy(source, _.isNil);

// currying and partial application
// curryRight(_.omitBy) ====> omitBy(predicate)(obj)
const removeUndefinedOrNull = _.curryRight(_.omitBy)(_.isNil);
x \mapsto F \space x \quad \xleftrightarrow{\eta} \quad F
function kvPairsToStr(pair) {
  return _.map(pair, (value, key) => `${key}=${value}`);
}
                           
function metrisToLine(input) {
  return _.map(
    input,
    (metrics: Metrics) => {
      const tagSet = kvPairsToStr(metrics.tags);
      const fieldSet = kvPairsToStr(metrics.fields);
      const timestamp = 1000000 * (metrics.timestamp ?? Date.now());
      const measurementAndTags = _.join(',', [metrics.name, tagSet]);
      return _.join(' ', [measurementAndTags, fieldSet, `${timestamp}`]);
    }
  );
};

lodash/fp: auto currying

_.map(fn)(arr)
const kvPairsToStr = _.map((value, key) => `${key}=${value}`));
                           
const metrisToLine = _.map(
    (metrics: Metrics) => {
      const tagSet = kvPairsToStr(metrics.tags);
      const fieldSet = kvPairsToStr(metrics.fields);
      const timestamp = 1000000 * (metrics.timestamp ?? Date.now());
      const measurementAndTags = _.join(',', [metrics.name, tagSet]);
      return _.join(' ', [measurementAndTags, fieldSet, `${timestamp}`]);
    }
  );

partial application of _.map

Thinking functionally

Thinking functionally

Thinking functionally

Your code should

tell a story

Thinking functionally

  •   通过函数来重用

  •   通过包/模块scope来隔离

  •   通过闭包来封装

  •   通过partial application来实现builder模式

Stream processing of data

Thinking functionally

Pipeline operation

JavaScript TC39 stage 2

const name = "charles"

const greet = name => `Hello, ${name}`
const capitalize = str => str.toUpperCase()
const exclaim = str => `${str}!!!`

const loudGreeting = name 
                      |> greet
                      |> capitalize
                      |> exclaim

console.log(loudGreeting) // HELLO, CHARLES!!!
expression >| function

Functional

Reactive Programming

Introduction to RP:  https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

Reading

Thanks!

Happy Coding

Practical Functional Programming

By Leo Liang

Practical Functional Programming

  • 144