();
3 | a.forEach((x) => {
4 | if (!b.has(x)) {
5 | out.add(x);
6 | }
7 | });
8 | return out;
9 | };
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jdc",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "main": "core/index.ts",
6 | "scripts": {
7 | "start": "node core/cli/index.js watch",
8 | "build": "node core/cli/index.js build"
9 | },
10 | "dependencies": {
11 | "@babel/core": "^7.18.10",
12 | "@babel/generator": "^7.18.12",
13 | "@babel/parser": "^7.18.11",
14 | "@babel/preset-env": "^7.18.10",
15 | "@babel/preset-react": "^7.18.6",
16 | "@babel/preset-typescript": "^7.18.6",
17 | "@babel/register": "^7.18.9",
18 | "@babel/traverse": "^7.18.11",
19 | "@babel/types": "^7.18.10",
20 | "@mdx-js/mdx": "^2.1.2",
21 | "@mdx-js/react": "^2.1.2",
22 | "@miniflare/core": "^2.7.1",
23 | "@miniflare/html-rewriter": "^2.7.1",
24 | "@silvenon/remark-smartypants": "^1.0.0",
25 | "@types/chalk": "^2.2.0",
26 | "@types/chokidar": "^2.1.3",
27 | "@types/glob": "^7.2.0",
28 | "@types/mdx": "^2.0.2",
29 | "@types/node": "^18.7.1",
30 | "@types/react": "^18.0.17",
31 | "@types/react-dom": "^18.0.6",
32 | "@types/sharp": "^0.30.5",
33 | "@types/string-hash": "^1.1.1",
34 | "@types/svgo": "^2.6.4",
35 | "browserslist": "^4.21.3",
36 | "chalk": "^4.1.2",
37 | "chokidar": "^3.5.3",
38 | "css-class-generator": "^2.0.0",
39 | "csso": "^5.0.5",
40 | "date-fns": "^2.29.1",
41 | "express": "^4.18.1",
42 | "glob": "^8.0.3",
43 | "highlight.js": "^11.6.0",
44 | "http-server": "^14.1.1",
45 | "jdc": "link:.",
46 | "jszip": "^3.10.1",
47 | "minimatch": "^5.1.0",
48 | "p-all": "^4.0.0",
49 | "postcss": "^8.4.16",
50 | "postcss-transform-classes": "^1.0.0",
51 | "posthtml": "^0.16.6",
52 | "posthtml-match-helper": "^1.0.3",
53 | "posthtml-minifier": "^0.1.0",
54 | "prettier": "^2.7.1",
55 | "react": "^18.2.0",
56 | "react-dom": "^18.2.0",
57 | "sharp": "^0.30.7",
58 | "string-hash": "^1.1.3",
59 | "svgo": "^2.8.0",
60 | "terser": "^5.14.2",
61 | "typescript": "^4.7.4"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/pages/404.mdx:
--------------------------------------------------------------------------------
1 | import { cssVariable } from "jdc";
2 |
3 | export const title = "404";
4 | export const css = "/assets/base.css";
5 |
6 |
16 | 404
17 |
18 |
19 | Page not found
20 |
--------------------------------------------------------------------------------
/pages/blog.mdx:
--------------------------------------------------------------------------------
1 | import Posts from "../components/Posts";
2 |
3 | export const title = "Jacob Does Code Blog";
4 | export const css = "/assets/blog.css";
5 |
6 | # Blog
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pages/calipers.mdx:
--------------------------------------------------------------------------------
1 | import { A, InlineSvg, classNames, cssVariable } from "jdc";
2 | import Icon from "../components/Icon";
3 | import Lead from "../components/Lead";
4 | import AppPromoImage from "../components/AppPromoImage";
5 | import AppBlock from "../components/AppBlock";
6 | import BetterTogether from "../components/BetterTogether";
7 | import LegalLinks from "../components/LegalLinks";
8 |
9 | export const title = "Calipers";
10 | export const description = "Measure things big and small";
11 | export const favicon = "/assets/calipers/favicon.png";
12 | export const appleTouchIcon = "/assets/calipers/apple-touch.png";
13 | export const appId = "6445826923";
14 | export const css = "/assets/app.css";
15 | export const primary = `var(${cssVariable("--calipers")})`;
16 |
17 |
18 |
19 | # Calipers
20 |
21 | Measure things big and small
22 |
23 |
24 |
25 | ## A 5 meter tape measure.
26 |
27 | Measure **centimeters** and **inches** right on your phone screen.
28 |
29 | For larger measurements, just slide your phone across a surface. As you do, Calipers will show you a summary of the meters or feet measured.
30 |
31 | Markings along the ruler let you record lengths at multiple intervals. Once you're done, Calipers can calculate the relative distances between each interval.
32 |
33 |
34 |
35 | ---
36 |
37 |
38 |
39 | ## Precision Measurements.
40 |
41 | You can measure anything that fits on your phone screen down to the **tenth of a millimeter**. Just place an object on your phone screen and adjust the two markers until they just touch the edges of the object, and Calipers will give you a precise length.
42 |
43 |
44 |
45 | ---
46 |
47 | # Get it now
48 |
49 | Available for iPhone
50 |
51 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/pages/cv.mdx:
--------------------------------------------------------------------------------
1 | import { classNames } from "jdc";
2 | import Lead from "../components/Lead";
3 |
4 |
5 | export const title = "CV";
6 | export const css = "/assets/cv.css";
7 |
8 | {/* Print to PDF using FireFox with US letter paper size (oops) and margins set to 0.9" vertically and 1" horizontally */}
9 |
10 | # Jacob Parker
11 |
12 | Senior frontend and mobile with over **9 years React** and **8 years React Native** experience. Skilled in everything relating to JavaScript, CSS, and iOS development.
13 |
14 | I've my career focused **JavaScript** projects using **React**, **React Native**, and anything related. I've got over 7 years' experience with all the tools you'd expect: **TypeScript**, **Jest**, **NodeJS**, **ExpressJS**, **Webpack**, **Babel**, **ESLint**, **Yarn**, **ES6**; and 3 years' **Apollo** and **GraphQL**. I specialise in building application architectures that scale to support every requirement from product and design: I see my job as making things happen, and seldom say something cannot be done.
15 |
16 | #### React & Web
17 |
18 | For **React**, I've used just about everything — **Redux**, **React Router**, **MobX**, **React Intl**, and many more. I have an incredibly strong understanding of everything CSS, have used just about every tool there is, and even written my own. Whether you're using **Styled Components**, **SASS**, **postCSS**, **CSS Modules**, or **vanilla CSS** — or if you don't have an architecture for your CSS — I can help.
19 |
20 | #### React Native
21 |
22 | If you're using **React Native**, you're probably using some of my code! I maintain the *safe-area-context* package (used by **react-navigation**), developed the CSS engine for *Styled Components*, helped implement the *gap* and *transform-origin* properties, and added a bunch of fixes to modals. I've worked on a tonne of apps, and never accept React Native cannot do something — I'll make it!
23 |
24 | #### Polyglot
25 |
26 | I have experience in a lot more than I can fit on my CV, including backend experience, functional programming, and if there's something new, I'll learn it!
27 |
28 | ## Experience
29 |
30 | ### Tesco
31 |
32 | Worked with a team to build a **micro-frontend** system, delivering business-critical features not available with other solutions, including a module system ensuring consistent dependencies between the server and client, which us to server render pages and achieve better performance and SEO than the legacy site. Overhauled the translation system to ensure translations cannot be missed, which was a common occurrence for the legacy site.
33 |
34 |
35 |
36 | -
37 |
38 | #### Nando's
39 |
40 | Worked on an internal communications and rota management app for Nando's 40,000 employees in **React Native**. In charge of resuming the codebase, which had been untouched for over 2 years, to ensure Nando's could again build and publish releases. Guided the team through app submissions. Migrated authentication to new provider to avoid Nando's having to pay additional fees.
41 |
42 |
43 |
44 | -
45 |
46 | #### WonderBill
47 |
48 | Worked on a team building a household bill aggregation app in **React Native**. Worked with designers and other members of the tech team to build new screens, a new component library, and address tech debt.
49 |
50 |
51 |
52 | -
53 |
54 | #### Red Deer
55 |
56 | Worked as part of a team to build a data vis application with **React**, **Redux**, and **D3** for visualising market data for a hedge fund. Set up key infrastructure to bridge the old jQuery site to React, enabling a gradual migration.
57 |
58 |
59 |
60 | -
61 |
62 | #### Zoopla
63 |
64 | Senior developer doing full stack development on various consumer-facing projects, using **Node**, **Express**, **SCSS**, **React**, and **Vue**.
65 |
66 |
67 |
68 | -
69 |
70 | #### Our Star Club
71 |
72 | Architected both **React** web and **React Native** applications for a social media app. Set up infrastructure, guided the team through native development, and added native code where React Native bindings were not available.
73 |
74 |
75 |
76 | -
77 |
78 | #### Concentra
79 |
80 | Developed a **React** & **D3**-based data visualization app for large, multi-national corporations with over 1M employees, which supported 60fps in browser and PowerPoint export.
81 |
82 |
83 |
84 | -
85 |
86 | #### AutoTrip
87 |
88 | Developed both a web and native app in **React** and **React Native**.
89 |
90 |
91 |
92 | -
93 |
94 | #### Tido Music
95 |
96 | Worked on a responsive music notation engine in JavaScript, with web and native apps built using **React** and **React Native**.
97 |
98 |
99 |
100 | -
101 |
102 | #### Geneity
103 |
104 | Fullstack development using Python and JavaScript.
105 |
106 |
107 |
108 |
109 |
110 | ## Apps & Projects
111 |
112 |
113 |
114 | #### GitHub
115 |
116 | [https://github.com/jacobp100](https://github.com/jacobp100)
117 |
118 | #### Apps
119 |
120 | [https://jacobdoescode.com](https://jacobdoescode.com)
121 |
122 |
123 |
124 |
125 |
126 | -
127 |
128 | #### [Pocket Jam](https://jacobdoescode.com/pocket-jam)
129 |
130 | Music app written in **React Native** and **Swift**
131 |
132 |
133 |
134 | -
135 |
136 | #### [Piano Tabs](https://jacobdoescode.com/piano-tabs)
137 |
138 | Interactive piano sheet music reader written in **SwiftUI** and **UIKit**
139 |
140 |
141 |
142 | -
143 |
144 | #### [TechniCalc](https://jacobdoescode.com/technicalc)
145 |
146 | Scientific calculator using **React Native** and **ReasonML**
147 |
148 |
149 |
150 |
151 |
152 | ## Education
153 |
154 | First Class honours BSc in Theoretical Physics from the University of York.
155 |
--------------------------------------------------------------------------------
/pages/freebies.mdx:
--------------------------------------------------------------------------------
1 | import { A, InlineSvg, classNames, cssVariable } from "jdc";
2 | import Icon from "../components/Icon";
3 | import LegalLinks from "../components/LegalLinks";
4 | import ReuseSvg from "../components/ReuseSvg";
5 |
6 | export const title = "Freebies";
7 | export const css = "/assets/freebies.css";
8 | export const primary = `var(${cssVariable("--freebies")})`;
9 |
10 | # Freebies
11 |
12 | All the apps below are free. Some are even open source.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ## Rail App
21 |
22 | View live and offline UK rail timetables.
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ## Za
38 |
39 | Scrabble dictionary.
40 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/pages/index.mdx:
--------------------------------------------------------------------------------
1 | import { A, classNames, cssVariable } from "jdc";
2 | import Icon from "../components/Icon";
3 |
4 | export const title = "Jacob Does Code";
5 | export const css = "/assets/index.css";
6 |
7 |
70 |
--------------------------------------------------------------------------------
/pages/piano-tabs.mdx:
--------------------------------------------------------------------------------
1 | import { A, InlineSvg, classNames, cssVariable } from "jdc";
2 | import Icon from "../components/Icon";
3 | import Lead from "../components/Lead";
4 | import AppPromoImage from "../components/AppPromoImage";
5 | import AppBlock from "../components/AppBlock";
6 | import PressKit from "../components/PressKit";
7 | import LegalLinks from "../components/LegalLinks";
8 |
9 | export const title = "Piano Tabs";
10 | export const description = "Sheet music and piano practise reimagined";
11 | export const favicon = "/assets/piano-tabs/favicon.png";
12 | export const appleTouchIcon = "/assets/piano-tabs/apple-touch.png";
13 | export const appId = "1506390976";
14 | export const css = "/assets/app.css";
15 | export const primary = `var(${cssVariable("--piano-tabs")})`;
16 |
17 |
18 |
19 | # Piano Tabs
20 |
21 | Sheet music and piano practise reimagined
22 |
23 |
24 |
25 | ## Over 800 pieces included. Import your own for anything else.
26 |
27 | Piano Tabs includes a giant library of classical pieces with over **800 pieces** from over **90 composers**.
28 |
29 | If that's not enough, you can import your own MIDI files. They're the industry standard, and **super-easy** to get hold of.
30 |
31 | Pieces are displayed as you would play them on a keyboard, so there is **no learning curve** to get started.
32 |
33 | Scroll through a piece for **interactive playback** to help you either find or study a section.
34 |
35 | ---
36 |
37 |
38 |
39 | ## Loop sections. Practise until perfect.
40 |
41 | Looping lets you pick specific parts of the piece you want to practise. Just drag the top and bottom handles set the start and end of the loop.
42 |
43 | With a loop set up up, you can use the **speed trainer** to have it start each loop playing slowly, and gradually speed up after each loop. Tweak how slow it starts, and how many loops it takes to reach the full tempo.
44 |
45 |
46 |
47 | ---
48 |
49 |
50 |
51 | ## Make your mark. Annotate anywhere.
52 |
53 | Bookmarks let you mark out sections within a piece. You can add as many as you need.
54 |
55 | Use your finger or Apple Pencil to **freehand draw anywhere** in the piece to annotate.\*
56 |
57 | * iPad only
58 |
59 |
60 |
61 | ---
62 |
63 |
64 |
65 | ## Customise every aspect of playback.
66 |
67 | View every track within a MIDI file, and **change the key and tempo** to your liking.
68 |
69 | **Percussion tracks** play along side the active track, or can be swapped out for a standard click **metronome**, or silenced entirely.
70 |
71 |
72 |
73 | ---
74 |
75 | # Get it now
76 |
77 | Available for iOS and macOS
78 |
79 |
90 |
91 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/pages/pocket-jam.mdx:
--------------------------------------------------------------------------------
1 | import { A, InlineSvg, classNames, cssVariable } from "jdc";
2 | import Icon from "../components/Icon";
3 | import Lead from "../components/Lead";
4 | import AppPromoImage from "../components/AppPromoImage";
5 | import AppBlock from "../components/AppBlock";
6 | import PressKit from "../components/PressKit";
7 | import LegalLinks from "../components/LegalLinks";
8 |
9 | export const title = "Pocket Jam";
10 | export const description = "Jam sessions you can carry in your pocket";
11 | export const favicon = "/assets/pocket-jam/favicon.png";
12 | export const appleTouchIcon = "/assets/pocket-jam/apple-touch.png";
13 | export const appId = "1153284525";
14 | export const css = "/assets/app.css";
15 | export const primary = `var(${cssVariable("--pocket-jam")})`;
16 |
17 |
18 |
19 | # Pocket Jam
20 |
21 | Jam sessions you can carry in your pocket
22 |
23 |
24 |
25 | ## Practise any song.
26 |
27 | You can play any song you can find on **Apple Music**\*, or add anything that shows up in the **Files** app — that includes your Safari downloads, iCloud, and other providers like Dropbox (provided you have their app).
28 |
29 | {/* prettier-ignore */}
30 | * Apple Music not available on macOS. Pitch adjustments and file exporting not available for Apple Music
31 |
32 | ---
33 |
34 |
35 |
36 | ## Change songs for your instrument and ability.
37 |
38 | You no longer have to retune your instrument if a song is in a different tuning to you — and you no longer need a capo. Now you can transpose the song any amount of semitones up to an octave.
39 |
40 | When a song is too fast or slow, you can control that too. Change the tempo anywhere between half and double the original.
41 |
42 | Everything works in **real time** — but you can export it to use in other applications.
43 |
44 |
45 |
46 | ---
47 |
48 |
49 |
50 | ## Loop sections. Practise until perfect.
51 |
52 | Looping lets you pick specific parts of the song you want to practise. Just drag the left and right handles on the play bar to set the start and end of the loop.
53 |
54 | With a loop set up up, you can use the **speed trainer** to have it start each loop playing slowly, and gradually speed up after each loop. Tweak how slow it starts, and how many loops it takes to reach the full tempo.
55 |
56 |
57 |
58 | ---
59 |
60 |
61 |
62 | ## Skip to the good bit.
63 |
64 | Set up **section markers** in your song for the bits that are important to you.
65 |
66 | Use them to jump to the section in playback, or to set up loops between sections faster.
67 |
68 |
69 |
70 | ---
71 |
72 | # Get it now
73 |
74 | Available for iOS and macOS
75 |
76 |
87 |
88 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/pages/privacy.mdx:
--------------------------------------------------------------------------------
1 | import { cssVariable } from "jdc";
2 |
3 | export const title = "Privacy Policy";
4 | export const css = "/assets/privacy.css";
5 | export const primary = `var(${cssVariable("--blog")})`;
6 |
7 | # Privacy Policy
8 |
9 | All data you create is stored locally on your device, and is not uploaded to any servers.
10 |
11 | Neither usage data nor analytics are collected.
12 |
13 | For [TechniCalc](/pages/technicalc.mdx), crash reports are generated by and uploaded to the third party, [Sentry](https://sentry.io/). Crash reports contain only the information on where in the code the crash occurred, details about the device (make and model etc.), and details about the operating system (name and version). No personal information is uploaded.
14 |
--------------------------------------------------------------------------------
/pages/technicalc.mdx:
--------------------------------------------------------------------------------
1 | import { A, InlineSvg, Video, classNames, cssVariable } from "jdc";
2 | import LightBox from "../components/LightBox";
3 | import Icon from "../components/Icon";
4 | import Lead from "../components/Lead";
5 | import AppPromoImage from "../components/AppPromoImage";
6 | import AppBlock from "../components/AppBlock";
7 | import PressKit from "../components/PressKit";
8 | import LegalLinks from "../components/LegalLinks";
9 | import TechnicalcComputation from "../components/TechnicalcComputation";
10 |
11 | export const title = "TechniCalc";
12 | export const description = "A smart calculator for a smart phone";
13 | export const appId = "1504965415";
14 | export const favicon = "/assets/technicalc/favicon.png";
15 | export const appleTouchIcon = "/assets/technicalc/apple-touch.png";
16 | export const css = [
17 | "/assets/app.css",
18 | "/assets/lightbox.css",
19 | "/assets/technicalc/icon-grid.css",
20 | "/assets/technicalc/computation-critical.css",
21 | ];
22 | export const js = "/assets/lightbox.js";
23 | export const primary = `var(${cssVariable("--technicalc")})`;
24 | export const banner = ;
25 |
26 |
27 |
28 | # TechniCalc
29 |
30 | A smart calculator for a smart phone
31 |
32 |
33 |
34 |
35 |
36 | ## TechniCalc is every bit functional as it is beautiful.
37 |
38 | The modern interface that makes a clean break from traditional pocket calculators. Equations are entered in a natural way, adhering to every detail of hand-written maths.
39 |
40 | The **advanced maths engine** also makes this the only calculator you'll ever need.
41 |
42 | You'll find your garden variety logs, sins, and cosines — but for when you need it, the engine has full support for imaginary numbers, vectors, matrices, sums and products, and differentiation and integration.
43 |
44 | ---
45 |
46 |
47 |
48 |
49 |
50 |
51 |
Advanced Maths
52 |
53 |
54 |
Currency Conversion
55 |
56 |
57 |
Custom Equations
58 |
59 |
60 |
Graphing
61 |
62 |
63 |
Date Calculations
64 |
65 |
66 |
100+ Constants
67 |
68 |
69 |
Unit Conversion
70 |
71 |
72 |
Statistics
73 |
74 |
75 |
Custom Variables
76 |
77 |
78 |
79 | ---
80 |
81 |
82 |
83 |
84 |
85 | ## Converting between units couldn't be simpler — or more powerful.
86 |
87 | All the units you would expect are supported, and you can combine them infinitely.
88 |
89 | **Meters** to **yards**? check.
90 |
91 | **Kilogram-meters-per-second-squared** to something else? Also check.
92 |
93 |
94 |
95 | ---
96 |
97 |
98 |
99 | ## Solvers give you answers. Fast.
100 |
101 | Find all the roots for quadratic and cubic equations.
102 |
103 | Find the intersection of multiple lines or planes.
104 |
105 | Even find a root for any given equation.
106 |
107 |
108 |
109 | ---
110 |
111 |
112 |
113 | ## Every constant you'll ever need, built right in.
114 |
115 | With over 100 constants built in, no matter whether you're doing engineering, physics, or chemistry, you'll find every constant you need.
116 |
117 | If you don't find one? You can **add your own** — and even give it your own symbol.
118 |
119 |
120 |
121 | ---
122 |
123 |
124 |
125 | ## Leave your formula book at home.
126 |
127 | Whether you struggle to remember your equations — or just struggle to type it in — you can add them all to memory.
128 |
129 | You can even add symbols to remind you what — and where — to fill in when you insert them.
130 |
131 |
132 |
133 | ---
134 |
135 |
136 |
137 | ## All the power. From your wrist to the big screen.
138 |
139 | From showing you the most recent result on Apple Watch face — to **external keyboard support** — to making **full use of larger screens** on iPads and MacBooks, TechniCalc gets the best out of your hardware.
140 |
141 | With full support for **split screen** on iPad and **global shortcut** keys on macOS, TechniCalc gets the best out of your hardware's software too.
142 |
143 |
144 |
145 | ---
146 |
147 | # Get it now
148 |
149 | Available for iOS and macOS
150 |
151 |
161 |
162 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/posts/2019-11-01-polymorphic-variants-in-reason-ml.mdx:
--------------------------------------------------------------------------------
1 | export const title = "Polymorphic Variants in ReasonML";
2 | export const css = ["/assets/blog.css", "/assets/blog-highlight.css"];
3 |
4 | # Polymorphic Variants in ReasonML
5 |
6 | Polymorphic variants are one of the more unique features of OCaml (or ReasonML if you prefer). These are actually one of the things that aren't documented ReasonML site, but they can be really useful nonetheless.
7 |
8 | At their simplest, they work exactly the same as regular variants. The first difference is that these variants begin with a `` ` `` character. They can also be used without a type definition. Let's find the index of a pair of brackets in a list of characters.
9 |
10 | ```reasonml
11 | let firstBracketPair = inputChars => {
12 | let rec iter = (currentState, chars, currentIndex) =>
13 | switch (currentState, chars) {
14 | | (`FoundBracket(startIndex), [')', ..._]) =>
15 | Some((startIndex, currentIndex))
16 | | (_, ['(', ...tail]) =>
17 | iter(`FoundBracket(currentIndex), tail, currentIndex + 1)
18 | | (_, [_, ...tail]) =>
19 | iter(currentState, tail, currentIndex + 1)
20 | | (_, []) =>
21 | None
22 | };
23 | iter(`NoBrackets, inputChars, 0);
24 | };
25 | ```
26 |
27 | In the above example, we could have defined a type just for the iteration state, but with polymorphic variants, we don't have to.
28 |
29 | ## Diving Deeper
30 |
31 | Unlike when defining the types for regular variants, you can build polymorphic variants using other variants. Other than the backtick for each name, the types for polymorphic variants also need square brackets around them.
32 |
33 | ```reasonml
34 | type primary = [ | `Red | `Green | `Blue];
35 | type colorFunctions = [ | `Rgb(int, int, int) | `Hsl(int, int, int)];
36 | /* Combiles both the variants primary and colorFunctions */
37 | type colors = [ primary | colorFunctions];
38 | ```
39 |
40 | Now that our types are a bit more complicated, you'll want to actually write the type definitions. You'll be able to compile without them, but when you do get errors — especially with large types — the error messages will be multiple pages on your terminal and won't help you at all.
41 |
42 | The above example is a common way for articles to demonstrate polymorphic variants. But it's not a great example — this could be a regular variant type, and it might be better that way. So I'm going to give two examples of cases where polymorphic variants actually helped.
43 |
44 | ## Units of Measure
45 |
46 | When converting between units — like meters to inches — it's normally just multiplying by something. However, Celsius and Fahrenheit do their own thing, and need to be handled differently.
47 |
48 | For this example, we want to take a unit and a value, and convert it into standard units (_si_ units). I represent this with polymorphic variants, and have one function that handles all the ‘nice' values, and another function that handles the edge cases.
49 |
50 | ```reasonml
51 | type length = [ | `Meter | `Inch];
52 | type time = [ | `Second | `Minute | `Hour];
53 | type temperatureLinear = [ | `Kelvin];
54 | type temperatureNonLinear = [ | `Celsius];
55 |
56 | type unitLinear = [ length | time | temperatureLinear];
57 | type anyUnit = [ unitLinear | temperatureNonLinear];
58 |
59 | let siScale = (unit: unitLinear) =>
60 | switch (unit) {
61 | | `Meter => 1.
62 | | `Inch => 0.0254
63 | | `Second => 1.
64 | | `Minute => 60.
65 | | `Hour => 3600.
66 | | `Kelvin => 1.
67 | };
68 |
69 | let toSi = (value, unit: anyUnit) =>
70 | switch (unit) {
71 | | #unitLinear as linearUnit => value *. siScale(linearUnit)
72 | | `Celsius => value +. 273.15
73 | };
74 | ```
75 |
76 | > Note: `#unitLinear` in the means match against all cases in the unitLinear type
77 |
78 | With this setup, we can be much more granular about how we handle edge cases.
79 |
80 | If we added another linear unit — like feet — to this, our compiler would enforce that it's in the `siScale` function. If we added Fahrenheit, it would make sure it was handled in the `toSi` function.
81 |
82 | If we used regular variants, we could put all the units in one variant, but then we'd lose the ability to abstract things out like we did, and the type-checker would not be as helpful. Or we'd be able to keep the abstraction, but introducing more variants: we'd need one variant for all the linear units, one variant for temperature units, and one more to wrap it, like `LinearUnit(linearUnit) | TemperatureUnit(temperatureUnit)`. The user would then have to give units in this format. 🤮
83 |
84 | ## Mixing Scalars and Vectors
85 |
86 | Say we have a numeric type that's more complicated than a float. Maybe it's an exact fraction, or a decimal. We can also have vectors built up of that type, and nan types. We want to build a maths library where you can add and subtract anything of these types. Polymorphic variants are also a good fit here!
87 |
88 | ```reasonml
89 | type scalar = [ | `Fraction(int, int) | `Decimal(float)];
90 | type value = [ scalar | `Vector(list(scalar)) | `NaN];
91 |
92 | let addScalar = (a, b) =>
93 | switch (a, b) {
94 | | (`Fraction(n1, d1), `Fraction(n2, d2)) =>
95 | `Fraction((n1 * d2 + n2 * d1, d1 * d2))
96 | | (`Fraction(n, d), `Decimal(f))
97 | | (`Decimal(f), `Fraction(n, d)) =>
98 | `Decimal(f *. float_of_int(n) /. float_of_int(d))
99 | | (`Decimal(f1), `Decimal(f2)) =>
100 | `Decimal(f1 *. f2)
101 | };
102 |
103 | let add = (a, b) =>
104 | switch (a, b) {
105 | | (#scalar as a, #scalar as b) => addScalar(a, b)
106 | | (`Vector(a), `Vector(b)) => `Vector(List.map2(addScalar))
107 | | _ => `NaN
108 | };
109 | ```
110 |
111 | In the same manner as the previous examples, we _could_ use regular variants here, but it would be less nice for the same reasons.
112 |
113 | ## Performance
114 |
115 | This power can come at a cost. Normally when you see performance warnings about polymorphic variants, it talks about the performance of switch statements and memory usage. Realistically, these aren't going to affect you.
116 |
117 | However, there is something to be aware of if you're compiling to JavaScript **and you have a lot of polymorphic variants in one type**: when running `switch` over a polymorphic type, the code size is a lot larger than you'd expect.
118 |
119 | If we take the units example, and add over a hundred units, every switch statement over the units was 2kb of JS minified — this adds up quickly! I changed this to a regular variant, and each switch statement went down to just over 100–200 bytes.
120 |
121 | Again, this will only affect you if your types are huge, and will not affect you at all if your types aren't huge. If in doubt, read what BuckleScript outputs!
122 |
123 | ## Conclusion
124 |
125 | Polymorphic variants are really cool and you should use them more!
126 |
--------------------------------------------------------------------------------
/posts/2020-04-08-a-primer-in-reason-ml.mdx:
--------------------------------------------------------------------------------
1 | export const title = "A Primer in ReasonML";
2 | export const css = ["/assets/blog.css", "/assets/blog-highlight.css"];
3 |
4 | # A Primer in ReasonML
5 |
6 | If you've used Reason a little bit, you might have seen `string_of_int`, `string_of_float`, or if you're using Belt, `Float.toString`.
7 |
8 | So the question is — why is this necessary? A lot of languages let you do `toString` without saying the type you're converting from.
9 |
10 | A lot of languages support something called function overloading. Take C# for example,
11 |
12 | ```c#
13 | string ToString(int x) { /*...*/ }
14 | string ToString(float x) { /*...*/ }
15 | ```
16 |
17 | In JavaScript, you can get something to the same effect by using `typeof` checks.
18 |
19 | Most languages, too, support essentially the same thing through object inheritance too. You can have a class `Pet` with a `toString` method on it, then make subclasses `Dog` and `Cat` which each have their own `toString` methods.
20 |
21 | Reason doesn't support the first kind of overloading. Functions can only be defined once, and you can't do `typeof` checks on what is passed in.
22 |
23 | Reason does technically have some concept of classes, but they are rarely used. It's best to just not use them.
24 | It's because of these reasons there are multiple kinds of `+` operator — including `+.` for floats, `++` for strings.
25 |
26 | This might initially seem like a significant limitation, but the omission of these features is intentional: these features always have edge cases in any language that includes them.
27 |
28 | ## Modules
29 |
30 | The most unique part of Reason is its module system. They are much more powerful than classes — both for the user, the type checker and the compiler.
31 |
32 | Usually, you will make your modules as isolated as possible, and group all the functionality in the module — not far off what your average class looks like.
33 |
34 | The convention is to have a type `t` that encompasses all the data a class would have. You also normally have a `make` function to act as a constructor and/or an `empty` variable if that makes sense for your module. For example,
35 |
36 | ```reasonml
37 | module Person = {
38 | type t = {name: string};
39 |
40 | let make = name => {name: name};
41 |
42 | let toString = person => "Person :" ++ person.name;
43 | };
44 |
45 | let person = Person.make("Bob");
46 | let personString = Person.toString(person);
47 | Js.log(personString);
48 |
49 | /* Or */
50 | let personString = Person.(make("Bob")->toString);
51 | ```
52 |
53 | I said before this isn't too far from what an average class looks like. But there is one crucial difference.
54 |
55 | In OOP, your data and your functions are forcibly coupled together. Here, however, the grouping is just for the programmer — there is nothing actually forcing the type and the two functions together.
56 |
57 | OOP tends to suffer from data not being strictly hierarchical. By not coupling the data and functions means that when your data isn't hierarchical, you have much better tools to organise your code — although that's another blog post.
58 |
59 | ## Bonus Time
60 |
61 | As a last bonus, something you'll see in Belt is defining operators in a module — and this is something you can do too!
62 |
63 | ```reasonml
64 | module Fraction = {
65 | type t = {
66 | num: int,
67 | den: int,
68 | };
69 |
70 | let make = (num, den) => {num, den};
71 |
72 | let (+) = (a, b) => {
73 | num: a.num * b.den + a.den * b.num,
74 | den: a.den * b.den,
75 | };
76 | };
77 |
78 | let fractionSum = Fraction.(make(1, 2) + make(1, 3));
79 | Js.log(fractionSum);
80 | ```
81 |
82 | This is a bit like marmite — some people love it, some hate it. It really depends on your project how much you'll make use of it — if at all.
83 |
--------------------------------------------------------------------------------
/site.config.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { Config } from "./core";
3 | import Layout from "./components/Layout";
4 |
5 | export default {
6 | pages: [
7 | "pages/**/*.mdx",
8 | "posts/**/*.mdx",
9 | "assets/technicalc/computation.tsx",
10 | ],
11 | urlForPage(filename) {
12 | const dateMatch = filename.match(/(\d{4})-(\d{2})-(\d{2})-/);
13 |
14 | let url = path.basename(filename, path.extname(filename));
15 | if (dateMatch != null) {
16 | url = url.replace(dateMatch[0], dateMatch[0].replace(/-/g, "/"));
17 | }
18 |
19 | return url === "index" ? "/" : `/${url}`;
20 | },
21 | Layout,
22 | cssAnalyzer: true,
23 | } as Config;
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "lib": ["ES2020", "DOM"],
5 | "types": ["node"],
6 | "jsx": "react-jsx",
7 | "strict": true,
8 | "esModuleInterop": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------