failte!

Welcome!

Tha mi air mo dhòigh a bhith còmhla ribh an-diugh.

I am delighted to be with you today.

Seo Gàidhlig airson luchd-tòiseachaidh

This is Gaelic for beginners

agus 's e tidseir a th' agaibh, an t-ollamh Cory

and I am your teacher, professor Cory

tòisichidh sinn le ruideigin simplidh

let's start with something simple.

Ciamar a chanas sibh "Hello" ann an Gàidhlig?

How do you say "Hello" in Gaelic?

"Halò"

"halò"

Sgoinneil!

Brilliant!

Cory Brown

Records and Tuples

How a slew of new JavaScript features are going to change the way we right JavaScript

Stage 0

Stage 1

Stage 2

Stage 3

Stage 4

Strawman

Proposal

Draft

Candidate

Finished

TC 39 Stages

Records and Tuples

Stage 2

Records and Tuples (stage 2)

Records and Tuples (STAGE 2)

Records and Tuples (STAGE 2)

Records and Tuples (STAGE 2)

const peoples = [
  { name: 'StrongSad', id: -1 }, 
  { name: 'Homestar', id: -2 },
  { name: 'Homsar', id: -3 },
]

console.log(`${peoples}`)
// '[object Object],[object Object],[object Object]'

const otherPeoples = #[
  #{ name: 'StrongBad', id: 1 }, 
  #{ name: 'The Cheat', id: 2},
  #{ name: 'Bubs', id: 3},
]

console.log(`${otherPeoples}`)
// `#[ #{ name: 'StrongBad', id: 1 },  #{ name: 'Homestar', id: 2}, #{ name: 'Bubs', id: 3}, #{ name: 'Coach Z', id: 4}, ]`

Records and Tuples (STAGE 2)

Records and Tuples (STAGE 2)

Records and Tuples (STAGE 2)

Records and Tuples (STAGE 2)

Pipeline operator

Stage 2

Pipeline Operator (stage 2)

Pipeline Operator (stage 2)

Pipeline Operator (stage 2)

Consecutive operations

const firstBook = books[0];
const title = firstBook.title;
const alphaNumeric = title.replaceAll(/[a-z0-9]/, '');
const length = alphaNumeric.length
length(removeAll(/[a-z0-9]/, get('title', first(books)));
books[0].title.replaceAll(/[a-z0-9]/, '').length

Intermediate variables

Function nesting

Chaining

Pipeline Operator (stage 2)

books |> first |> get('title') |> (x) => removeAll(/[a-z0-9]/, x) |> length;

Hack style

F# style

books |> first(%) |> get('title', %) |> removeAll(/[a-z0-9]/, %) |> length(%);

Pipeline Operator (stage 2)

Hack style

value |> % + 1

F# style

value |> (x) => x + 1
value |> fn + 1
// Syntax error: topic token required
value |> fn + 1
// TypeError: (fn + 1) is not a function
value |> await %
// Works out of the box wit Hack style
value |> await
// Requies special casing in the JS engine

Pipeline Operator (stage 2)

Hack style

value |> Number.parseInt(%, 10)

F# style

const parseIntBaseTen = (str) => Number.parseInt(str, 10)

value |> parseIntBaseTen

Pipeline Operator (stage 2)

  • I doubt Hack pipes offer sufficient reason to entice someone who doesn't already care about or like pipelines.
  • Those that do care about pipelines almost universally think about them in the F# way. (i.e. an operator equivalent to `pipe` function.
  • Maybe the `pipe` function is sufficient and an operator is unnecessary, even more so if we get partial function application (stage 1).
  • It seems unlikely we will get partial function application 😞.

Pipeline Operator (stage 2)

  • Hack style can be used in more scenarios than F#.
  • Many of those scenarios are footguns or just silly.
books |> %.title |> %.replaceAll(/[a-z0-9]/, '') |> %.length // ??? Why tho?
  • Maybe the community will develop some really interesting and useful patterns with Hack style
  • Maybe Hack style is the compromise that no one wants, and no one will use.
  • I am worried it will be the latter.

Temporal

Stage 3

replacement for Date

Temporal (stage 3)

Temporal (stage 3)

  • No support for Plain Dates or Plain Times
  • Date parsing is inconsistent and implementation specific
  • Date objects are mutable.
  • Daylight Savings Time is unpredictable
  • No (reasonable) support for Date computation
  • No support for non-Gregorian calendars

Temporal (stage 3)

"Zoned" Dates and Times

Temporal.ZonedDateTime.from('2022-09-23T08:00-06:00[America/Denver]')
  .epochSeconds     // 1663941600
//.epochNanoseconds // 1663941600000000000n

ZonedDateTime

Temporal.Instant.from('2022-09-23T08:00-06:00').epochSeconds // 1663941600

Instant

Temporal (stage 3)

"Plain" Dates and Times

Temporal.PlainTime.from({
  hour: 08,
  minute: 00
});

PlainTime

Temporal.PlainDate.from({ 
  year: 2022,
  month: 9, // Yep, 1 indexed months 🥳
  day: 23
}); // => 2022-09-23

PlainDate

PlainMonthDay // => 09-23

PlainYearMonth // => 2022-09

PlainDateTime
// => 2022-09-23T08:00:00

Temporal (stage 3)

const duration = Temporal.Duration.from({
  years: 1,
});

const relativeTo = Temporal.ZonedDateTime.from('2022-09-23T08:00[America/Denver]');

duration.total({
  unit: 'minute',
  relativeTo
}); // => 525600

Temporal (stage 3)

const duration = Temporal.Duration.from('P1Y1M1DT1H1M1.1S')
// One year, one month, one day, one hour, one minute, one second, and 100 milliseconds

duration.add({ years: 1 }, {relativeTo: Temporal.Now.zonedDateTimeISO()})
// P2Y1M1DT1H1M1.1S

Temporal (stage 3)

Duration methods

 duration.with : copy of duration with durationLike properties updated.

 duration.add : sums two durationLike objects.

 duration.subtract : duration less durationLike object.

 duration.negated : flips the sign on all properties of duration

 duration.abs : makes the sign of every property of duration positive.

 duration.round : rounds a duration to a give `roundTo` unit.

 duration.total : a float representing the number of desired units.

Temporal (stage 3)

(more) Duration methods

 duration.toString : the duration as an ISO 8601 string.

 duration.toJSON : same as duration.toString

 duration.toLocalString : a language-sensitive representation of duration. e.g. `1 year, 3 months, 5 days`

 duration. valueOf : always throws an exception. Durations are not comparable with relational operators

Temporal (stage 3)

Gregorian calendar default.

Other built in calendars follow the Intl.DateTimeFormat calendar locales.

You can create your own calendar! It just has to adhere to certain invariants.

Temporal

Stage 3

Temporal has no technical blockers.

Stage 4 is blocked only by IETF adoption & 2 JS engine Temporal implementations.

Pattern Matching

Stage 1

Pattern Matching (stage 1)

Pattern Matching (stage 1)

It's like regular expression matching, except not.

It's like a switch statement, except not.

It's like type checking, except not.

😏

It's like a ternary expression, except not.

Pattern Matching (stage 1)

(for the purposes of this proposal)

The ability to evaluate an expression provided a given set of conditions about an object is true

Pattern Matching (stage 1)

match (res) {
  when ({ status: 200, body, headers: { 'Content-Type': type } }) match {
    if (type === 'applicaiton/json'): handleJSONData(body)
    if (type === 'text/html'): handleTextData(body)
  } 
  	
  when ({ status, destination: url }) if (300 <= status && status < 400):
    handleRedirect(url)
  when ({ status: 500 }) if (!hasRetried): do {
    retry(req);
    hasRetried = true;
  }
  default: throwSomething();
}

Modified from an example in the proposal documentation

Pattern Matching (stage 1)

<Fetch url={API_URL}>
  {match (props) {
    when ({ loading }): <Loading />
    when ({ error }): <Error error={error} />
    when ({ data }): <Page data={data} />
  }}
</Fetch>

Modified from an example in the proposal documentation

Pattern Matching (stage 1)

const SomeObj = ({
  [Symbol.matcher]: (matchable) => ({
    matched: matchable === 3,
    value: { a: 1, b: { c: 2 } },
  });
});

match (3) {
  when (${SomeObj} with {a, b: {c}}): do {
    // passes the custom matcher,
    // then further applies an object pattern to the result’s value
    assert(a === 1);
    assert(c === 2);
  }
}
const SomeObj = ({
  [Symbol.matcher]: (matchable) => ({
    matched: matchable === 3,
    value: { a: 1, b: { c: 2 } },
  });
});









Pattern Matching (stage 1)

const Option = ({ hasValue, value }) => ({
  get value() { 
    if (!hasValue) throw new Exception('Can’t get the value of an Option.None.');
    return value;
  },
  [Option.type]: hasValue ? Option.Some[Option.type] : Option.None[Option.type]
});
Option.type = Symbol('type');

Option.Some = (val) => Option(true, val);
Option.Some[Option.type] = Symbol('Some');
Option.Some[Symbol.matcher] = (val) => ({
  matched: val[Option.type] === Option.SomeType,
  value: val.value,
});

Option.None = () => Option(false)
Option.None[Option.type] = Symbol('None');
Option.None[Symbol.matcher] = (val) => ({
  matched: val[Option.type] === Option.NoneType
});

match (result) {
  when (${Option.Some} with val): console.log(val);
  when (${Option.None}): console.log("none");
}











Option.Some[Symbol.matcher] = (val) => ({
  matched: val[Option.type] === Option.SomeType,
  value: val.value,
});



Option.None[Symbol.matcher] = (val) => ({
  matched: val[Option.type] === Option.NoneType
});

match (result) {
  when (${Option.Some} with val): console.log(val);
  when (${Option.None}): console.log("none");
}

Pattern Matching (stage 1)

Extractors (stage 1)

Leverages Symbol.matcher protocol to allow for user defined logic during destructuring (data validation, transformations, etc.)

Pattern Matching (stage 1)

const toInstant = (value) => {
  switch(true) {
    case (value instanceof Temporal.Instant): return value;
    case (value instanceof Date): return Temporal.Instant.fromEpochMilliseconds(+value);
    case (typeof value === "string"): return Temporal.Instant.from(value);
    default: throw new TypeError();
  }
}

const Book = ({
    title,
    createdAt = Temporal.Now.instant(),
    modifiedAt = createdAt
}) => ({
  title,
  createdAt: toInstant(createdAt),
  modifiedAt: toInstant(modifiedAt) // some duplication if `modifiedAt` is `undefined`
});

Book({ title: "...", createdAt: Temporal.Instant.from("...") });
Book({ title: "...", createdAt: new Date() });


const toInstant = (value) => {
  switch(true) {
    case (value instanceof Temporal.Instant): return value;
    case (value instanceof Date): return Temporal.Instant.fromEpochMilliseconds(+value);
    case (typeof value === "string"): return Temporal.Instant.from(value);
    default: throw new TypeError();
  }
}















Pattern Matching (stage 1)

Extractors (stage 1)

const InstantExtractor = {
  [Symbol.matcher]: value =>
    value instanceof Temporal.Instant ? { matched: true, value: [value] } :
    value instanceof Date ? { matched: true, value: [Temporal.Instant.fromEpochMilliseconds(value.getTime())] } :
    typeof value === "string" ? { matched: true, value: [Temporal.Instant.from(value)] } :
    { matched: false };
  }
}

const Book = ({
  title,
  // Extract `createdAt` as an Instant
  createdAt: InstantExtractor(createdAt) = Temporal.Now.instant(),
  modifiedAt: InstantExtractor(modifiedAt) = createdAt
}) => ({ title, createdAt, modifiedAt });

Book({ title: "...", createdAt: Temporal.Instant.from("...") });
Book({ title: "...", createdAt: new Date() });

Pattern Matching (stage 1)

  • Current pattern matching proposal is too complicated.
  • Extractors are step 1 in breaking the pattern matching proposal into useful stages about which the committee can gain understanding about patterns and usage to inform next steps.
  • If this approach is adopted, it will likely mean the pattern matching proposal as we know it will cease forward motion.
  • without this approach, the pattern matching proposal faces significant hurdles.

Tapadh Leibh

Thank you

Records and Tuples

By Cory Brown

Records and Tuples

Records and Tuples: How a slew of new JS proposals are going radically change how we write JavaScript

  • 284