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
- 425