Welcome!
I am delighted to be with you today.
This is Gaelic for beginners
and I am your teacher, professor Cory
let's start with something simple.
How do you say "Hello" in Gaelic?
"halò"
Brilliant!
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
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}, ]`
Stage 2
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
books |> first |> get('title') |> (x) => removeAll(/[a-z0-9]/, x) |> length;
Hack style
F# style
books |> first(%) |> get('title', %) |> removeAll(/[a-z0-9]/, %) |> length(%);
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
Hack style
value |> Number.parseInt(%, 10)
F# style
const parseIntBaseTen = (str) => Number.parseInt(str, 10)
value |> parseIntBaseTen
books |> %.title |> %.replaceAll(/[a-z0-9]/, '') |> %.length // ??? Why tho?
Stage 3
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.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
const duration = Temporal.Duration.from({
years: 1,
});
const relativeTo = Temporal.ZonedDateTime.from('2022-09-23T08:00[America/Denver]');
duration.total({
unit: 'minute',
relativeTo
}); // => 525600
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
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.
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
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.
Stage 3
Temporal has no technical blockers.
Stage 4 is blocked only by IETF adoption & 2 JS engine Temporal implementations.
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.
(for the purposes of this proposal)
The ability to evaluate an expression provided a given set of conditions about an object is true
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
<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
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 } },
});
});
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");
}
Leverages Symbol.matcher protocol to allow for user defined logic during destructuring (data validation, transformations, etc.)
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();
}
}
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() });
Thank you