Skip to content
This repository has been archived by the owner on Jan 26, 2022. It is now read-only.

Latest commit

 

History

History
829 lines (732 loc) · 27.3 KB

core-real-examples.md

File metadata and controls

829 lines (732 loc) · 27.3 KB

Core Proposal real-world examples

Living Document. J. S. Choi, 2018-12.

WHATWG Fetch Standard

The WHATWG Fetch Standard contains several examples of using the DOM fetch function, resolving its promises into values, then processing the values in various ways. These examples may become more easily readable with smart pipelines.

With smart pipelines Status quo
'/music/pk/altes-kamuffel'
|> await fetch(#)
|> await #.blob()
|> playBlob;
fetch('/music/pk/altes-kamuffel')
  .then(res => res.blob())
  .then(playBlob);
[Equivalent to above.]
playBlob(
  await (
    await fetch('/music/pk/altes-kamuffel')
  ).blob()
);
'https://example.com/'
|> await fetch(#, { method: 'HEAD' })
|> #.headers.get('content-type')
|> console.log;
fetch('https://example.com/',
  { method: 'HEAD' }
).then(response =>
  console.log(
    response.headers.get('content-type'))
);
'https://example.com/'
|> await fetch(#, { method: 'HEAD' })
|> #.headers.get('content-type')
|> console.log;
console.log(
  (await
    fetch('https://example.com/',
      { method: 'HEAD' }
    )
  ).headers.get('content-type')
);
[Equivalent to above.]
{
  const url = 'https://example.com/';
  const response =
    await fetch(url, { method: 'HEAD' });
  const contentType =
    response.headers.get('content-type');
  console.log(contentType);
}
'https://pk.example/berlin-calling'
|> await fetch(#, { mode: 'cors' })
|> do {
  if (#.headers.get('content-type')
    && #.headers.get('content-type')
      .toLowerCase()
      .indexOf('application/json') >= 0
   )
     return #.json();
   else
     throw new TypeError();
}
|> await #.json()
|> processJSON;

This example uses do expressions, which come from another ES proposal, and which work well with smart pipelines--in this case to embed ifelse statements.

fetch('https://pk.example/berlin-calling',
  { mode: 'cors' }
).then(response => {
  if (response.headers.get('content-type')
    && response.headers.get('content-type')
      .toLowerCase()
      .indexOf('application/json') >= 0
  )
    return response.json();
  else
    throw new TypeError();
}).then(processJSON);

jQuery

As the single most-used JavaScript library in the world, jQuery has provided an alternative human-ergonomic API to the DOM since 2006. jQuery is under the stewardship of the JS Foundation, a member organization of TC39 through which jQuery’s developers are represented in TC39. jQuery’s API requires complex data processing that becomes more readable with smart pipelines.

With smart pipelines Status quo
return data
|> buildFragment([#], context, scripts)
|> #.childNodes
|> jQuery.merge([], #);

The path that a reader’s eyes must trace while reading this pipeline moves straight down, with some movement toward the right then back: from data to buildFragment (and its arguments), then .childNodes, then jQuery.merge. Here, no one-off-variable assignment is necessary.

parsed = buildFragment(
  [ data ], context, scripts
);
return jQuery.merge(
  [], parsed.childNodes
);

From jquery/src/core/parseHTML.js. In this code, the eyes first must look for data – then upwards to parsed = buildFragment (and then back for buildFragment’s other arguments) – then down, searching for the location of the parsed variable in the next statement – then right when noticing its .childNodes postfix – then back upward to return jQuery.merge.

(key |> toType) === 'object';
key |> toType |> # === 'object';

|> has a looser precedence than most operators, including ===. (Only assignment operators, arrow function =>, yield operators, and the comma operator are any looser.)

toType(key) === 'object';

From jquery/src/core/access.js.

context = context
|> # instanceof jQuery
    ? #[0] : #;
context =
  context instanceof jQuery
    ? context[0] : context;

From jquery/src/core/access.js.

context
|> # && #.nodeType
  ? #.ownerDocument || #
  : document
|> jQuery.parseHTML(match[1], #, true)
|> jQuery.merge;
jQuery.merge(
  this, jQuery.parseHTML(
    match[1],
    context && context.nodeType
      ? context.ownerDocument
        || context
      : document,
    true
  )
);

From jquery/src/core/init.js.

match
|> context[#]
|> (this[match] |> isFunction)
  ? this[match](#);
  : this.attr(match, #);

Note how, in this version, the parallelism between the two clauses is very clear: they both share the form match |> context[#] |> something(match, #).

if (isFunction(this[match])) {
  this[match](context[match]);
} else
  this.attr(match, context[match]);
}

From jquery/src/core/init.js. Here, the parallelism between the clauses is somewhat less clear: the common expression context[match] is at the end of both clauses, at a different offset from the margin.

elem = match[2]
|> document.getElementById;
elem = document.getElementById(match[2]);

From jquery/src/core/init.js.

// Handle HTML strings
if ()
  
// Handle $(expr, $(...))
else if (!# || #.jquery)
  return context
  |> # || root
  |> #.find(selector);
// Handle $(expr, context)
else
  return context
  |> this.constructor
  |> #.find(selector);

The parallelism between the final two clauses becomes clearer here too. They both are of the form return context |> something |> #.find(selector).

// Handle HTML strings
if ()
  
// Handle $(expr, $(...))
else if (!context || context.jquery)
  return (context || root).find(selector);
// Handle $(expr, context)
else
  return this.constructor(context)
    .find(selector);

From jquery/src/core/init.js. The parallelism is much less clear here.

Underscore.js

Underscore.js is another utility library very widely used since 2009, providing numerous functions that manipulate arrays, objects, and other functions. It too has a codebase that transforms values through many expressions – a codebase whose readability would therefore benefit from smart pipelines.

With smart pipelines Status quo
function (obj, pred, context) {
  return obj
  |> isArrayLike
  |> # ? _.findIndex : _.findKey
  |> #(obj, pred, context)
  |> (# !== void 0 && # !== -1)
      ? obj[#] : undefined;
}
function (obj, pred, context) {
  var key;
  if (isArrayLike(obj)) {
    key = _.findIndex(obj, pred, context);
  } else {
    key = _.findKey(obj, pred, context);
  }
  if (key !== void 0 && key !== -1)
    return obj[key];
}
function (obj, pred, context) {
  return pred
  |> cb
  |> _.negate
  |> _.filter(obj, #, context);
}
function (obj, pred, context) {
  return _.filter(obj,
    _.negate(cb(pred)),
    context
  );
}
function (
  srcFn, boundFn, ctxt, callingCtxt, args
) {
  if (!(callingCtxt instanceof boundFn))
    return srcFn.apply(ctxt, args);
  var self = srcFn
  |> #.prototype |> baseCreate;
  return self
  |> srcFn.apply(#, args)
  |> _.isObject(#) ? # : self;
}
function (
  srcFn, boundFn,
  ctxt, callingCtxt, args
) {
  if (!(callingCtxt instanceof boundFn))
    return srcFn.apply(ctxt, args);
  var self = baseCreate(srcFn.prototype);
  var result = srcFn.apply(self, args);
  if (_.isObject(result)) return result;
  return self;
}
function (obj) {
  return obj
  |>  # == null
    ? 0
    : #|> isArrayLike
    ? #|> #.length
    : #|> _.keys |> #.length;
  };
}

Smart pipelines make parallelism between all three clauses becomes clearer:
0 if it is nullish,
#|> #.length if it is array-like, and
#|> something |> #.length otherwise.
(Technically, #|> #.length could simply be #.length, but it is written in this redundant form in order to emphasis its parallelism with the other branch.)

This particular example becomes even clearer when paired with Additional Feature BP.

function (obj) {
  if (obj == null) return 0;
  return isArrayLike(obj)
    ? obj.length
    : _.keys(obj).length;
}

Lodash

Lodash is a fork of Underscore.js that remains under rapid active development. Along with Underscore.js’ other utility functions, Lodash provides many other high-order functions that attempt to make functional programming more ergonomic. Like jQuery, Lodash is under the stewardship of the JS Foundation, a member organization of TC39, through which Lodash’s developers also have TC39 representation. And like jQuery and Underscore.js, Lodash’s API involves complex data processing that becomes more readable with smart pipelines.

With smart pipelines Status quo
function hashGet (key) {
  return this.__data__
  |> nativeCreate
    ? (#[key] === HASH_UNDEFINED
      ? undefined : #)
    : hashOwnProperty.call(#, key)
    ? #[key]
    : undefined;
}
function hashGet (key) {
  var data = this.__data__;
  if (nativeCreate) {
    var result = data[key];
    return result === HASH_UNDEFINED
      ? undefined : result;
  }
  return hasOwnProperty.call(data, key)
    ? data[key] : undefined;
}
function listCacheHas (key) {
  return this.__data__
  |> assocIndexOf(#, key)
  |> # > -1;
}
function listCacheHas (key) {
  return assocIndexOf(this.__data__, key)
    > -1;
}
function mapCacheDelete (key) {
  const result = key
  |> getMapData(this, #)
  |> #['delete']
  |> #(key);
  this.size -= result ? 1 : 0;
  return result;
}
function mapCacheDelete (key) {
  var result =
    getMapData(this, key)['delete'](key);
  this.size -= result ? 1 : 0;
  return result;
}
function castPath (value, object) {
  return value |>
    #|> isArray
    ? #
    : (#|> isKey(#, object))
    ? [#]
    : #|> toString |> stringToPath;
}
function castPath (value, object) {
  if (isArray(value)) {
    return value;
  }
  return isKey(value, object)
    ? [value]
    : stringToPath(toString(value));
}