Browse Source

dump dump dump

Amélia Liao 3 years ago
parent
commit
2a8c36a011
22 changed files with 735 additions and 399 deletions
  1. +3
    -1
      .gitignore
  2. +10
    -6
      blag.cabal
  3. +185
    -58
      css/default.scss
  4. +1
    -1
      css/katex.min.css
  5. +1
    -1
      diagrams/eq/0cube.tex
  6. +213
    -8
      package-lock.json
  7. +1
    -1
      package.json
  8. +3
    -3
      pages/contact.md
  9. +1
    -1
      pages/posts/2019-10-04-amulet-kinds.md
  10. +3
    -3
      pages/posts/2020-01-31-lazy-eval.lhs
  11. +2
    -2
      pages/posts/2020-07-12-continuations.md
  12. +0
    -162
      pages/posts/2020-09-09-typing-proof.md
  13. +2
    -2
      pages/posts/2020-10-30-reflecting-equality.md
  14. +13
    -12
      pages/posts/2021-01-15-induction.md
  15. +242
    -90
      site.hs
  16. +1
    -1
      stack.yaml
  17. BIN
      static/pfp.jpg
  18. +1
    -6
      sync
  19. +17
    -18
      templates/default.html
  20. +14
    -9
      templates/post-list.html
  21. +1
    -13
      templates/post.html
  22. +21
    -1
      templates/tikz.tex

+ 3
- 1
.gitignore View File

@ -10,4 +10,6 @@
/fonts
/css/fonts
/.mailmap
/.mailmap
.katex_cache

+ 10
- 6
blag.cabal View File

@ -6,19 +6,23 @@ cabal-version: >= 1.10
executable site
main-is: site.hs
build-depends: base
, text
, hsass
, aeson
, hakyll
, pandoc
, skylighting
, binary
, process
, containers
, deepseq
, hashable
, directory
, pandoc-types
, containers
, bytestring
, uri-encode
, deepseq
, text
, hsass
, hakyll-sass
, skylighting
, pandoc-types
, unordered-containers
ghc-options: -threaded
default-language: Haskell2010


+ 185
- 58
css/default.scss View File

@ -1,13 +1,15 @@
$purple: #424969;
$orange: #ffac5f;
$blonde: #f5ddbc;
$purple: #4834d4;
$orange: #ffbe76;
$blonde: #f5ddbc;
$light-purple: #faf0fa;
$yugo: #ea8472;
$yugo: #ea8472;
$pink_glamour: #ff7979;
$carmine_pink: #eb4d4b;
$header: $orange;
$header-height: 50px;
$max-width: 95ch;
$max-width: 80ch;
.mathpar, .math-paragraph {
display: flex;
@ -15,6 +17,10 @@ $max-width: 95ch;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
> figure {
width: auto;
}
}
a#mastodon {
@ -59,7 +65,7 @@ div#header {
font-size: 18pt;
a {
color: $purple;
color: black;
text-decoration: none;
}
@ -82,7 +88,7 @@ div#header {
line-height: $header-height;
a {
color: $purple;
color: black;
text-decoration: none;
margin-left: .5em;
@ -101,18 +107,22 @@ div#header {
}
div#content {
max-width: $max-width;
div#content, div.post-list-synopsys {
max-width: $max-width + 4ch;
margin: 0px auto 0px auto;
flex: 1 0 auto;
padding: 2ch;
span.katex, span.together {
display: inline-block;
}
span.together {
white-space: nowrap;
}
p {
text-align: justify;
letter-spacing: 1.75;
code {
display: inline-block;
@ -130,6 +140,10 @@ div#content {
span.theorem, span.paragraph-marker {
font-style: italic;
}
span.word {
display: inline-block;
}
}
p.image {
@ -137,57 +151,69 @@ div#content {
}
h1 {
font-variant: small-caps;
max-width: 120ch;
}
blockquote {
background-color: $light-purple;
border-radius: 10px;
border: 1px solid $purple;
> article {
> blockquote {
width: 85%;
margin: auto;
padding: 0em 1em;
}
border-left: 12px solid $purple;
border-right: 3px dashed $purple;
border-top: 3px dashed $purple;
border-bottom: 3px dashed $purple;
> article > blockquote {
width: 80%;
margin: auto;
}
padding: 2ch;
}
> article > div.code-container {
width: 80%;
margin: auto;
> div.code-container {
// width: 80%;
// margin: auto;
pre {
padding: 0px 1em;
}
pre {
padding: 0px 1em;
width: 80%;
margin: auto;
}
> span {
display: inline;
> span {
display: inline;
}
}
}
div.code-container {
padding-top: 5px;
background-color: $light-purple;
border-radius: 10px;
border-radius: 5px;
border: 1px solid darken($light-purple, 10%);
overflow-x: auto;
> span {
padding: 0.5em 1em 0.25em 1em;
> div.code-tag {
background-color: darken($light-purple, 10%);
overflow: hidden;
line-height: 80%;
font-size: 12pt;
margin-top: 5px;
width: 100%;
border-top-left-radius: 10px;
span {
padding: 0.5em 1em 0.25em 1em;
box-shadow: 1px 1px darken($light-purple, 20%);
display: none;
border-top-left-radius: 10px;
box-shadow: 1px 1px darken($light-purple, 20%);
// display: none;
float: right;
}
}
> span::after {
content: " code";
pre {
overflow-x: auto;
}
div.sourceCode {
@ -207,6 +233,42 @@ div#content {
}
}
div.warning {
&:before {
content: "Heads up!";
display: block;
font-size: 16pt;
}
margin: 16px auto;
width: 85%;
border-left: 12px solid $carmine_pink;
border-right: 3px dashed $carmine_pink;
border-top: 3px dashed $carmine_pink;
border-bottom: 3px dashed $carmine_pink;
padding: 2ch;
&>:first-child {
margin-top: 0.5em;
}
&>:last-child {
margin-bottom: 0px;
}
}
div.text-image {
display: flex;
margin-bottom: -18px;
> figure {
min-width: 25px;
padding-left: 2em;
}
}
* {
max-width: 100%;
}
@ -224,7 +286,6 @@ div#content {
padding: .2em 1em;
background-color: white;
font-size: 0pt;
margin-top: 0.5em;
margin-bottom: 0.5em;
@ -234,7 +295,6 @@ div#content {
}
details[open] {
background-color: $light-purple;
font-size: 14pt;
}
@ -299,7 +359,16 @@ div#content {
}
font-size: 14pt;
line-height: 1.6;
line-height: 1.4;
span.katex-display {
overflow-x: auto;
overflow-y: clip;
}
}
.footnote-back {
margin-left: 0.5em;
}
div.info, span#reading-length {
@ -320,6 +389,7 @@ div#footer {
padding-right: 1em;
background-color: $light-purple;
height: 50px;
}
.definition {
@ -330,6 +400,9 @@ div#footer {
list-style: none;
padding: 0px;
width: 90%;
margin: auto;
div.post-list-item {
margin-top: .2em;
margin-bottom: .2em;
@ -339,17 +412,19 @@ div#footer {
border-radius: 10px;
background-color: $light-purple;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-items: flex-end;
font-size: 11pt;
div.post-list-header {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-items: flex-end;
a {
font-size: 14pt;
padding-right: 2em;
a {
font-size: 14pt;
padding-right: 2em;
}
}
font-size: 11pt;
}
}
@ -389,21 +464,61 @@ figure {
overflow-x: auto;
}
> p {
margin-bottom: 0px;
}
figcaption {
font-size: 14pt;
}
display: inline-block;
figcaption::before {
counter-increment: figure;
content: "Figure " counter(figure) ". ";
> p {
margin-top: 0px;
margin-bottom: 0px;
}
}
}
// figcaption::before {
// counter-increment: figure;
// content: "Figure " counter(figure) ". ";
// display: inline;
// }
}
@media only screen and (max-width: $max-width) {
div#content {
margin-left: 1em;
margin-right: 1em;
margin-left: 1px;
margin-right: 1px;
padding: 1ch;
div.text-image {
display: block;
> figure {
padding-left: 0px;
}
}
figure figcaption {
max-width: 85%;
margin: auto;
p {
font-style: italic;
}
}
ul {
padding-left: 20px;
}
p {
hyphens: auto;
span.katex {
display: inline;
}
}
}
.mathpar {
@ -417,6 +532,14 @@ figure {
flex-grow: 0;
}
}
header {
nav div {
&>:nth-child(3) {
display: none;
}
}
}
}
// Contact page
@ -461,6 +584,10 @@ figure {
}
}
span.math.inline {
display: inline-block;
}
@font-face {
font-family: 'Fantasque Sans Mono';
src: url('fonts/FantasqueSansMono-Regular.woff2') format('woff2');


+ 1
- 1
css/katex.min.css
File diff suppressed because it is too large
View File


+ 1
- 1
diagrams/eq/0cube.tex View File

@ -1 +1 @@
\node[draw,circle,label=right:$A$,fill,outer sep=0.1cm, inner sep=0pt, minimum size=0.1cm] (i0) at (0, 0) {};
\node[draw,circle,label=right:$x:A$,fill,outer sep=0.1cm, inner sep=0pt, minimum size=0.1cm] (i0) at (0, 0) {};

+ 213
- 8
package-lock.json View File

@ -1,8 +1,213 @@
{
"name": "blag",
"version": "1.0.0",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"katex": "^0.13.11",
"sass": "^1.28.0"
},
"devDependencies": {}
},
"node_modules/anymatch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/binary-extensions": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
"integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
"dependencies": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.5.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.1.2"
}
},
"node_modules/commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"engines": {
"node": ">= 6"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/glob-parent": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/katex": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.11.tgz",
"integrity": "sha512-yJBHVIgwlAaapzlbvTpVF/ZOs8UkTj/sd46Fl8+qAf2/UiituPYVeapVD8ADZtqyRg/qNWUKt7gJoyYVWLrcXw==",
"dependencies": {
"commander": "^6.0.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
"engines": {
"node": ">=8.6"
}
},
"node_modules/readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/sass": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.28.0.tgz",
"integrity": "sha512-9FWX/0wuE1KxwfiP02chZhHaPzu6adpx9+wGch7WMOuHy5npOo0UapRI3FNSHva2CczaYJu2yNUBN8cCSqHz/A==",
"dependencies": {
"chokidar": ">=2.0.0 <4.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
}
},
"dependencies": {
"anymatch": {
"version": "3.1.1",
@ -42,9 +247,9 @@
}
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
},
"fill-range": {
"version": "7.0.1",
@ -95,11 +300,11 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"katex": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.11.1.tgz",
"integrity": "sha512-5oANDICCTX0NqYIyAiFCCwjQ7ERu3DQG2JFHLbYOf+fXaMoH8eg/zOq5WSYJsKMi/QebW+Eh3gSM+oss1H/bww==",
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.11.tgz",
"integrity": "sha512-yJBHVIgwlAaapzlbvTpVF/ZOs8UkTj/sd46Fl8+qAf2/UiituPYVeapVD8ADZtqyRg/qNWUKt7gJoyYVWLrcXw==",
"requires": {
"commander": "^2.19.0"
"commander": "^6.0.0"
}
},
"normalize-path": {


+ 1
- 1
package.json View File

@ -4,7 +4,7 @@
"description": "[Go here instead](https://abby.how)",
"main": "index.js",
"dependencies": {
"katex": "^0.11.1",
"katex": "^0.13.11",
"sass": "^1.28.0"
},
"devDependencies": {},


+ 3
- 3
pages/contact.md View File

@ -9,7 +9,7 @@ span#reading-length { display: none; }
<div class="contact-list">
<div class="contact-card">
<span class="username">@abby#4600</span>
<span class="username">Abbie#4600</span>
My Discord friend requests are always open. Feel free to add me for questions or comments!
</div>
@ -18,7 +18,7 @@ My Discord friend requests are always open. Feel free to add me for questions or
<span class="username">{abby}</span>
<span>
Message me directly on <a href="https://webchat.freenode.net/">Freenode</a> IRC, or join `##dependent` to talk about types!
Message me directly on <a href="https://libera.chat">Libera</a> IRC, or join `##dependent` to talk about types!
</span>
</div>
</div>
@ -32,4 +32,4 @@ If you like what I do, here are some ways you can support this blog:
You can send me a one-time donation on Ko-Fi. Just remember not to read the name on the receipt!
(Paypal sucks)
</div>
</div>
</div>

+ 1
- 1
pages/posts/2019-10-04-amulet-kinds.md View File

@ -275,7 +275,7 @@ let zs : vect (S (S (S Z))) int = ys
```
Internally, type functions do pretty much the same thing as the functional dependency + evidence
approach we used internally. Each equation gives rise to an equality _axiom_, represented as a
approach we used earlier. Each equation gives rise to an equality _axiom_, represented as a
constructor because our intermediate language pretty much lets constructors return whatever they damn
want.


+ 3
- 3
pages/posts/2020-01-31-lazy-eval.lhs View File

@ -128,7 +128,7 @@ conditionals to enable/disable each section. To compile a specific
section, use GHC like this:
```bash
ghc -XCPP -DSection1 2020-01-09.lhs
ghc -XCPP -DSection=1 2020-01-09.lhs
```
-----
@ -1345,11 +1345,11 @@ inspired to write a G-machine of your own, please let me know!
[^1]: I'd prefer the plural "redices".
[Rio]: https://github.com/plt-abigail/rio
[Rio]: https://github.com/plt-hokusai/rio
[^2]: Which I'm not going to draw here because it's going to be rendered at an absurd size.
[^3]: A real implementation could use pointer tagging instead.
[a Literate Haskell source file]: /lhs/2020-01-31.lhs
[a Literate Haskell source file]: /pages/posts/2020-01-31-lazy-eval.lhs
<!-- vim: fdm=marker
-->

+ 2
- 2
pages/posts/2020-07-12-continuations.md View File

@ -6,7 +6,7 @@ maths: true
Continuations are a criminally underappreciated language feature. Very few languages (off the top of my head: most Scheme dialects, some Standard ML implementations, and Ruby) support the---already very expressive---undelimited continuations---the kind introduced by `call/cc`{.scheme}---and even fewer implement the more expressive delimited continuations. Continuations (and tail recursion) can, in a first-class, functional way, express _all_ local and non-local control features, and are an integral part of efficient implementations of algebraic effects systems, both as language features and (most importantly) as libraries.
<div style="display: flex">
<div class="text-image">
<div>
Continuations, however, are notoriously hard to understand. In an informal way, we can say that a continuation is a first-class representation of the "future" of a computation. By this I do not mean a future in the sense of an asynchronous computation, but in a temporal sense. While descriptions like this are "correct" in some sense, they're also not useful. What does it mean to store the "future" of a computation in a value?
@ -14,7 +14,7 @@ Operationally, we can model continuations as just a segment of control stack. Th
With no offense to the authors of these articles, some of which I greatly respect as programming language designers and implementers, I do not think that this approach is very productive in a functional programming context. This reductionist, imperative view would almost be like explaining the _concept_ of a proper tail call by using a trampoline, rather than presenting trampolines as one particular implementation strategy for the tail-call-ly challenged.
</div>
<figure style="min-width:20%; padding-left: 12pt">
<figure>
<img class="tikzpicture" src="/diagrams/cc/delimcc.svg" />
_Fig 1. A facsimile of a diagram you'd find attached to an explanation of delimited continuations. Newer frames on top._
</figure>


+ 0
- 162
pages/posts/2020-09-09-typing-proof.md View File

@ -1,162 +0,0 @@
---
title: "This Sentence is False, or: On Natural Language, Typing and Proof"
date: September 9th, 2020
---
The Liar's paradox is often the first paradox someone dealing with logic, even in an informal setting, encounters. It is _intuitively_ paradoxical: how can a sentence be both true, and false? This contradicts (ahem) the law of non-contradiction, that states that "no proposition is both true and false", or, symbolically, $\neg (A \land \neg A)$. Appealing to symbols like that gives us warm fuzzy feelings, because, _of course, the algebra doesn't lie!_
There's a problem with that the appeal to symbols, though. And it's nothing to do with non-contradiction: It's to do with well-formedness. How do you accurately translate the "this sentence is false" sentence into a logical formula? We can try by giving it a name, say $L$ (for liar), and state that $L$ must represent some logical formula. Note that the equality symbol $=$ here is _not_ a member of the logic we're using to express $L$, it's a symbol of this discourse. It's _meta_​logical.
$$ L = \dots $$
But what should fill in the dots? $L$ is the sentence we're symbolising, so "this sentence" must mean $L$. Saying "X is false" can be notated in a couple of equivalent ways, such as $\neg X$ or $X \to \bot$. We'll go with the latter: it's a surprise tool that will help us later. Now we know how to fill in the dots: It's $L \to \bot$.
<details>
<summary>Truth tables demonstrating the equivalence between $\neg A$ and $A \to \bot$, if you are classically inclined.</summary>
<div class="mathpar">
<table>
<tr>
<th> $A$ </th>
<th> $\neg A$ </th>
</tr>
<tr><td>$\top$</td><td>$\bot$</td></tr>
<tr><td>$\bot$</td><td>$\top$</td></tr>
</table>
<table>
<tr>
<th> $A$ </th>
<th> $A\to\bot$ </th>
</tr>
<tr><td>$\top$</td><td>$\bot$</td></tr>
<tr><td>$\bot$</td><td>$\top$</td></tr>
</table>
</div>
</details>
But wait. If $L = L \to \bot$, then $L = (L \to \bot) \to \bot$, and also $L = ((L \to \bot) \to \bot) \to \bot$, and so... forever. There is no finite, well-formed formula of first-order logic that represents the sentence "This sentence is false", thus, assigning a truth value to it is meaningless: Saying "This sentence is false" is true is just as valid as saying that it's false, both of those are as valid as saying "$\neg$ is true".
Wait some more, though: we're not done. It's known, by the [Curry-Howard isomorphism], that logical systems correspond to type systems. Therefore, if we can find a type-system that assigns a meaning to our sentence $L$, then there _must_ exist a logical system that can express $L$, and so, we can decide its truth!
Even better, we don't need to analyse the truth of $L$ logically, we can do it type-theoretically: if we can build an inhabitant of $L$, then it is true; If we can build an inhabitant of $\neg L$, then it's false; And otherwise, I'm just not smart enough to do it.
So what is the smallest type system that lets us assign a meaning to $L$?
# A system of equirecursive types: $\lambda_{\text{oh no}}$[^1]
[^1]: The reason for the name will become obvious soon enough.
We do not need a complex type system to express $L$: a simple extension over the basic simply-typed lambda calculus $\lambda_{\to}$ will suffice. No fancy higher-ranked or dependent types here, sorry!
As a refresher, the simply-typed lambda calculus has _only_:
* A set of base types $\mathbb{B}$,
* Function types $\tau \to \sigma$,
* For each base type $b \in \mathbb{B}$, a set of base terms $\mathbb{T}_b$,
* Variables $v$,
* Lambda abstractions $\lambda v. e$, and
* Application $e\ e'$.
<details>
<summary>Type assignment rules for the basic $\lambda_{\to}$ calculus.</summary>
<div class="math-paragraph">
<div>
$$\frac{x : \tau \in \Gamma}{\Gamma \vdash x : \tau}$$
</div>
<div>
$$\frac{b \in \mathbb{B} \quad x \in \mathbb{T}_{b}}{\Gamma \vdash x : b}$$
</div>
<div>
$$\frac{\Gamma, x : \sigma \vdash e : \tau}{\Gamma \vdash \lambda x. e : \sigma \to \tau}$$
</div>
<div>
$$\frac{\Gamma, e : \sigma \to \tau \quad \Gamma \vdash e' : \sigma}{\Gamma \vdash e\ e' : \tau}$$
</div>
</div>
</details>
First of all, we'll need a type to represent the logical proposition $\bot$. This type is empty: It has no type formers. Its elimination rule corresponds to the principle of explosion, and we write it $\mathtt{absurd}$. The inference rule:
<div class="math-paragraph">
$$\frac{\Gamma \vdash e : \bot}{\mathtt{absurd}\ e : A}$$
</div>
We're almost there. What we need now is a type former that serves as a solution for equations of the form $v = ... v ...$. That's right: we're just _inventing_ a solution to this class of equations---maths!
These are the _equirecursive_ types, $\mu a. \tau$. The important part here is _equi_: these types are entirely indistinguishable from their unrollings. Formally, we extend the set of type formers with type variables $a$ and $\mu$-types $\mu a. \tau$, where $\mu a$ acts as a binder for $a$.
Since we invented $\mu$ types as a solution for equations of the form $a = \tau$, we have that $\mu a. \tau = \tau[\mu a.\tau/a]$, where $\tau[\sigma{}/a]$ means "substitute $\sigma{}$ everywhere $a$ occurs in $\tau$". The typing rules express this identity, saying that anywhere a term might have one as a type, the other works too:
<div class="math-paragraph">
<div>
$$\frac{\Gamma \vdash e : \tau[\mu a.\tau / a]}{\Gamma \vdash e : \mu a. \tau}$$
</div>
<div>
$$\frac{\Gamma \vdash e : \mu a.\tau}{\Gamma \vdash e : \tau[\mu a. \tau / a]}$$
</div>
</div>
Adding these rules, along with the one for eliminating $\bot$, to the $\lambda_{\to}$ calculus nets us the system $\lambda_{\text{oh no}}$. With it, one can finally formulate a representation for our $L$-sentence: it's $\mu a. a \to \bot$.
There exists a closed term of this type, namely $\lambda k. k\ k$, which means: The "this sentence is false"-sentence is true. We can check this fact ourselves, or, more likely, use a type checker that supports equirecursive types. For example, OCaml with the `-rectypes` compiler option does.
We'll first define the empty type `void` and the type corresponding to $L$:
<div class="math-paragraph">
~~~~{.ocaml}
type void ;;
type l = ('a -> void) as 'a ;;
~~~~
</div>
Now we can define our proof of $L$, called `yesl`, and check that it has the expected type:
<div class="math-paragraph">
~~~~{.ocaml}
let yesl: l = fun k -> k k ;;
~~~~
</div>
However. This same function is also a proof that... $\neg L$. Check it out:
<div class="math-paragraph">
~~~~{.ocaml}
let notl (x : l) : void = x x ;;
~~~~
</div>
# I am Bertrand Russell
Bertrand Russell (anecdotally) once proved, starting from $1 = 0$, that he was the Pope. I am also the Pope, as it turns out, since I have on hand a proof that $L$ and $\neg L$, in violation of non-contradiction; By transitivity, I am Bertrand Russell. <span style="float: right; display: inline-block;"> $\blacksquare$ </span>
Alright, maybe I'm not Russell (drat). But I am, however, a trickster. I tricked you! You thought that this post was going to be about a self-referential sentence, but it was actually about typed programming language design (not very shocking, I know). It's a demonstration of how recursive types (in any form) are logically inconsistent, and of how equirecursive types _are wrong_.
The logical inconsistency, we all deal with, on a daily basis. It comes with Turing completeness, and it annoys me to no end every single time I accidentally do `let x = ... x ...`{.haskell}. I _really_ wish I had a practical, total functional programming language to use for my day-to-day programming, and this non-termination _everywhere_ is a great big blotch on Haskell's claim of purity.
The kind of recursive types you get in Haskell is _fine_. They're not _great_ if you like the propositions-as-types interpretation, since it's trivial to derive a contradiction from them, but they're good enough for programming that implementing a positivity checker to ensure your definitions are strictly inductive isn't generally worth the effort.
Unless your language claims to have "zero runtime errors", in which case, if you implement isorecursive types instead of inductive types, you are _wrong_. See: Elm. God damn it.
<details>
<summary>So much for "no runtime errors"... I guess spinning forever on the client side is acceptable.</summary>
<div class="flex-list">
```elm
-- Elm
type Void = Void Void
type Omega = Omega (Omega -> Void)
yesl : Omega
yesl = Omega (\(Omega x) -> x (Omega x))
notl : Omega -> Void
notl (Omega x) = x (Omega x)
```
</div>
</details>
Equirecursive types, however, are a totally different beast. They are _basically_ useless. Sure, you might not have to write a couple of constructors, here and there... at the cost of _dramatically_ increasing the set of incorrect programs that your type system accepts. Suddenly, typos will compile fine, and your program will just explode at runtime (more likely: fail to terminate). Isn't this what type systems are meant to prevent?
Thankfully, very few languages implement equirecursive types. OCaml is the only one I know of, and it's gated behind a compiler flag. However, that's a footgun that should _not_ be there.
[Curry-Howard isomorphism]: https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence

+ 2
- 2
pages/posts/2020-10-30-reflecting-equality.md View File

@ -95,9 +95,9 @@ The elements of $\mathrm{Prop}$ are taken to be _propositions_, not in the sense
Given $A : u$, and some $B : v$ with one variable $x : A$ free (where $u$ and $v$ are possibly distinct universes), we can form the type $\prod_{x : A} B$ of _dependent products_ from $A$ to $B$. If $v$ is in $\mathrm{Prop}$, then the dependent product also lives in $\mathrm{Prop}$. Otherwise, it lives in $\mathrm{Set}$. Moreover, we can also form the type $\sum_{x : A} B$ of _dependent sums_ of $A$ and $B$, which always lives in $\mathrm{Set}$.
Given a type $A$ and two elements $a, b : A$, we can form the _proposition_ $a \equiv_{A} b$. Note the emphasis on the word proposition here! Since equivalence is a proposition, we have uniqueness of equality proofs by definition: there's at most one way for things to be equal, conflicting with univalence. So we get _some_ extensionality, namely of functions, but not for arbitrary types. Given types <span class=together>$A$ and $B$,</span> a proof $p : A \equiv B$ and $x : A$, we have the term $\mathrm{coe}(A, B, P, x) : B$, which represents the *coe*rcion of $x$ along the path $p$.
Given a type $A$ and two elements $a, b : A$, we can form the _proposition_ $a \equiv_{A} b$. Note the emphasis on the word proposition here! Since equivalence is a proposition, we have uniqueness of equality proofs by definition: there's at most one way for things to be equal, conflicting with univalence. So we get _some_ extensionality, namely of functions, but not for arbitrary types. Given types <span class=together>$A$ and $B$,</span> a proof $p : A \equiv B$ and $x : A$, we have the term $\mathrm{coe}(A, B, p, x) : B$, which represents the *coe*rcion of $x$ along the path $p$.
Here is where my presentation of observational equality starts to differentiate from the paper's: McBride et al use _heterogeneous_ equality, i.e. a 4-place relation $(x : A) \equiv (y : B)$, where $A$ and $B$ are potentially distinct types. But! Their system only allows you to _use_ an equality when $A \equiv B$. The main motivation for heterogeneous equality is to "bunch up" as many equalities as possible to be eliminated all in one go, since coercion in their system does not compute. However, if coercion computes normally, then we don't need to do this "bunching": one can just use coercion normally.
Here is where my presentation of observational equality starts to differentiate from the paper's: McBride et al use _heterogeneous_ equality, i.e. a 4-place relation $(x : A) \equiv (y : B)$, where $A$ and $B$ are potentially distinct types. But! Their system only allows you to _use_ an equality when $A \equiv B$. The main motivation for heterogeneous equality is to "bunch up" as many equalities as possible to be eliminated all in one go, since coercion in their system does not compute. However, if coercion computes normally, then we don't need to (and, in fact, can't) do this "bunching": one just uses coercion normally.
The key idea of OTT is to identify as "equal" objects which support the same _observations_: for functions, observation is _application_; for pairs, it's projection, etc. This is achieved by making the definition of equality acts as a "pattern matching function" on the structure of terms and types. For example, there is a rule which says an equality between functions is a function that returns equalities:


+ 13
- 12
pages/posts/2021-01-15-induction.md View File

@ -35,11 +35,11 @@ Before we consider the full induction principle, it's useful to consider a simpl
- A _motive_, the type we elminate into;
- A _method_ for the constructor `zero`, that is, an element of `A`, and
- A _method_ for the constructor `succ`, which is a function from `A → A`.
- A _method_ for the constructor `suc`, which is a function from `A → A`.
To put it into code:
```
```agda
foldNat : (A : Type) → A → (A → A) → Nat → A
```
@ -72,7 +72,7 @@ A morphism between algebras $(N_0, z_0, s_0)$ and $(N_1, z_1, s_1)$ consists of
Suppose for a second that we have a "special" `Nat`{.dt}-algebra, denoted `Nat*`{.dt}. The statement that there exists a recursion principle for the `Nat`{.dt}ural numbers is then a function with the type boxed below.
```
```agda
recursion : (α : Alg) → Morphism Nat* α
```
@ -142,12 +142,13 @@ subst : {ℓ ℓ' : _}
subst B refl x = x
pairPath : {ℓ ℓ' : _}
{A : Set ℓ} {B : A → Set ℓ'}
{x y : A}
{e : B x} {e' : B y}
(p : x ≡ y)
→ subst B p e ≡ e' → (x , e) ≡ (y , e')
pairPath refl refl = refl
{A : Set ℓ}
{B : A → Set ℓ'}
{a b : Σ A B}
(p : (a ₁) ≡ (b ₁))
→ subst B p (a ₂) ≡ (b ₂)
→ a ≡ b
pairPath {_} {_} {A} {B} {(a , b)} {(a' , b')} refl refl = refl
_∘_ : {ℓ : _} {A : Set ℓ} {x y z : A} → y ≡ z → x ≡ y → x ≡ z
refl ∘ refl = refl
@ -429,7 +430,7 @@ Complicating it further: Induction-induction
We've seen, in the past 2 thousand-something words, how to build algebras, algebra homomorphisms, displayed algebras and algebra sections for inductive types, both indexed and not. Now, we'll complicate it further: Induction-induction allows the formation of two inductive families of types $A : \mathrm{Set}$ and $B : A \to \mathrm{Set}$ _together_, such that the constructors of A can refer to those of B and vice-versa.
The classic example is defining a type together with an inductively-defined predicate on it, for instance defining sorted lists together with a "less than all elements" predicate in one big induction. However, in the interest of brevity, I'll consider a simpler example: A syntax for contexts and Π-types in type theory. We have one base type, `ι`, which is valid in any context, and a type for dependent products `Π`. If `σ` is a type in `Γ` and `τ` a type in the context `Γ, σ`, then `Π σ τ` is also a type in `Γ`.
The classic example is defining a type together with an inductively-defined predicate on it, for instance defining sorted lists together with a "less than all elements" predicate in one big induction. However, in the interest of brevity, I'll consider a simpler example: A syntax for contexts and Π-types in type theory. We have one base type, `ι`, which is valid in any context, and a type for dependent products `Π`. Its type expresses the rule for $\prod$-introduction, which says that given $\Gamma \vdash \sigma\ \mathrm{type}$ and $\Gamma, \sigma \vdash \tau\ \mathrm{type}$, then $\Gamma \vdash (\prod(\sigma) \tau)\ \mathrm{type}$.
```agda
data Ctx : Set
@ -444,7 +445,7 @@ data Ty where
Π : {Γ : Ctx} (σ : Ty Γ) (τ : Ty (pop Γ σ)) → Ty Γ
```
Here I'm using Agda's forward-declarationthe definition of their algebras feature to specify the signatures of `Ctx` and `Ty` before its constructors. This is because the constructors of `Ctx` mention the type `Ty`, and the type _of_ `Ty` mentions `Ctx`, so there's no way to untangle them other than this.
Here I'm using Agda's forward-declaration feature to specify the signatures of `Ctx` and `Ty` before their constructors. This is because the constructors of `Ctx` mention the type `Ty`, and the type _of_ `Ty` mentions `Ctx`, so there's no good way to untangle them.
When talking about our standard set of categorical gizmos for inspecting values of inductive type, it's important to note that, just like we can't untangle the definitions of `Ctx` and `Ty`, we can't untangle their algebras either. Instead of speaking of `Ctx`-algebras or `Ty`-algebras, we can only talk of `Ctx-Ty`-algebras.[^2] Let's think through the algebras first, since the rest are derived from that.
@ -546,7 +547,7 @@ I-Alg-Morphism (I0 , l0 , r0 , seg0) (I1 , l1 , r1 , seg1) =
Σ (I r0 ≡ r1) λ r →
```
Now the type of the fourth component is interesting. We want `seg0` and `seg1` to be "equal" in some appropriate sense of equality. But they have totally different types! `seg0 : l0 ≡I0 r0` and `seg1 : l1 ≡I1 r1`. It's easy enough to fix the type of `seg0` to be a path in `I0`: `ap I seg0` is a path in `I1` between `I l0` and `I r0`. Out of the pan, into the fire, though. Now the endpoints don't match up!
Now the type of the fourth component is interesting. We want `seg0` and `seg1` to be "equal" in some appropriate sense of equality. But they have totally different types! `seg0 : l0 ≡I0 r0` and `seg1 : l1 ≡I1 r1`. It's easy enough to fix the type of `seg0` to be a path in `I1`: Since `I : I0 → I1`, its action on paths `ap I seg0` is a path in `I1` between `I l0` and `I r0`. Out of the pan, into the fire, though. Now the endpoints don't match up!
Can we fix `I l0` and `I r0` in the endpoints of `seg0`? The answer, it turns out, is yes. We can use path _transitivity_ to alter both endpoints. `r` correctly lets us go from `I r0` to `r1`, but `l` is wrong—it takes `l1` to `I r0`. We want that backwards, so we apply _symmetry_ here.


+ 242
- 90
site.hs View File

@ -1,45 +1,54 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BlockArguments #-}
import qualified Data.ByteString.Lazy.Char8 as BS
import Data.Functor
import Control.DeepSeq (rnf)
import Control.Concurrent
import Control.Exception
import Control.DeepSeq (rnf)
import Control.Monad
import Text.Pandoc.Highlighting
import Text.Pandoc.Options
import Text.Pandoc.Definition
import Text.Sass.Functions
import Text.Pandoc.Walk (query, walkM)
import qualified Data.ByteString.Lazy.Char8 as BS
import Data.ByteString.Lazy.Char8 (pack, unpack)
import qualified Data.HashMap.Strict as HMap
import qualified Data.Text.Encoding as T
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import Data.Functor
import Data.Monoid
import Data.Binary
import Data.Maybe
import Data.Aeson
import Data.List
import Data.Char
import Hakyll.Core.Compiler.Internal
import Hakyll.Core.Compiler
import Hakyll.Web.Sass
import Hakyll
import qualified Skylighting as Sky
import Data.ByteString.Lazy.Char8 (pack, unpack)
import qualified Network.URI.Encode as URI (encode)
import qualified Skylighting as Sky
import System.Directory
import System.Environment
import System.Process
import System.Exit
import System.IO
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Hakyll.Core.Compiler.Internal
import Data.List
import Data.Char
import qualified Data.Map.Strict as Map
import Data.Monoid
import Text.Pandoc.Walk (query, walkM, walk)
import Text.Pandoc.Highlighting
import Text.Pandoc.Definition
import Text.Pandoc.Options
import Text.Sass.Functions
import Debug.Trace
import Data.Maybe
import Data.Text (Text)
import Data.IORef
import Data.Hashable (Hashable (hashWithSalt))
import GHC.Stack
import Text.Read (readMaybe)
readerOpts :: ReaderOptions
readerOpts = def { readerExtensions = pandocExtensions
@ -75,36 +84,85 @@ conf = def { destinationDirectory = ".site"
, tmpDirectory = ".store/tmp"
, deployCommand = "./sync" }
tikzFilter :: Block -> Compiler Block
tikzFilter (CodeBlock (id, "tikzpicture":extraClasses, namevals) contents) =
(imageBlock . T.pack . ("data:image/svg+xml;utf8," <>) . URI.encode . filter (/= '\n') . T.unpack . itemBody <$>) $
makeItem contents
>>= loadAndApplyTemplate (fromFilePath "templates/tikz.tex") (bodyField "body") . fmap T.unpack
>>= withItemBody (pure . BS.fromStrict . T.encodeUtf8
>=> unixFilterLBS "rubber-pipe" ["--pdf"]
>=> unixFilterLBS "pdftocairo" ["-svg", "-", "-"]
>=> pure . T.decodeUtf8 . BS.toStrict)
. fmap T.pack
where imageBlock fname = Para [Image (id, "tikzpicture":extraClasses, namevals) [] (fname, "")]
tikzFilter x = return x
katexFilter :: Pandoc -> Compiler Pandoc
katexFilter (Pandoc meta doc) = do
id <- compilerUnderlying <$> compilerAsk
t <- getMetadata id
case lookupString "fastbuild" t of
Just _ -> pure (Pandoc meta doc)
Nothing -> Pandoc meta <$> walkM go doc
katexFilter :: IORef KatexCache -> Pandoc -> Compiler Pandoc
katexFilter cacheVar (Pandoc meta doc) =
do
initCache <- unsafeCompiler (readIORef cacheVar)
id <- compilerUnderlying <$> compilerAsk
t <- getMetadata id
invalidateCache id (abbrevs t) cacheVar
doc <- Pandoc meta <$> walkM (go (show id) (abbrevs t)) doc
unsafeCompiler $ flushCache cacheVar
pure doc
where
go :: Inline -> Compiler Inline
go (Math kind math) = unsafeCompiler $ do
let args =
case kind of
DisplayMath -> ["-Std"]
InlineMath -> ["-St"]
(contents, _) <- readProcessBS "node_modules/.bin/katex" args . BS.fromStrict . T.encodeUtf8 $ math
pure . RawInline "html" . T.init . T.init . T.decodeUtf8 . BS.toStrict $ contents
go x = pure x
abbrevs :: HMap.HashMap Text Value -> [String]
abbrevs x =
case HMap.lookup "abbreviations" x of
Just (Object map) -> concat $ mapMaybe oneAbbrev (HMap.toList map)
_ -> []
oneAbbrev (x, String t) = Just ["-m", '\\':T.unpack x ++ ':':T.unpack t]
oneAbbrev _ = Nothing
go :: String -> [String] -> Inline -> Compiler Inline
go id abbrevs (Math kind math) = unsafeCompiler $ do
cache <- readIORef cacheVar
case HMap.lookup (id, kind, math) (spanMap cache) of
Just x -> pure (RawInline "html" x)
Nothing -> do
let args = flip (:) abbrevs $ case kind of { DisplayMath -> "-td"; InlineMath -> "-t" }
(contents, _) <- readProcessBS "node_modules/.bin/katex" args . BS.fromStrict . T.encodeUtf8 $ math
let text = T.init . T.init . T.decodeUtf8 . BS.toStrict $ contents
atomicModifyIORef' cacheVar (\m -> (bumpCacheEntry cache id abbrevs kind text math, ()))
pure $ RawInline "html" text
go id _ x = pure x
bumpCacheEntry (KatexCache spans depends abbrevVars) id abbrevs kind text math =
let
str = T.unpack text
usedAbbrevs = map (\x -> (T.pack (takeWhile (/= ':') (tail x)), T.pack (tail (dropWhile (/= ':') (tail x)))))
$ filter (\x -> (takeWhile (/= ':') (tail x)) `isInfixOf` str)
$ filter (not . isPrefixOf "-") abbrevs
addDeps [] x = x
addDeps ((k, _):xs) vl = HMap.alter (\v -> Just (maybe [(id, kind, math)] ((id, kind, math):) v)) (id, k) $ addDeps xs vl
recordVars [] x = x
recordVars ((k, v):xs) x = HMap.insert (id, k) v (recordVars xs x)
in
case usedAbbrevs of
[] -> KatexCache (HMap.insert (id, kind, math) text spans) depends abbrevVars
xs -> KatexCache (HMap.insert (id, kind, math) text spans) (addDeps xs depends) (recordVars xs abbrevVars)
abbreviationFilter :: Pandoc -> Compiler Pandoc
abbreviationFilter (Pandoc meta doc) =
do
id <- compilerUnderlying <$> compilerAsk
t <- getMetadata id
case HMap.lookup "abbreviations" t of
Just (Object map) -> do
pure (Pandoc meta (walk (replace map) doc))
_ -> pure (Pandoc meta doc)
where
replace map x =
case x of
Str t | Just (e, r) <- entity t -> fromMaybe (Str t) (toInline r =<< HMap.lookup e map)
x -> x
toInline r (String t) = Just (Str (t <> r))
toInline _ _ = Nothing
entity x
| T.isPrefixOf "&" x && T.length x >= 3 =
let
(name, rest') = T.span (/= ';') (T.tail x)
rest = T.tail rest'
in pure (name, rest)
| otherwise = Nothing
estimateReadingTime :: Pandoc -> Pandoc
estimateReadingTime (Pandoc meta doc) = Pandoc meta doc' where
@ -116,24 +174,41 @@ estimateReadingTime (Pandoc meta doc) = Pandoc meta doc' where
doc' = RawBlock "html" ("<span id=reading-length>" <> wordCount <> "</span>")
: doc
addLanguageTag :: Block -> Block
addLanguageTag block@(CodeBlock (identifier, classes@(language:classes'), kv) text) =
Div
( mempty
, "code-container":if haskv then "custom-tag":classes' else classes'
, []
)
[Plain [Span (mempty, [], []) [Str tag]], block]
addLanguageTag :: Pandoc -> Pandoc
addLanguageTag (Pandoc meta blocks) = Pandoc meta (map go blocks) where
go :: Block -> Block
go block@(CodeBlock (identifier, classes@(language:classes'), kv) text) =
Div
( mempty
, "code-container":if haskv then "custom-tag":classes' else classes'
, []
)
[block, Div (mempty, ["code-tag"], []) [Plain [Span (mempty, [], []) [Str tag]]]]
where
language' = case T.uncons language of
Nothing -> mempty
Just (c, cs) -> T.cons (toUpper c) cs
tag = fromMaybe language' (lookup "tag" kv)
haskv = fromMaybe False (True <$ lookup "tag" kv)
go block@(CodeBlock (identifier, [], kv) text) = Div (mempty, ["code-container"], []) [block]
go x = x
saveSynopsys :: Pandoc -> Compiler Pandoc
saveSynopsys (Pandoc meta doc) =
do
id <- getUnderlying
n <- fromMaybe (1 :: Int) . readMaybe . fromMaybe "" . lookupString "synopsys" <$> getMetadata id
case dropWhile (not . isParagraph) doc of
p:ps -> do
saveSnapshot "synopsys" =<< makeItem (take n (p:ps))
pure ()
[] -> pure ()
pure $ Pandoc meta doc
where
language' = case T.uncons language of
Nothing -> mempty
Just (c, cs) -> T.cons (toUpper c) cs
tag = fromMaybe language' (lookup "tag" kv)
haskv = fromMaybe False (True <$ lookup "tag" kv)
addLanguageTag block@(CodeBlock (identifier, [], kv) text) = Div (mempty, ["code-container"], []) [block]
addLanguageTag x = x
isParagraph Para{} = True
isParagraph _ = False
sassImporter :: SassImporter
sassImporter = SassImporter 0 go where
@ -146,15 +221,26 @@ sassImporter = SassImporter 0 go where
} ]
go _ _ = pure []
setup :: IO (IORef KatexCache)
setup = do
setEnv "AMC_LIBRARY_PATH" "/usr/lib/amuletml/lib/"
loadCache
compiler :: IORef KatexCache -> Compiler (Item String)
compiler katexCache = do
wops <- writerOptions
pandocCompilerWithTransformM readerOpts wops $
katexFilter katexCache
>=> abbreviationFilter
>=> saveSynopsys
>=> pure . estimateReadingTime
>=> pure . addLanguageTag
main :: IO ()
main = (*>) (setEnv "AMC_LIBRARY_PATH" "/usr/lib/amuletml/lib/") $ hakyllWith conf $ do
let compiler = do
wops <- writerOptions
pandocCompilerWithTransformM readerOpts wops $
walkM tikzFilter
>=> katexFilter
>=> pure . estimateReadingTime
>=> walkM (pure . addLanguageTag)
main = setup >>= \katexCache -> hakyllWith conf $ do
match "static/*" do
route idRoute
compile copyFileCompiler
match "static/**/*" $ do
route idRoute
@ -178,25 +264,34 @@ main = (*>) (setEnv "AMC_LIBRARY_PATH" "/usr/lib/amuletml/lib/") $ hakyllWith co
route $ setExtension "svg"
compile $ getResourceBody
>>= loadAndApplyTemplate "templates/tikz.tex" (bodyField "body")
>>= withItemBody (return . pack
>>= withItemBody (return . pack
>=> unixFilterLBS "rubber-pipe" ["--pdf"]
>=> unixFilterLBS "pdftocairo" ["-svg", "-", "-"]
>=> return . unpack)
match "pages/posts/*" $ do
match "pages/posts/*" do
route $ metadataRoute pathFromTitle
compile $ compiler
>>= loadAndApplyTemplate "templates/post.html" postCtx
>>= saveSnapshot "content"
>>= loadAndApplyTemplate "templates/default.html" postCtx
>>= relativizeUrls
compile $ do
wops <- writerOptions
id <- getUnderlying
r <- compiler katexCache
>>= loadAndApplyTemplate "templates/post.html" postCtx
>>= saveSnapshot "content"
>>= loadAndApplyTemplate "templates/default.html" postCtx
>>= relativizeUrls
loadSnapshot id "synopsys" >>= saveSnapshot "synopsys" . writePandocWith wops . fmap (Pandoc mempty)
pure r
match "pages/posts/*.lhs" $ version "raw" $ do
route idRoute
compile copyFileCompiler
create ["archive.html"] $ do
route idRoute
compile $ do
posts <- recentFirst =<< loadAll "pages/posts/*"
posts <- recentFirst =<< onlyPublic =<< loadAll ("pages/posts/*" .&&. hasNoVersion)
let archiveCtx =
listField "posts" postCtx (return posts) <>
constField "title" "Archives" <>
@ -209,7 +304,7 @@ main = (*>) (setEnv "AMC_LIBRARY_PATH" "/usr/lib/amuletml/lib/") $ hakyllWith co
match "pages/*.html" $ do
route $ gsubRoute "pages/" (const "")
compile $ do
posts <- fmap (take 5) . recentFirst =<< loadAll "pages/posts/*"
posts <- fmap (take 5) . recentFirst =<< onlyPublic =<< loadAll ("pages/posts/*" .&&. hasNoVersion)
let indexCtx =
listField "posts" postCtx (return posts) <>
constField "title" "Home" <>
@ -220,8 +315,8 @@ main = (*>) (setEnv "AMC_LIBRARY_PATH" "/usr/lib/amuletml/lib/") $ hakyllWith co
>>= relativizeUrls
match "pages/*.md" $ do
route $ gsubRoute "pages/" (const "") <> setExtension "html"
compile $ compiler
route $ setExtension "html" <> gsubRoute "pages/" (const "")
compile $ compiler katexCache
>>= loadAndApplyTemplate "templates/default.html" defaultContext
>>= relativizeUrls
@ -241,13 +336,26 @@ main = (*>) (setEnv "AMC_LIBRARY_PATH" "/usr/lib/amuletml/lib/") $ hakyllWith co
route idRoute
compile $ do
let feedCtx = postCtx <> bodyField "description"
posts <- (take 10 <$>) . recentFirst =<< loadAllSnapshots "pages/posts/*" "content"
posts <- fmap (take 10) . recentFirst =<< onlyPublic =<< loadAllSnapshots ("pages/posts/*" .&&. hasNoVersion) "synopsys"
renderRss rssfeed feedCtx posts
onlyPublic :: [Item String] -> Compiler [Item String]
onlyPublic = filterM isPublic where
isPublic item = do
t <- getMetadata (itemIdentifier item)
case HMap.lookup "public" t of
Just (Bool False) -> pure False
_ -> pure True
postCtx :: Context String
postCtx =
dateField "date" "%B %e, %Y"
<> synopsysField
<> defaultContext
where
synopsysField = field "synopsys" $ \x -> do
let id = itemIdentifier x
itemBody <$> loadSnapshot id "synopsys"
readProcessBS :: FilePath -> [String] -> BS.ByteString -> IO (BS.ByteString, String)
readProcessBS path args input =
@ -305,7 +413,7 @@ pathFromTitle meta =
case lookupString "title" meta of
Just s -> s
Nothing -> error "post has no title?"
title = filter (/= "") . map (filter isAlphaNum . map toLower) . words $ titleString
(category, title') =
@ -318,4 +426,48 @@ pathFromTitle meta =
Nothing -> constRoute (category (intercalate "-" title' <> ".html"))
foldMapM :: (Monad w, Monoid m, Foldable f) => (a -> w m) -> f a -> w m
foldMapM k = foldr (\x y -> do { m <- k x; (m <>) <$> y }) (pure mempty)
foldMapM k = foldr (\x y -> do { m <- k x; (m <>) <$> y }) (pure mempty)
loadCache :: HasCallStack => IO (IORef KatexCache)
loadCache = do
t <- doesFileExist ".katex_cache"
let fixup (a, b, c) = KatexCache (HMap.fromList a) (HMap.fromList b) (HMap.fromList c)
map <- if t
then (fixup <$> decodeFile ".katex_cache") `catch` \e ->
const (print e *> pure (KatexCache mempty mempty mempty)) (e :: SomeException)
else pure (KatexCache mempty mempty mempty)
var <- newIORef map
pure var
flushCache :: IORef KatexCache -> IO ()
flushCache var = do
KatexCache x y z <- readIORef var
Data.Binary.encodeFile ".katex_cache" (HMap.toList x, HMap.toList y, HMap.toList z)
invalidateCache :: Identifier -> [String] -> IORef KatexCache -> Compiler KatexCache
invalidateCache id abbrevs cacheVar = unsafeCompiler $ atomicModifyIORef' cacheVar (\x -> (go x, go x)) where
currentValues = map (\x -> (T.pack (takeWhile (/= ':') (tail x)), T.pack (tail (dropWhile (/= ':') (tail x)))))
$ filter (not . isPrefixOf "-") abbrevs
ident = show id
go (KatexCache spanMap abbrKeys abbrText) =
let
go (abbr, val) (spanMap, abbrKeys, abbrText) =
case HMap.lookup (ident, abbr) abbrText of
Just vl | vl /= val ->
let l = HMap.lookupDefault [] (ident, abbr) abbrKeys
in (foldr HMap.delete spanMap l, abbrKeys, abbrText)
_ -> (spanMap, abbrKeys, abbrText)
(a, b, c) = foldr go (spanMap, abbrKeys, abbrText) currentValues
in KatexCache a b c
data KatexCache
= KatexCache { spanMap :: HMap.HashMap (String, MathType, Text) Text
, abbreviationKeys :: HMap.HashMap (String, Text) [(String, MathType, Text)]
, abbreviationText :: HMap.HashMap (String, Text) Text
}
instance Hashable MathType where
hashWithSalt s DisplayMath = hashWithSalt s (0 :: Int)
hashWithSalt s InlineMath = hashWithSalt s (1 :: Int)

+ 1
- 1
stack.yaml View File

@ -3,4 +3,4 @@ resolver: lts-16.20
extra-deps:
- hakyll-sass-0.2.4@sha256:4d2fbd8b63f5ef15483fc6dda09e10eefd63b4f873ad38dec2a3607eb504a8ba,939
- hsass-0.8.0@sha256:05fb3d435dbdf9f66a98db4e1ee57a313170a677e52ab3a5a05ced1fc42b0834,2899
- hlibsass-0.1.10.1@sha256:08db56c633e9a83a642d8ea57dffa93112b092d05bf8f3b07491cfee9ee0dfa5,2565
- hlibsass-0.1.10.1@sha256:08db56c633e9a83a642d8ea57dffa93112b092d05bf8f3b07491cfee9ee0dfa5,2565

BIN
static/pfp.jpg View File

Before After
Width: 1500  |  Height: 1500  |  Size: 948 KiB

+ 1
- 6
sync View File

@ -1,8 +1,3 @@
#!/usr/bin/env bash
if rg fastbuild pages/posts >/dev/null; then
echo "posts still have fast build, remove and rebuild then repush"
exit 1
fi
rsync .site/ "${SYNC_SERVER}:/var/www/abby.how/" -vuar0
rsync .site/ "ahti:/home/abby/abby.how" -vuar0

+ 17
- 18
templates/default.html View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
@ -7,14 +7,22 @@
<title>$title$</title>
<link rel="stylesheet" type="text/css" href="/css/default.css" />
<link rel="stylesheet" type="text/css" href="/css/code.css" />
<link rel="stylesheet" type="text/css" href="/css/katex.min.css" />
<link rel="apple-touch-icon" sizes="180x180" href="/static/icon/apple-touch-icon.png">
<!-- lazily load code and KaTeX css -->
<link rel="stylesheet" type="text/css" href="/css/katex.min.css" media="disabled" onload="this.media='all'" />
<link rel="stylesheet" type="text/css" href="/css/code.css" media="disabled" onload="this.media='all'" />
<!-- fallback for noscript users -->
<noscript>
<link rel="stylesheet" type="text/css" href="/css/katex.min.css" />
<link rel="stylesheet" type="text/css" href="/css/code.css" />
</noscript>
<link rel="apple-touch-icon" sizes="180x180" href="/static/icon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/icon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/icon/favicon-16x16.png">
<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="Blog feed for abby.how" />
<link rel="alternate" type="application/rss+xml" href="/feed.xml" title="Blog feed for abby.how" />
<meta property="og:type" content="website">
<meta property="og:title" content="$title$">
@ -23,19 +31,9 @@
<noscript>
<style> .script { display: none } </style>
</noscript>
$if(fastbuild)$
<style>
.math {
color: red;
}
</style>
$endif$
</head>
<body>
<a rel="me" href="https://functional.cafe/@hydraz" id=mastodon></a>
<header>
<div id="header">
<div id="logo">
@ -43,19 +41,20 @@
</div>
<nav>
<div id="navigation">
<div class="nav-button"><a href="/contact.html">Contact</a></div>
<div class="nav-button"><a href="/pages/contact.html">Contact</a></div>
<div class="nav-button"><a href="/archive.html">Archive</a></div>
<div class="nav-button"><a href="/oss.html">Licenses</a></div>
<div class="nav-button"><a href="/pages/oss.html">Open-Source</a></div>
<div class="nav-button"><a href="/feed.xml">RSS</a></div>
</div>
</nav>
</div>
</header>
<div id="content">
<h1>$title$</h1>
$body$
</div>
<footer>
<div id="footer" style="display: flex; justify-content: space-between;">
<div>


+ 14
- 9
templates/post-list.html View File

@ -1,10 +1,15 @@
<ul class="post-list">
$for(posts)$
<li>
<div class="post-list-item">
<a href="$url$">$title$</a>
$date$
</div>
</li>
$endfor$
</ul>
$for(posts)$
<li>
<div class="post-list-item">
<div class="post-list-header">
<a href="$url$">$title$</a>
$date$
</div>
<div clas="post-list-synopsys">
$synopsys$
</div>
</div>
</li>
$endfor$
</ul>

+ 1
- 13
templates/post.html View File

@ -1,21 +1,9 @@
<div class="info">
Posted on $date$ <br />
Comment on this post <a href="#comments">here</a>
</div>
<article>
$body$
</article>
<hr />
<div id="comments">
<script src="https://utteranc.es/client.js"
repo="plt-hokusai/blag"
issue-term="og:title"
label="comment"
theme="github-light"
crossorigin="anonymous"
async>
</script>
</div>
<hr />

+ 21
- 1
templates/tikz.tex View File

@ -2,15 +2,35 @@
\usepackage[pdftex,active,tightpage]{preview}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{mathrsfs}
\usepackage{tikz}
\usetikzlibrary{matrix}
\usetikzlibrary{calc}
\usetikzlibrary{fit}
\usetikzlibrary{arrows}
\usetikzlibrary{arrows.meta}
\usetikzlibrary{decorations.pathreplacing}
\usepackage{xcolor}
\usepackage{tgpagella}
\definecolor{red}{RGB}{235, 77, 75}
\definecolor{blue}{RGB}{9, 123, 227}
\tikzset{
ncbar angle/.initial=90,
ncbar/.style={
to path=(\tikztostart)
-- ($$(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$$)
-- ($$(\tikztotarget)!($$(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$$)!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztostart)$$)
-- (\tikztotarget)
},
ncbar/.default=0.5cm,
}
\tikzset{square left brace/.style={ncbar=0.5cm}}
\tikzset{square right brace/.style={ncbar=-0.5cm}}
\tikzset{round left paren/.style={ncbar=0.5cm,out=120,in=-120}}
\tikzset{round right paren/.style={ncbar=0.5cm,out=60,in=-60}}
\begin{document}
\begin{preview}
\begin{tikzpicture}[auto]


Loading…
Cancel
Save