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

Illustrations of usefulness for each function #3

Closed
js-choi opened this issue Oct 2, 2021 · 4 comments
Closed

Illustrations of usefulness for each function #3

js-choi opened this issue Oct 2, 2021 · 4 comments
Labels
documentation Improvements or additions to documentation help wanted Extra attention is needed

Comments

@js-choi
Copy link
Collaborator

js-choi commented Oct 2, 2021

The explainer is incomplete.

Each function’s section needs at least one code-block example illustrating its usefulness (for example, identity as the default value of an optional parameter). Ideally, this would be taken from real-world code (and preferably from real-world JavaScript code).

@js-choi js-choi added documentation Improvements or additions to documentation help wanted Extra attention is needed labels Oct 2, 2021
@theqp
Copy link

theqp commented Oct 9, 2021

This is from the pipe slides

console.log(
  chalk.dim(
    `$ ${Object.keys(envars)
      .map(envar => `${envar}=${envars[envar]}`)
      .join(" ")}`,
    "node",
    args.join(" ")
  )
);
pipe(
  Object.keys(envars)
    .map(envar => `${envar}=${envars[envar]}`)
    .join(" "),
  s =>
    chalk.dim(`$ ${s}`, "node", args.join(" ")),
  console.log
);

@js-choi
Copy link
Collaborator Author

js-choi commented Oct 17, 2021

@theqp: Thanks for the comment.
When I included that React example in the pipe proposal’s explainer, I did it with the intent of demonstrating a deeply nested expression that actually wasn’t amenable to a pipe function. s => chalk.dim($ ${s}, "node", args.join(" ")) is still kind of difficult to read—I would split it up further into two arrow functions (s => $ ${s}ands => chalk.dim(s, …)`). But several Committee representatives have had concerns about encouraging copious inline arrow functions in pipes (see tc39/proposal-pipeline-operator#221).

So, with this proposal, I will try to emphasize to the Committee that flow can usefully compose cached unary functions, including unary arrow functions, in a single function allocation, which can then be reused.

For this reason, I am less certain about the Committee accepting pipe in addition to flow. I would prefer real-world examples that would not involve allocations of many new inline functions, although I have been having difficulty finding such examples.


I’ve added a bunch more examples for the other functions just now, anyways.

@shuckster
Copy link

Thought I'd offer up a small example of my own which I've used for real projects -- a refactor of a small utility function where it was useful to use both pipe and flow.

(It also uses a pattern-matching library I wrote to tide me over until match makes it into the language, so maybe this isn't a great example for that reason?)

Anyway, thought I'd offer it up even if it only prompts others to share:

import { against, when, isString } from 'match-iz'
import { pipe, flow, memo } from './fp'

export const rxFromWildcard = memo((str) => {
  if (!isString(str) || !str.length) {
    throw new TypeError('Please pass a non-empty string')
  }
  return pipe(
    str
      .replace(rxConsecutiveWildcards, '*')
      .split('*')
      .map((x) => x.trim())
      .map((x) => x.replace(rxEscape, '\\$&')),

    against(
      when(hasNoWildcards)(templateMatchExact),
      when(hasNoWildcardAtStart)(flow(insertWildcards, templateMatchStart)),
      when(hasNoWildcardAtEnd)(flow(insertWildcards, templateMatchEnd))
    ),

    ($) => new RegExp($, 'i')
  )
})

//
// Helpers
//

// From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
const rxEscape = /[.*+?^${}()|[\]\\]/g

const rxConsecutiveWildcards = /\*{2,}/g

const hasNoWildcards = (x) => x.length === 1
const hasNoWildcardAtStart = (x) => x.at(0) !== ''
const hasNoWildcardAtEnd = (x) => x.at(-1) !== ''

const insertWildcards = (x) => x.join('(.*)')
const templateMatchExact = (x) => `^${x}$`
const templateMatchStart = (x) => `^${x}`
const templateMatchEnd = (x) => `${x}$`

Produces:

console.log(rxFromWildcard('hello, world!'))
console.log(rxFromWildcard('hello, *'))
console.log(rxFromWildcard('*, world!'))

/^hello, world!$/i
/^hello,(.*)/i
/(.*), world!$/i

Here's the previous code for comparison:

import { memo } from './fp'

const rxFromWildcard = memo(str => {
  if (!str.length) {
    throw new Error('String should not be empty')
  }
  const sanitized = str
    .split('*')
    .map(x => x.trim())
    .map(escapeStringForRegExp)

  // Allow matching of wildcards
  const rxString = sanitized.join('(.*)')

  switch (true) {
    // No wildcards? Match string exactly
    case sanitized.length === 1:
      return new RegExp(`^${rxString}$`)

    // No wildcard at the start? Match string-start exactly
    case sanitized[0] !== '':
      return new RegExp(`^${rxString}`)

    // No wildcard at the end? Match string-end exactly
    case sanitized[sanitized.length - 1] !== '':
      return new RegExp(`${rxString}$`)
  }
  return new RegExp(rxString)
})

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeStringForRegExp(string) {
  if (typeof string !== 'string') {
    throw new TypeError('Expected string to be passed-in')
  }
  // $& means the whole matched string
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

@js-choi
Copy link
Collaborator Author

js-choi commented Oct 26, 2021

We’ve got some examples from real-world code for each proposed function now—though thanks for the help!

@js-choi js-choi closed this as completed Oct 26, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants