Compare commits
10 Commits
music-theo
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bdd2be48bd | |||
| a350cd14f8 | |||
| 49ab3982f1 | |||
| 4d35ca04fe | |||
| 5e117e3f48 | |||
| a6f3cd3f9a | |||
| ccc8d6f0eb | |||
| 7a088d6739 | |||
| 626baefd76 | |||
| 4602d02720 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1 @@
|
||||
**/build/*
|
||||
|
||||
/.quarto/
|
||||
**/*.quarto_ipynb
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
format:
|
||||
gfm:
|
||||
variant: +yaml_metadata_block
|
||||
12
agda.rb
12
agda.rb
@@ -46,7 +46,17 @@ class AgdaContext
|
||||
# assumes that links can't span multiple pages, and that links
|
||||
# aren't nested, so ensure that the parent of the textual node
|
||||
# is the preformatted block itself.
|
||||
raise "unsupported Agda HTML output" if at.parent.name != "pre"
|
||||
if at.parent.name != "pre"
|
||||
# Costmetic highlight warnings are sometimes applied to newlines.
|
||||
# If they don't have content, treat them as normal newlines at the
|
||||
# top level.
|
||||
#
|
||||
# This is an <a class="CosmeticProblem">\n</a> node.
|
||||
unless at.parent.name == "a" and at.parent['class'] == "CosmeticProblem" and at.content.strip.empty?
|
||||
raise "unsupported Agda HTML output in file #{file} at line #{line} (content #{at.content.inspect})"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Increase the line and track the final offset. Written as a loop
|
||||
# in case we eventually want to add some handling for the pieces
|
||||
|
||||
19
assets/scss/onspiders.scss
Normal file
19
assets/scss/onspiders.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
@import "variables.scss";
|
||||
@import "mixins.scss";
|
||||
|
||||
.spiderweb {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
opacity: 0.25;
|
||||
max-width: 900px;
|
||||
|
||||
@include below-container-width {
|
||||
width: $container-width-threshold;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
@@ -1,51 +1,3 @@
|
||||
@import "variables.scss";
|
||||
|
||||
body {
|
||||
background-color: #1c1e26;
|
||||
--text-color: white;
|
||||
font-family: $font-code;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
text-align: left;
|
||||
font-family: $font-code;
|
||||
}
|
||||
|
||||
h1::after {
|
||||
content: "(writing)";
|
||||
font-size: 1rem;
|
||||
margin-left: 0.5em;
|
||||
position: relative;
|
||||
bottom: -0.5em;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
nav .container {
|
||||
justify-content: flex-start;
|
||||
|
||||
a {
|
||||
padding-left: 0;
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.header-divider {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: auto;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
content: "* * *";
|
||||
color: $primary-color;
|
||||
font-size: 2rem;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code for the CSS glitch effect. Originally from: https://codepen.io/mattgrosswork/pen/VwprebG */
|
||||
|
||||
.glitch {
|
||||
|
||||
47
assets/scss/writing.scss
Normal file
47
assets/scss/writing.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
@import "variables.scss";
|
||||
|
||||
body {
|
||||
background-color: #1c1e26;
|
||||
--text-color: white;
|
||||
font-family: $font-code;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
text-align: left;
|
||||
font-family: $font-code;
|
||||
}
|
||||
|
||||
h1::after {
|
||||
content: "(writing)";
|
||||
font-size: 1rem;
|
||||
margin-left: 0.5em;
|
||||
position: relative;
|
||||
bottom: -0.5em;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
nav .container {
|
||||
justify-content: flex-start;
|
||||
|
||||
a {
|
||||
padding-left: 0;
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.header-divider {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: auto;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
content: "* * *";
|
||||
color: $primary-color;
|
||||
font-size: 2rem;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
718
assets/svg/spiderweb.svg
Normal file
718
assets/svg/spiderweb.svg
Normal file
@@ -0,0 +1,718 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="197.21727mm"
|
||||
height="106.16592mm"
|
||||
viewBox="0 0 197.21727 106.16592"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
|
||||
sodipodi:docname="spiderweb.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#000000"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.2119635"
|
||||
inkscape:cx="292.91312"
|
||||
inkscape:cy="235.56816"
|
||||
inkscape:window-width="1728"
|
||||
inkscape:window-height="996"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="mainlayer" /><defs
|
||||
id="defs1" /><g
|
||||
inkscape:groupmode="layer"
|
||||
id="mainlayer"
|
||||
inkscape:label="Main Layer"
|
||||
transform="translate(-12.908849,0.44133637)"><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 107.50107,0.48134806 -4.01124,4.49258204 c 0,0 5.61573,31.9294199 14.28,37.0637999 8.66426,5.134379 7.54112,5.294828 7.54112,5.294828"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ccsc" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 173.22006,0.15397339 85.91715,39.263214"
|
||||
id="path3" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 183.22833,-0.15397339 c 0,0 -49.11751,32.33441039 -64.66882,56.50823339"
|
||||
id="path4" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 185.53793,-0.30794677 c 0,0 -48.80956,45.57612277 -56.50823,61.43538277"
|
||||
id="path5" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 184.76806,4.1572814 c 0,0 -42.1887,53.8906846 -31.41057,70.2118646"
|
||||
id="path6" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 190.46508,0 c 0,0 -31.10262,53.428764 -18.93873,84.99331"
|
||||
id="path7" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 191.85084,-0.15397339 c 0,0 -17.55297,60.81948839 -3.23344,92.69197939"
|
||||
id="path8" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 193.54455,-0.15397339 c 0,0 -6.62086,54.50657839 16.32117,86.53304339"
|
||||
id="path9" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 196.31607,-0.30794677 c 0,0 6.00496,36.33771977 13.54965,48.19366877"
|
||||
id="path10" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 197.39388,0.15397339 209.86572,26.483423"
|
||||
id="path11" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 198.62567,0.30794677 209.55778,15.551312"
|
||||
id="path12" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 115.28177,0.20224871 c 0,0 -2.52811,22.24735829 -0.30337,25.68558729"
|
||||
id="path13" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 120.33799,1.415741 0.30337,11.932674 v 0 c 0,0 -1.21349,6.269711 -1.21349,10.213561"
|
||||
id="path14" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 120.64136,13.752913 c 0,0 1.92136,7.584327 2.02249,8.59557"
|
||||
id="path15" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 125.89983,0.80899486 c 0,0 0,9.50569014 5.05621,17.79788714"
|
||||
id="path16" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 126.91107,10.112436 c 0,0 -0.4045,5.865213 0.70787,10.314684"
|
||||
id="path17" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 128.63018,0.20224871 134.39427,17.494514"
|
||||
id="path18" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 135.30439,0.30337307 c 0,0 0.60675,9.91018693 2.42698,15.57315093"
|
||||
id="path19" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 139.75386,0.7078705 c 0,0 -1.41574,3.94385 1.61799,13.7529125"
|
||||
id="path20" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 143.09097,1.1123679 c 0,0 1.82024,8.3933216 3.84272,11.2248041"
|
||||
id="path21" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 144.30446,6.0674614 c 0,0 -0.809,2.9326065 -0.30337,6.7753316"
|
||||
id="path22" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 147.54044,0.40449743 148.75393,10.618058"
|
||||
id="path23" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 150.87754,0.20224871 3.43823,7.78657559"
|
||||
id="path24" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 153.2034,0.10112436 3.3371,6.37083444"
|
||||
id="path25" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 155.52926,0.20224871 3.33711,5.66296389"
|
||||
id="path26" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 159.06861,0.50562179 161.4956,4.7528448"
|
||||
id="path27" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 162.00122,0.40449743 164.02371,3.94385"
|
||||
id="path28" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 164.4282,-0.30337307 1.92137,3.33710377"
|
||||
id="path29" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 115.28177,26.797955 c 0,0 -2.02249,11.427052 11.93267,16.786643"
|
||||
id="path30" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 120.03461,24.269846 c 0,0 3.84273,11.022554 12.74167,14.86528"
|
||||
id="path31" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 126.30432,21.33724 c 0,0 0.4045,9.404565 9.00007,15.067529"
|
||||
id="path32" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 134.69765,17.797887 c 0,0 -4.85397,8.494446 5.15734,14.460782"
|
||||
id="path33" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 137.428,16.078773 c 0,0 0.30337,6.26971 5.05622,13.95516"
|
||||
id="path34" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 140.15836,14.15741 c 0,0 -1.01124,6.370835 4.75285,13.854038"
|
||||
id="path35" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 143.29321,13.045042 c 0,0 -0.91011,7.584327 4.34835,12.640545"
|
||||
id="path36" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 146.32695,12.842793 c 0,0 -2.32586,4.044975 3.13485,11.325928"
|
||||
id="path37" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 147.54044,11.427052 c 0,0 0.20225,7.17983 4.75284,10.820307"
|
||||
id="path38" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 149.76517,9.9101872 c 0,0 1.41574,8.4944458 3.94385,10.3146838"
|
||||
id="path39" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 151.58541,9.7079384 c 0,0 2.02249,6.4719586 5.25847,8.5955706"
|
||||
id="path40" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 153.2034,9.0000679 c 0,0 2.12361,5.9663371 4.85397,7.8877001"
|
||||
id="path41" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 154.31577,8.2921974 5.86521,7.0787046"
|
||||
id="path42" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 209.52546,105.56381 12.995207,0"
|
||||
id="path43" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 158.24027,7.5068802 4.05133,6.7919388"
|
||||
id="path44" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 160.24291,5.8928883 3.88494,7.1587677"
|
||||
id="path45" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 162.81832,4.4087534 3.71034,6.8968616"
|
||||
id="path46" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 165.48103,3.666686 3.01192,6.416701"
|
||||
id="path47" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 167.88184,2.4008064 2.79367,5.9365392"
|
||||
id="path48" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 171.19932,0.52381229 2.1389,5.98019051"
|
||||
id="path49" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 173.42552,0.43651024 1.70239,4.62700856"
|
||||
id="path50" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 176.17554,0.43651024 1.30953,3.14287386"
|
||||
id="path51" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 176.52474,4.6270087 1.39684,1.7460409"
|
||||
id="path52" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 174.56045,5.9365393 1.83334,1.920645"
|
||||
id="path53" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 171.85408,7.8571843 2.70637,2.0952494"
|
||||
id="path54" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 170.23899,9.4286212 2.09526,2.4881088"
|
||||
id="path55" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 167.00882,11.654823 3.49208,2.051599"
|
||||
id="path56" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 165.00087,12.702448 3.44843,2.793666"
|
||||
id="path57" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 163.25483,14.317536 3.31748,3.011921"
|
||||
id="path58" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 161.02863,15.32151 4.23415,3.797639"
|
||||
id="path59" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 158.80243,17.89692 3.75399,3.579384"
|
||||
id="path60" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 155.6159,18.900894 5.84924,4.103196"
|
||||
id="path61" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 154.48098,21.432653 5.10717,3.317478"
|
||||
id="path62" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 151.94922,22.698533 4.58335,4.845264"
|
||||
id="path63" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 149.06825,25.361245 6.19844,4.190498"
|
||||
id="path64" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 148.23888,26.539822 c 0,0 1.74604,3.492082 4.71431,5.674635"
|
||||
id="path65" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 144.57219,28.635073 c 0,0 2.35716,2.575409 6.54766,4.583356"
|
||||
id="path66" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 142.6952,30.992228 c 0,0 3.66669,4.365101 6.2421,5.019868"
|
||||
id="path67" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 138.63565,33.916847 c 0,0 4.23415,3.186523 8.25005,4.059544"
|
||||
id="path68" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 137.10787,35.008121 c 0,0 4.1905,5.89289 7.15877,6.023843"
|
||||
id="path69" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 134.88167,37.758137 c 0,0 2.79366,4.32145 6.72225,5.500028"
|
||||
id="path70" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 132.69912,39.373225 c 0,0 3.53573,5.325425 7.50797,6.111142"
|
||||
id="path71" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 130.77847,41.512124 c 0,0 3.66669,6.02384 7.20242,7.289721"
|
||||
id="path72" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 128.42131,43.781977 c 0,0 2.44446,5.369076 7.07147,6.984164"
|
||||
id="path73" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 127.06813,45.528018 c 0,0 2.70637,7.420676 6.11115,8.555601"
|
||||
id="path74" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 124.49272,48.452637 c 0,0 2.92462,6.373051 6.94051,7.988139"
|
||||
id="path75" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 182.0798,2.2845037 2.38172,2.0414713"
|
||||
id="path76" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 183.92685,5.4925301 2.2845,1.7498327 2.81918,2.0900778"
|
||||
id="path77" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 188.7875,10.110144 4.03433,-0.729097 4.71483,-1.3123744"
|
||||
id="path78" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 197.68248,7.8256402 2.38171,-0.8749163"
|
||||
id="path79" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 200.55026,5.055072 1.60401,-1.0693422"
|
||||
id="path80" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 201.6196,3.8399103 204.29296,0"
|
||||
id="path81" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 203.32083,6.9021176 c 0,0 3.35384,-2.7219619 4.18015,-6.65908529"
|
||||
id="path82" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 204.92484,8.4089178 c 0,0 3.45106,-2.2358971 4.61761,-7.3881821"
|
||||
id="path83" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 180.76743,4.4717943 1.84705,2.6247489"
|
||||
id="path84" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 182.46866,7.1451498 2.57614,2.7219619"
|
||||
id="path85" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 184.80176,10.547602 3.49967,1.992865"
|
||||
id="path86" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 188.44725,13.220958 4.37458,-0.194426"
|
||||
id="path87" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 192.67601,13.804235 5.88138,-1.992865"
|
||||
id="path88" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 198.55739,10.742028 3.0136,-1.0693422"
|
||||
id="path89" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 203.51525,7.5340016 -1.94426,1.6526195"
|
||||
id="path90" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 205.11927,9.4782599 -2.57615,2.0414711"
|
||||
id="path91" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 177.65661,7.5340016 2.43033,2.0900776"
|
||||
id="path92" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 180.23276,10.450389 3.0622,2.770569"
|
||||
id="path93" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 183.10054,14.14448 4.27737,2.381717"
|
||||
id="path94" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 188.39864,15.748494 4.13155,0.09721"
|
||||
id="path95" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 193.3079,15.991526 5.44392,-1.360981"
|
||||
id="path96" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 199.57813,15.408248 4.76343,-0.243032"
|
||||
id="path97" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 204.04992,13.220958 2.77057,-1.360981"
|
||||
id="path98" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 206.62607,10.596209 2.96499,-3.7913043"
|
||||
id="path99" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 207.74402,12.929319 1.70122,-2.284504"
|
||||
id="path100" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 175.71236,9.5754729 2.81917,2.3331101"
|
||||
id="path101" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 178.04547,13.609809 4.13155,2.867781"
|
||||
id="path102" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 182.37144,16.428984 4.81204,2.235897 5.2495,0.09721"
|
||||
id="path103" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 192.28716,18.713488 7.4854,-0.82631"
|
||||
id="path104" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 200.21001,19.296765 4.95786,-1.701226"
|
||||
id="path105" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 205.4109,15.311035 2.77057,-1.5068"
|
||||
id="path106" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 200.8419,20.852172 -7.97146,1.5068"
|
||||
id="path107" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 186.50299,22.164546 6.85351,0.194426"
|
||||
id="path108" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 180.71882,19.393978 5.83278,2.770568"
|
||||
id="path109" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 176.49006,15.408248 4.71483,3.499665"
|
||||
id="path110" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 173.96252,11.568338 2.91639,3.208026"
|
||||
id="path111" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 171.62941,13.46399 3.30524,3.15942"
|
||||
id="path112" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 174.88605,18.178817 4.76343,3.062207"
|
||||
id="path113" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 179.40645,23.039463 7.14515,2.18729"
|
||||
id="path114" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 185.87111,26.150276 6.70769,-0.534671"
|
||||
id="path115" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 200.79329,23.428314 -8.2631,2.187291"
|
||||
id="path116" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 207.74402,22.504792 c 0,0 -5.00647,2.867781 -5.88139,3.062207"
|
||||
id="path117" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 168.42139,16.185952 5.2981,2.624749"
|
||||
id="path118" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 166.91459,17.692752 4.76343,3.888517"
|
||||
id="path119" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 172.65015,20.657746 5.63835,3.402452"
|
||||
id="path120" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 178.48292,26.053063 7.04794,1.652619"
|
||||
id="path121" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 185.28783,28.726418 7.63121,-0.68049"
|
||||
id="path122" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 201.71681,26.393308 -8.79777,1.604014"
|
||||
id="path123" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 202.49452,29.066664 193.3565,31.74002"
|
||||
id="path124" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 185.23922,31.399773 8.2145,-0.14582"
|
||||
id="path125" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 176.68449,29.455514 8.65195,1.166556"
|
||||
id="path126" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 169.9768,25.858637 6.41605,3.256634"
|
||||
id="path127" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 171.09474,23.379708 c 0,0 3.93713,3.353845 6.41605,3.645484"
|
||||
id="path128" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 165.16475,20.074469 c 0,0 3.30524,3.645484 5.34672,3.985729"
|
||||
id="path129" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 162.88025,21.678482 c 0,0 4.4718,4.325975 6.56187,4.569007"
|
||||
id="path130" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 160.49853,23.914379 c 0,0 4.90926,3.791303 7.14515,4.131549"
|
||||
id="path131" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 175.42072,31.691413 c 0,0 -4.61762,-0.388853 -7.77704,-3.499665"
|
||||
id="path132" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 184.80176,33.100999 c 0,0 -7.14515,-0.194424 -9.13801,-1.604013"
|
||||
id="path133" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 184.65594,35.142471 8.79778,-1.263769"
|
||||
id="path134" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 203.32083,31.5942 -10.20736,2.478929"
|
||||
id="path135" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 166.91459,29.892974 c 0,0 -5.58975,-1.020736 -8.06868,-4.180156"
|
||||
id="path136" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 166.37992,30.524857 c 0,0 6.12441,3.548272 8.84637,3.839911"
|
||||
id="path137" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 184.51013,36.114601 c 0,0 -9.33244,-2.041472 -9.67269,-2.624749"
|
||||
id="path138" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 184.36431,37.47558 c 0,0 8.36031,-0.437457 9.0408,-0.729096"
|
||||
id="path139" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 204.34156,33.975915 c 0,0 -10.01293,3.402455 -10.83924,3.402455"
|
||||
id="path140" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 204.6332,36.211814 -11.13088,3.305238"
|
||||
id="path141" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 183.73242,41.023854 9.96433,-1.701228"
|
||||
id="path142" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 173.52507,38.544923 10.20735,2.478931"
|
||||
id="path143" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 174.4972,36.697877 9.86711,1.992866"
|
||||
id="path144" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 164.97033,33.635672 9.13801,2.867781"
|
||||
id="path145" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 156.36699,29.018058 8.94358,3.353845"
|
||||
id="path146" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 164.24123,34.899438 c 0,0 -8.3117,-2.916385 -9.18662,-5.152284"
|
||||
id="path147" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 173.71949,38.787956 c 0,0 -7.97146,-2.138685 -9.96432,-4.714827"
|
||||
id="path148" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 206.33443,38.593529 -12.20022,2.867782"
|
||||
id="path149" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 183.97545,42.530654 9.7213,-1.166556"
|
||||
id="path150" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 172.74736,42.093194 c 0,0 9.04081,1.798439 10.5476,0.923523"
|
||||
id="path151" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 162.58861,37.864433 c 0,0 7.19376,4.034338 10.15875,3.694091"
|
||||
id="path152" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 153.20756,32.12887 c 0,0 6.22163,5.881383 9.08941,5.686956"
|
||||
id="path153" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 150.72864,34.559195 c 0,0 8.11727,5.443923 10.20735,5.395317"
|
||||
id="path154" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 149.31905,35.725748 c 0,0 6.70769,7.290969 10.79063,6.075809"
|
||||
id="path155" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 171.58081,46.516382 c 0,0 -4.76344,1.749835 -11.66555,-4.277368"
|
||||
id="path156" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 183.00333,48.071791 c 0,0 -4.81204,1.117949 -11.2767,-1.701226"
|
||||
id="path157" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 194.91191,46.710808 c 0,0 -9.42965,2.819176 -11.47113,1.749833"
|
||||
id="path158" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 194.37724,43.94024 -10.74203,0.729099"
|
||||
id="path159" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 160.64435,41.995981 11.13088,2.527538"
|
||||
id="path160" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 183.19775,46.224745 171.82384,44.572126"
|
||||
id="path161" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 206.43164,41.169672 194.23142,44.08606"
|
||||
id="path162" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 207.35516,43.11393 -12.73489,3.694092"
|
||||
id="path163" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 208.08426,45.301222 195.54379,49.57859"
|
||||
id="path164" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 147.28059,37.851884 c 0,0 7.4801,7.80252 11.22015,6.448364"
|
||||
id="path165" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 145.41056,39.850877 c 0,0 5.28766,7.99597 11.86499,7.480102"
|
||||
id="path166" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 142.44432,43.139542 c 0,0 6.38387,6.899749 13.41259,7.738033"
|
||||
id="path167" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 141.15465,44.751632 c 0,0 3.09521,8.576321 12.70327,12.058439"
|
||||
id="path168" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 139.67152,46.621657 c 0,0 4.51385,13.283626 12.63879,15.991938"
|
||||
id="path169" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 137.35011,49.00755 c 0,0 8.18942,11.607054 15.41159,9.93048"
|
||||
id="path170" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 135.86699,51.071028 c 0,0 -3.67557,8.124936 15.02468,15.218135"
|
||||
id="path171" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 131.74004,56.874552 2.64383,8.124936"
|
||||
id="path172" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 146.76472,71.641302 4.57834,-2.192443"
|
||||
id="path173" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 131.4821,56.229718 5.28766,0.709318"
|
||||
id="path174" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 157.72694,45.58992 13.15465,4.320402"
|
||||
id="path175" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 157.27555,47.00856 c 0,0 9.60806,5.739043 13.15466,4.642821"
|
||||
id="path176" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 156.56623,49.00755 c 0,0 6.19043,5.739043 12.83224,5.15869"
|
||||
id="path177" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 154.88966,53.198987 14.63778,4.062468"
|
||||
id="path178" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 168.94708,63.387398 c 0,0 -13.67052,-6.319393 -14.31536,-11.284633"
|
||||
id="path179" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 169.14053,59.840799 161.85389,59.32493"
|
||||
id="path180" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 153.5355,56.874552 c 0,0 3.35315,5.094208 14.89572,9.479092"
|
||||
id="path181" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 151.66548,63.838785 c 0,0 13.79949,6.254911 16.83022,5.287655"
|
||||
id="path182" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 168.68915,72.28614 c 0,0 -17.41058,-2.127962 -17.99093,-4.126953"
|
||||
id="path183" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 170.62366,49.910322 c 0,0 11.60705,2.450378 13.02569,1.354156"
|
||||
id="path184" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 170.04331,55.971783 c 0,0 6.83526,-1.289674 12.70327,0.902769"
|
||||
id="path185" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 183.52038,55.907299 c 0,0 12.18741,-0.644837 12.63879,-1.547606"
|
||||
id="path186" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 182.87555,49.587903 c 0,0 12.76775,1.998994 13.15465,3.095215"
|
||||
id="path187" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 170.23676,51.457928 c 0,0 4.3204,-0.19345 5.22317,0.902772 0.90277,1.096221 6.06146,2.063475 7.22217,0.902771"
|
||||
id="path188" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 183.32693,52.038281 4.25592,-1.547606"
|
||||
id="path189" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 190.87151,50.619641 4.19144,-1.354156"
|
||||
id="path190" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 196.61056,54.295209 c 0,0 12.50982,-5.868011 13.34811,-3.933503"
|
||||
id="path191" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 195.12744,52.16725 c 0,0 14.18639,-0.515869 14.63778,-3.997987"
|
||||
id="path192" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 169.72089,57.841808 c 0,0 11.67153,3.159696 13.15466,1.547606"
|
||||
id="path193" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 197.31988,56.939036 -14.37985,3.288665"
|
||||
id="path194" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 197.77126,60.356667 c 0,0 -9.4146,-0.451384 -9.67254,-1.354156"
|
||||
id="path195" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 169.2695,61.517373 c 0,0 13.28363,0 13.67053,1.22519"
|
||||
id="path196" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 168.81812,64.161201 c 0,0 5.93249,-0.902769 14.37985,1.354156"
|
||||
id="path197" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 168.36673,67.965735 14.89572,0.967255"
|
||||
id="path198" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 169.39847,73.769261 13.7995,-2.192443"
|
||||
id="path199" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 168.23777,71.641302 c 0,0 1.48312,5.674559 15.92745,2.643828"
|
||||
id="path200" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 169.33399,76.735508 c 0,0 7.0932,-0.773803 14.31536,10.833248"
|
||||
id="path201" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 184.74557,76.671024 -8.96322,2.192443"
|
||||
id="path202" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 184.55212,79.766238 177.26547,79.44382"
|
||||
id="path203" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 162.11182,80.346591 7.35113,-2.966246"
|
||||
id="path204" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 165.27152,82.023166 4.12695,-2.256928"
|
||||
id="path205" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 171.07504,82.539035 c 0,0 14.25089,-0.128969 14.63779,1.870024"
|
||||
id="path206" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 180.74759,89.890168 c 0,0 4.90075,-3.417634 5.99698,-2.256928"
|
||||
id="path207" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 182.87555,67.901253 15.60503,-4.642821"
|
||||
id="path208" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 199.44784,65.773294 c 0,0 -15.21814,2.837278 -15.66952,2.837278"
|
||||
id="path209" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 182.94003,71.899237 17.3461,-3.224181"
|
||||
id="path210" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 183.52038,73.640296 201.5758,70.996465"
|
||||
id="path211" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 183.71383,77.444827 202.3496,73.575811"
|
||||
id="path212" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 185.06799,80.153141 c 0,0 19.47405,-2.063477 21.08614,-0.128968"
|
||||
id="path213" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 203.70376,77.057926 -7.02872,2.385894"
|
||||
id="path214" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 186.35766,85.440797 c 0,0 9.67254,2.450377 13.54156,1.80554"
|
||||
id="path215" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 188.1632,90.470518 209.37831,86.02115"
|
||||
id="path216" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 186.6156,87.568756 c 0,0 17.34609,-2.127959 20.44131,-3.804534"
|
||||
id="path217" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 197.70678,57.068005 12.25189,-2.966246"
|
||||
id="path218" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 198.09368,60.55012 11.86499,-3.804534"
|
||||
id="path219" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 199.06094,62.613595 10.63979,-2.643828"
|
||||
id="path220" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 199.77026,65.773294 c 0,0 8.83425,-4.255921 9.8015,-3.546599"
|
||||
id="path221" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 200.86648,68.610572 c 0,0 4.38488,1.354156 8.83425,-1.483122"
|
||||
id="path222" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 201.70476,71.060949 2.32141,-2.063474"
|
||||
id="path223" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 202.99444,74.220646 6.96423,-3.030728"
|
||||
id="path224" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 205.1224,79.314854 4.64282,-2.64383"
|
||||
id="path225" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 208.34658,83.635256 1.41864,-0.838287"
|
||||
id="path226" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 29.690113,9.1690057 175.30265,0"
|
||||
id="path227" /></g></svg>
|
||||
|
After Width: | Height: | Size: 43 KiB |
BIN
content/blog/i_love_programming_languages/cardeli-products.png
Normal file
BIN
content/blog/i_love_programming_languages/cardeli-products.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
415
content/blog/i_love_programming_languages/index.md
Normal file
415
content/blog/i_love_programming_languages/index.md
Normal file
@@ -0,0 +1,415 @@
|
||||
---
|
||||
title: "Reasons to Love the Field of Programming Languages"
|
||||
date: 2025-12-31
|
||||
tags: ["Programming Languages", "Compilers", "Type Systems"]
|
||||
---
|
||||
|
||||
I work at HPE on the
|
||||
[Chapel Programming Language](https://chapel-lang.org). Recently, another HPE
|
||||
person asked me:
|
||||
|
||||
> So, you work on the programming language. What's next for you?
|
||||
|
||||
This caught me off-guard because I hadn't even conceived of moving on.
|
||||
I don't want to move on, because __I love the field of programming languages__.
|
||||
In addition, I have come to think there is something in PL for everyone, from
|
||||
theorists to developers to laypeople.
|
||||
So, in that spirit, I am writing this list as a non-exhaustive survey that holds
|
||||
the dual purpose of explaining my personal infatuation with PL, and providing
|
||||
others with ways to engage with PL that align with their existing interests.
|
||||
I try to provide rationale for each claim, but you can just read the reasons
|
||||
themselves and skip the rest.
|
||||
|
||||
My general thesis goes something like this: programming languages are a unique
|
||||
mix of the __inherently human and social__ and the __deeply mathematical__,
|
||||
a mix that often remains deeply grounded in the practical, __low-level realities of
|
||||
our hardware__.
|
||||
|
||||
Personally, I find all of these properties equally important, but we have to
|
||||
start somewhere. Let's begin with the human aspect of programming languages.
|
||||
|
||||
### Human Aspects of PL
|
||||
|
||||
> Programs must be written for people to read, and only incidentally for machines
|
||||
> to execute.
|
||||
>
|
||||
> --- Abelson & Sussman, _Structure and Interpretation of Computer Programs_.
|
||||
|
||||
As we learn more about the other creatures that inhabit our world, we discover
|
||||
that they are similar to us in ways that we didn't expect. However, our
|
||||
language is unique to us. It gives us the ability to go far beyond
|
||||
the simple sharing of information: we communicate abstract concepts,
|
||||
social dynamics, stories. In my view, storytelling is our birthright more
|
||||
so than anything else.
|
||||
|
||||
I think this has always been reflected in the broader discipline of programming.
|
||||
_Code should always tell a story_, I've heard throughout my education and career.
|
||||
_It should explain itself_. In paradigms such as
|
||||
[literate programming](https://en.wikipedia.org/wiki/Literate_programming),
|
||||
we explicitly mix prose and code. Notebook technologies
|
||||
like [Jupyter](https://jupyter.org/) intersperse computation with explanations
|
||||
thereof.
|
||||
|
||||
* __Reason 1__: programming languages provide the foundation of expressing
|
||||
human thought and stories through code.
|
||||
|
||||
From flowery prose to clinical report, human expression takes a wide variety
|
||||
of forms. The need to vary our descriptions is also well-served by the diversity
|
||||
of PL paradigms. From stateful transformations in languages like Python and C++,
|
||||
through pure and immutable functions in Haskell and Lean, to fully declarative
|
||||
statements-of-fact in Nix, various languages have evolved to
|
||||
support the many ways in which we wish to describe our world and our needs.
|
||||
|
||||
* __Reason 2__: diverse programming languages enable different perspectives
|
||||
and ways of storytelling, allowing us choice in how to express our thoughts
|
||||
and solve our problems.
|
||||
|
||||
Those human thoughts of ours are not fundamentally grounded in logic,
|
||||
mathematics, or anything else. They are a product of millennia of evolution
|
||||
through natural selection, of adaptation to ever-changing conditions.
|
||||
Our cognition is limited, rife with blind spots, and partial to the subject
|
||||
matter at hand. We lean on objects, actors, contracts, and more as helpful,
|
||||
mammal-compatible analogies. I find this to be beautiful; here is something
|
||||
we can really call ours.
|
||||
|
||||
* __Reason 3__: programming languages imbue the universe's fundamental rules of
|
||||
computation with humanity's identity and idiosyncrasies. They carve out
|
||||
a home for us within impersonal reality.
|
||||
|
||||
Storytelling (and, more generally, writing) is not just about communicating
|
||||
with others. Writing helps clarify one's own thoughts, and to think deeper.
|
||||
In his 1979 Turing Award lecture,
|
||||
[Notation as a Tool of Thought](https://www.eecg.utoronto.ca/~jzhu/csc326/readings/iverson.pdf),
|
||||
Kenneth Iverson, the creator of [APL](https://tryapl.org/), highlighted ways
|
||||
in which programming languages, with their notation, can help express patterns
|
||||
and facilitate thinking.
|
||||
|
||||
Throughout computing history, programming languages built abstractions that ---
|
||||
together with advances in hardware --- made it possible to create ever more
|
||||
complex software. Dijkstra's
|
||||
[structured programming](https://en.wikipedia.org/wiki/Structured_programming)
|
||||
crystallized the familiar patterns of `if`/`else` and `while` out of
|
||||
a sea of control flow. Structures and objects partitioned data and state
|
||||
into bundles that could be reasoned about, or put out of mind when irrelevant.
|
||||
Recently, I dare say that notions of ownership and lifetimes popularized
|
||||
by Rust have clarified how we think about memory.
|
||||
|
||||
* __Reason 4__: programming languages combat complexity, and give us tools to
|
||||
think and reason about unwieldy and difficult problems.
|
||||
|
||||
The fight against complexity occurs on more battlegrounds than PL design alone.
|
||||
Besides its syntax and semantics, a programming language is comprised of its
|
||||
surrounding tooling: its interpreter or compiler, perhaps its package manager
|
||||
or even its editor. Language designers and developers take great care to
|
||||
[improve the quality of error messages](https://elm-lang.org/news/compiler-errors-for-humans),
|
||||
to provide [convenient editor tooling](https://chapel-lang.org/blog/posts/chapel-lsp/),
|
||||
and build powerful package managers
|
||||
like [Yarn](https://yarnpkg.com/). Thus, in each language project, there is
|
||||
room for folks who, even if they are not particularly interested in grammars or
|
||||
semantics, care about the user experience.
|
||||
|
||||
* __Reason 5__: programming languages provide numerous opportunities for
|
||||
thoughtful forays into the realms of User Experience and Human-Computer
|
||||
Interaction.
|
||||
|
||||
I hope you agree, by this point, that programming languages are fundamentally
|
||||
tethered to the human. Like any human endeavor, then, they don't exist in
|
||||
isolation. To speak a language, one usually wants a partner who understands
|
||||
and speaks that same language. Likely, one wants a whole community, topics
|
||||
to talk about, or even a set of shared beliefs or mythologies. This desire
|
||||
maps onto the realm of programming languages. When using a particular PL,
|
||||
you want to talk to others about your code, implement established design patterns,
|
||||
use existing libraries.
|
||||
|
||||
I mentioned mythologies earlier. In some ways, language
|
||||
communities do more than share know-how about writing code. In many
|
||||
cases, I think language communities rally around ideals embodied by their
|
||||
language. The most obvious example seems to be Rust. From what I've seen,
|
||||
the Rust community believes in language design that protects its users
|
||||
from the pitfalls of low-level programming. The Go community
|
||||
believes in radical simplicity. Julia actively incorporates contributions from
|
||||
diverse research projects into an interoperable set of scientific packages.
|
||||
|
||||
* __Reason 6__: programming languages are complex collaborative social projects
|
||||
that have the power to champion innovative ideas within the field of
|
||||
computer science.
|
||||
|
||||
So far, I've presented interpretations of the field of PL as tools for expression and thought,
|
||||
human harbor to the universe's ocean, and collaborative social projects.
|
||||
These interpretations coexist and superimpose, but they are only a fraction of
|
||||
the whole. What has kept me enamored with PL is that it blends these human
|
||||
aspects with a mathematical ground truth, through fundamental connections to
|
||||
computation and mathematics.
|
||||
|
||||
### The Mathematics of PL
|
||||
|
||||
> Like buses: you wait two thousand years for a definition of “effectively
|
||||
> calculable”, and then three come along at once.
|
||||
>
|
||||
> --- Philip Wadler, _Propositions as Types_
|
||||
|
||||
There are two foundations,
|
||||
[lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus) and
|
||||
[Turing machines](https://en.wikipedia.org/wiki/Turing_machine), that underpin
|
||||
most modern PLs. The abstract notion of Turing machines
|
||||
is closely related to, and most similar among the "famous" computational models,
|
||||
to the
|
||||
[von Neumann Architecture](https://en.wikipedia.org/wiki/Von_Neumann_architecture).
|
||||
Through bottom-up organization of "control unit instructions" into
|
||||
"structured programs" into the imperative high-level languages today, we can
|
||||
trace the influence of Turing machines in C++, Python, Java, and many others.
|
||||
At the same time, and running on the same hardware functional programming
|
||||
languages like Haskell represent a chain of succession from the lambda calculus,
|
||||
embellished today with types and numerous other niceties. These two lineages
|
||||
are inseparably linked: they have been mathematically proven to be equivalent.
|
||||
They are two worlds coexisting.
|
||||
|
||||
The two foundations have a crucial property in common: they are descriptions
|
||||
of what can be computed. Both were developed initially as mathematical formalisms.
|
||||
They are rooted not only in pragmatic concerns of "what can I do with
|
||||
these transistors?", but in the deeper questions of "what can be done
|
||||
with a computer?".
|
||||
|
||||
* __Reason 7__: general-purpose programming languages are built on foundations of computation,
|
||||
and wield the power to compute anything we consider "effectively computable at all".
|
||||
|
||||
Because of these mathematical beginnings, we have long had precise and powerful
|
||||
ways to talk about what code written in a particular language _means_.
|
||||
This is the domain of _semantics_. Instead of reference implementations
|
||||
of languages (CPython for Python, `rustc` for Rust), and instead of textual
|
||||
specifications, we can explicitly map constructs in languages either to
|
||||
mathematical objects ([denotational semantics](https://en.wikipedia.org/wiki/Denotational_semantics))
|
||||
or to (abstractly) execute them ([operational semantics](https://en.wikipedia.org/wiki/Operational_semantics)).
|
||||
|
||||
To be honest, the precise and mathematical nature of these tools is, for me,
|
||||
justification enough to love them. However, precise semantics for languages
|
||||
have real advantages. For one, they allow us to compare programs' real
|
||||
behavior with what we _expect_, giving us a "ground truth" when trying to
|
||||
fix bugs or evolve the language. For another, they allow us to confidently
|
||||
make optimizations: if you can _prove_ that a transformation won't affect
|
||||
a program's behavior, but make it faster, you can safely use it. Finally,
|
||||
the discipline of formalizing programming language semantics usually entails
|
||||
boiling them down to their most essential components. Stripping the
|
||||
[syntax sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) helps clarify
|
||||
how complex combinations of features should behave together.
|
||||
|
||||
Some of these techniques bear a noticeable resemblance to the study of
|
||||
semantics in linguistics. Given our preceding discussion on the humanity
|
||||
of programming languages, perhaps that's not too surprising.
|
||||
|
||||
* __Reason 8__: programming languages can be precisely formalized, giving
|
||||
exact, mathematical descriptions of how they should work.
|
||||
|
||||
In talking about how programs behave, we run into an important limitation
|
||||
of reasoning about Turing machines and lambda calculus, stated precisely in
|
||||
[Rice's theorem](https://en.wikipedia.org/wiki/Rice%27s_theorem):
|
||||
all non-trivial semantic properties of programs (termination, throwing errors)
|
||||
are undecidable. There will always be programs that elude not only human analysis,
|
||||
but algorithmic understanding.
|
||||
|
||||
It is in the context of this constraint that I like to think about type systems.
|
||||
The beauty of type systems, to me, is in how they tame the impossible.
|
||||
Depending on the design of a type system, a well-typed program may well be
|
||||
guaranteed not to produce any errors, or produce only the "expected" sort of
|
||||
errors. By constructing reasonable _approximations_ of program
|
||||
behavior, type systems allow us to verify that programs are well-behaved in
|
||||
spite of Rice's theorem. Much of the time, too, we can do so in a way that is
|
||||
straightforward for humans to understand and machines to execute.
|
||||
|
||||
* __Reason 9__: in the face of the fundamentally impossible, type systems
|
||||
pragmatically grant us confidence in our programs for surprisingly little
|
||||
conceptual cost.
|
||||
|
||||
At first, type systems look like engineering formalisms. That
|
||||
may well be the original intention, but in our invention of type systems,
|
||||
we have actually completed a quadrant of a deeper connection: the
|
||||
[Curry-Howard isomorphism](https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence).
|
||||
[Propositions](https://en.wikipedia.org/wiki/Proposition), in the logical sense,
|
||||
correspond one-to-one with types of programs, and proofs of these propositions
|
||||
correspond to programs that have the matching type.
|
||||
|
||||
This is an incredibly deep connection. In adding parametric polymorphism
|
||||
to a type system (think Java generics, or C++ templates without specialization),
|
||||
we augment the corresponding logic with the "for all x" (\(\forall x\)) quantifier.
|
||||
Restrict the copying of values in a way similar to Rust, and you get an
|
||||
[affine logic](https://en.wikipedia.org/wiki/Affine_logic), capable of reasoning about resources and their use.
|
||||
In languages like Agda with [dependent types](https://en.wikipedia.org/wiki/Dependent_type),
|
||||
you get a system powerful enough [to serve as a foundation for mathematics](https://en.wikipedia.org/wiki/Intuitionistic_type_theory).
|
||||
Suddenly, you can write code and mathematically prove properties about that
|
||||
code in the same language. I've done this in my work with
|
||||
[formally-verified static program analysis]({{< relref "series/static-program-analysis-in-agda" >}}).
|
||||
|
||||
This connection proves appealing even from the perspective of "regular"
|
||||
mathematics. We have developed established engineering practices
|
||||
for writing code: review, deployment, documentation. What if we could use
|
||||
the same techniques for doing mathematics? What if, through the deep
|
||||
connection of programming languages to logic, we could turn mathematics
|
||||
into a computer-verified, collaborative endeavor?
|
||||
I therefore present:
|
||||
|
||||
* __Reason 10__: type systems for programming languages deeply correspond
|
||||
to logic, allowing us to mathematically prove properties about code,
|
||||
using code, and to advance mathematics through the practices of software engineering.
|
||||
|
||||
{{< details summary="Bonus meta-reason to love the mathy side of PL!" >}}
|
||||
In addition to the theoretical depth, I also find great enjoyment in the way that PL is practiced.
|
||||
Here more than elsewhere, creativity and artfulness come into
|
||||
play. In PL, [inference rules](https://en.wikipedia.org/wiki/Rule_of_inference) are a
|
||||
lingua franca through which the formalisms I've mentioned above are expressed
|
||||
and shared. They are such a central tool in the field that I've
|
||||
developed [a system for exploring them interactively]({{< relref "blog/bergamot" >}})
|
||||
on this blog.
|
||||
|
||||
In me personally, inference rules spark joy. They are a concise and elegant
|
||||
way to do much of the formal heavy-lifting I described in this section;
|
||||
we use them for operational semantics, type systems, and sometimes more.
|
||||
When navigating the variety and complexity of the many languages and type
|
||||
systems out there, we can count on inference rules to take us directly to
|
||||
what we need to know. This same variety naturally demands flexibility in
|
||||
how rules are constructed, and what notation is used. Though this can sometimes
|
||||
be troublesome (one [paper](https://labs.oracle.com/pls/apex/f?p=LABS%3A0%3A%3AAPPLICATION_PROCESS%3DGETDOC_INLINE%3A%3A%3ADOC_ID%3A959")
|
||||
I've seen describes __27__ different ways of writing the simple operation of substitution in literature!),
|
||||
it also creates opportunities for novel and elegant ways of formalizing
|
||||
PL.
|
||||
|
||||
* __Bonus Reason__: the field of programming languages has a standard technique
|
||||
for expressing its formalisms, which precisely highlights core concepts
|
||||
and leaves room for creative expression and elegance.
|
||||
{{< /details >}}
|
||||
|
||||
I know that mathematics is a polarizing subject. Often, I find myself
|
||||
torn between wanting precision and eschewing overzealous formalism. The
|
||||
cusp between the two is probably determined by my own tolerance for abstraction.
|
||||
Regardless of how much abstraction you are interested in learning about,
|
||||
PL has another dimension, close to the ground: more often than not, our languages
|
||||
need to execute on real hardware.
|
||||
|
||||
### Pragmatics of PL
|
||||
|
||||
Your perfectly-designed language can be completely useless if there is no
|
||||
way to
|
||||
{{< sidenote "right" "execute-note" "execute it" >}}
|
||||
Technically, there are language that don't care if you execute them at all.
|
||||
Many programs in theorem-proving languages like Agda and Rocq exist only
|
||||
to be type-checked. So, you could nitpick this claim; or, you could take
|
||||
it more generally: your language can be useless if there's no
|
||||
way to make it efficiently do what it's been made to do.
|
||||
{{< /sidenote >}} efficiently. Thus, the field of PL subsumes not only
|
||||
the theoretical foundations of languages and their human-centric design; it
|
||||
includes also their realization as software.
|
||||
|
||||
The overall point of this section is that there is much depth to the techniques
|
||||
involved in bringing a programming language to life. If you are a tinkerer
|
||||
or engineer at heart, you will never run out of avenues of exploration.
|
||||
The reasons are all framed from this perspective.
|
||||
|
||||
One fascinating aspect to programming languages is the "direction" from
|
||||
which they have grown. On one side, you have languages that came
|
||||
together from the need to control and describe hardware. I'd say that
|
||||
this is the case for C and C++, Fortran, and others. More often than not,
|
||||
these languages are compiled to machine code. Still subject to human
|
||||
constraints, these languages often evolve more user-facing features as time
|
||||
goes on. On the other side, you have languages developed to enable
|
||||
people to write software, later faced constraints of actually working
|
||||
efficiently. These are languages like Python, Ruby, and JavaScript. These
|
||||
languages are often interpreted (executed by a dedicated program), with
|
||||
techniques such as [just-in-time compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation).
|
||||
There is no one-size-fits-all way to execute a language, and as a result,
|
||||
|
||||
* __Reason 11__: the techniques of executing programming languages are varied
|
||||
and rich. From compilation, to JIT, to interpretation, the field
|
||||
has many sub-disciplines, each with its own know-hows and tricks.
|
||||
|
||||
At the same time, someone whose goal is to actually develop a compiler
|
||||
likely doesn't want to develop everything from scratch. To do so would
|
||||
be a daunting task, especially if you want the compiler to run beyond
|
||||
the confines of a personal machine. CPU [architectures](https://en.wikipedia.org/wiki/Instruction_set_architecture)
|
||||
and operating system differences are hard for any individual to keep up with.
|
||||
Fortunately, we have a gargantuan ongoing effort in the field:
|
||||
the [LLVM Project](https://llvm.org/). LLVM spans numerous architectures
|
||||
and targets, and has become a common back-end for languages like C++
|
||||
(via [Clang](https://clang.llvm.org/get_started.html)), Swift, and Rust.
|
||||
LLVM helps share and distribute the load of keeping up with the ongoing
|
||||
march of architectures and OSes. It also provides a shared playground upon
|
||||
which to experiment with language implementations, optimizations, and more.
|
||||
|
||||
* __Reason 12__: large projects like LLVM enable language designers to
|
||||
lean on decades of precedent to develop a compiler for their language.
|
||||
|
||||
Though LLVM is powerful, it does not automatically grant languages implemented
|
||||
with it good performance. In fact, no other tool does. To make a language
|
||||
run fast requires a deep understanding of the language itself, the hardware
|
||||
upon which it runs, and the tools used to execute it. That is a big ask!
|
||||
Modern computers are extraordinarily complex. Techniques such as
|
||||
[out-of-order execution](https://en.wikipedia.org/wiki/Out-of-order_execution),
|
||||
[caching](https://en.wikipedia.org/wiki/Cache_(computing)#HARDWARE),
|
||||
and [speculative execution](https://en.wikipedia.org/wiki/Speculative_execution)
|
||||
are constantly at play. This means that any program is subject to hard-to-predict
|
||||
and often unintuitive effects. On top of that, depending on your language's
|
||||
capabilities, performance work can often entail working with additional
|
||||
hardware, such as GPUs and NICs, which have their own distinct performance
|
||||
characteristics. This applies both to compiled and interpreted languages.
|
||||
Therefore, I give you:
|
||||
|
||||
* __Reason 13__: improving the performance of a programming language is rife
|
||||
with opportunities to engage with low-level details of the hardware
|
||||
and operating system.
|
||||
|
||||
In the [mathematics section](#the-mathematics-of-pl), we talked about how constructing correct
|
||||
optimizations requires an understanding of the language's semantics. It
|
||||
was one of the practical uses for having a mathematical definition of a language.
|
||||
Reason 13 is where that comes in, but the synthesis is not automatic. In fact,
|
||||
a discipline sits in-between defining how a language behaves and
|
||||
optimizing programs: program analysis. Algorithms that analyze
|
||||
properties of programs such as [reaching definitions](https://en.wikipedia.org/wiki/Reaching_definition)
|
||||
enable optimizations such as [loop-invariant code motion](https://en.wikipedia.org/wiki/Loop-invariant_code_motion),
|
||||
which can have very significant performance impact. At the same time, for an
|
||||
analysis to be correct, it must be grounded in the program's mathematical
|
||||
semantics. There are many fascinating techniques in this discipline,
|
||||
including [ones that use lattice theory](https://cs.au.dk/~amoeller/spa/spa.pdf).
|
||||
|
||||
* __Reason 14__: the sub-discipline of program analysis serves as a grounded
|
||||
application of PL theory to PL practice, enabling numerous optimizations
|
||||
and transformations.
|
||||
|
||||
The programs your compiler generates are software, and, as we just saw,
|
||||
may need to be tweaked for performance. But the compiler and/or interpreter
|
||||
is itself a piece of software, and its own performance. Today's language
|
||||
implementations are subject to demands that hadn't been there historically.
|
||||
For instance, languages are used to provide [language servers](https://microsoft.github.io/language-server-protocol/)
|
||||
to enable editors to give users deeper insights into their code. Today,
|
||||
a language implementation may be called upon every keystroke to provide
|
||||
a typing user live updates. This has led to the introduction of
|
||||
techniques like the [query architecture](https://ollef.github.io/blog/posts/query-based-compilers.html)
|
||||
(see also [salsa](https://github.com/salsa-rs/salsa)) to avoid
|
||||
redundant work and re-used intermediate results. New language implementations
|
||||
like that of [Carbon](https://github.com/carbon-language/carbon-lang)
|
||||
are exploring alternative representations of programs in memory. In
|
||||
short,
|
||||
|
||||
* __Reason 15__: language implementations are themselves pieces of software,
|
||||
subject to unique constraints and requiring careful and innovative
|
||||
engineering.
|
||||
|
||||
### Conclusion
|
||||
|
||||
I've now given a tour of ways in which I found the PL field compelling,
|
||||
organized across three broad categories. There is just one more reason
|
||||
I'd like to share.
|
||||
|
||||
I was 16 years old when I got involved with the world of programming
|
||||
languages and compilers. Though I made efforts to learn about it through
|
||||
literature (the _Dragon Book_, and _Modern Compiler Design_), I simply
|
||||
didn't have the background to find these resources accessible. However, all
|
||||
was not lost. The PL community online has been, and still is, a vibrant and
|
||||
enthusiastic place. I have found it to be welcoming of folks with backgrounds
|
||||
spanning complete beginners and experts alike. Back then, it gave me
|
||||
accessible introductions to anything I wanted. Now, every week I see new
|
||||
articles go by that challenge my intuitions, teach me new things, or take PL
|
||||
ideas to absurd and humorous extremes. So, my final reason:
|
||||
|
||||
* __Reason 16__: the programming languages community is full of brilliant,
|
||||
kind, welcoming and enthusiastic people, who dedicate much of their
|
||||
time to spreading the joy of the field.
|
||||
|
||||
I ❤️ the programming languages community.
|
||||
@@ -1,484 +0,0 @@
|
||||
---
|
||||
title: "Some Music Theory From (Computational) First Principles"
|
||||
date: 2025-09-20T18:36:28-07:00
|
||||
draft: true
|
||||
filters: ["./to-parens.lua"]
|
||||
custom_js: ["playsound.js"]
|
||||
---
|
||||
|
||||
Sound is a perturbation in air pressure that our ear recognizes and interprets.
|
||||
A note, which is a fundamental building block of music, is a perturbation
|
||||
that can be described by a sine wave. All sine waves have a specific and
|
||||
unique frequency. The frequency of a note determines how it sounds (its
|
||||
_pitch_). Pitch is a matter of our perception; however, it happens to
|
||||
correlate with frequency, such that notes with higher frequencies
|
||||
are perceived as higher pitches.
|
||||
|
||||
Let's encode a frequency as a class in Python.
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
import math
|
||||
import colorsys
|
||||
```
|
||||
|
||||
```{python}
|
||||
class Frequency:
|
||||
def __init__(self, hz):
|
||||
self.hz = hz
|
||||
```
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
|
||||
def points_to_polyline(points, color):
|
||||
return """<polyline fill="none" stroke="{color}"
|
||||
stroke-width="4"
|
||||
points="{points_str}" />""".format(
|
||||
color=color,
|
||||
points_str=" ".join(f"{x},{y}" for x, y in points)
|
||||
)
|
||||
|
||||
def wrap_svg(inner_svg, width, height):
|
||||
return f"""<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="{width}" height="{height}"
|
||||
viewBox="0 0 {width} {height}">
|
||||
{inner_svg}
|
||||
</svg>"""
|
||||
|
||||
OVERLAY_HUE = 0
|
||||
class Superimpose:
|
||||
def __init__(self, *args):
|
||||
global OVERLAY_HUE
|
||||
|
||||
self.args = args
|
||||
self.color = hex_from_hsv(OVERLAY_HUE, 1, 1)
|
||||
OVERLAY_HUE = (OVERLAY_HUE + 1.618033988) % 1
|
||||
|
||||
def points(self, width, height):
|
||||
points = []
|
||||
if not self.args:
|
||||
return [0 for _ in range(width)]
|
||||
|
||||
first_points = [(x, y - height/2) for (x, y) in self.args[0].points(width, height)]
|
||||
for thing in self.args[1:]:
|
||||
other_points = thing.points(width, height)
|
||||
for (i, ((x1, y1), (x2, y2))) in enumerate(zip(first_points, other_points)):
|
||||
assert(x1 == x2)
|
||||
first_points[i] = (x1, y1 + (y2 - height/2))
|
||||
|
||||
# normalize
|
||||
max_y = max(abs(y) for x, y in first_points)
|
||||
if max_y > height / 2:
|
||||
first_points = [(x, y * (0.9 * height / 2) / max_y) for x, y in first_points]
|
||||
|
||||
return [(x, height/2 + y) for x, y in first_points]
|
||||
|
||||
def get_color(self):
|
||||
return self.color
|
||||
|
||||
def _repr_svg_(self):
|
||||
width = 720
|
||||
height = 100
|
||||
|
||||
points = self.points(width, height)
|
||||
return wrap_svg(
|
||||
points_to_polyline(points, self.get_color()),
|
||||
width, height
|
||||
)
|
||||
|
||||
def hugo_shortcode(body):
|
||||
return "{{" + "< " + body + " >" + "}}"
|
||||
|
||||
class PlayNotes:
|
||||
def __init__(self, *hzs):
|
||||
self.hzs = hzs
|
||||
|
||||
def _repr_html_(self):
|
||||
toplay = ",".join([str(hz) for hz in self.hzs])
|
||||
return hugo_shortcode(f"playsound \"{toplay}\"")
|
||||
|
||||
class VerticalStack:
|
||||
def __init__(self, *args):
|
||||
self.args = args
|
||||
|
||||
def _repr_svg_(self):
|
||||
width = 720
|
||||
height = 100
|
||||
buffer = 10
|
||||
|
||||
polylines = []
|
||||
for (i, arg) in enumerate(self.args):
|
||||
offset = i * (height + buffer)
|
||||
points = [(x, y+offset) for (x,y) in arg.points(width, height)]
|
||||
polylines.append(points_to_polyline(points, arg.get_color()))
|
||||
|
||||
return wrap_svg(
|
||||
"".join(polylines),
|
||||
width, len(self.args) * (height + buffer)
|
||||
)
|
||||
|
||||
def hex_from_hsv(h, s, v):
|
||||
r, g, b = colorsys.hsv_to_rgb(h, s, v)
|
||||
return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}"
|
||||
|
||||
def color_for_frequency(hz):
|
||||
while hz < 261.63:
|
||||
hz *= 2
|
||||
while hz > 523.25:
|
||||
hz /= 2
|
||||
|
||||
hue = (math.log2(hz / 261.63) * 360)
|
||||
return hex_from_hsv(hue / 360, 1, 1)
|
||||
|
||||
def Frequency_points(self, width=720, height=100):
|
||||
# let 261.63 Hz be 5 periods in the width
|
||||
points = []
|
||||
period = width / 5
|
||||
for x in range(width):
|
||||
y = 0.9 * height / 2 * math.sin(x/period * self.hz / 261.63 * 2 * math.pi)
|
||||
points.append((x, height/2 - y))
|
||||
return points
|
||||
|
||||
def Frequency_get_color(self):
|
||||
return color_for_frequency(self.hz)
|
||||
|
||||
def Frequency_repr_svg_(self):
|
||||
# the container on the blog is 720 pixels wide. Use that.
|
||||
width = 720
|
||||
height = 100
|
||||
|
||||
points = self.points(width, height)
|
||||
points_str = " ".join(f"{x},{y}" for x, y in points)
|
||||
return wrap_svg(
|
||||
points_to_polyline(points, self.get_color()),
|
||||
width, height
|
||||
)
|
||||
|
||||
Frequency.points = Frequency_points
|
||||
Frequency.get_color = Frequency_get_color
|
||||
Frequency._repr_svg_ = Frequency_repr_svg_
|
||||
```
|
||||
|
||||
Let's take a look at a particular frequency. For reason that are historical
|
||||
and not particularly interesting to me, this frequency is called "middle C".
|
||||
|
||||
```{python}
|
||||
middleC = Frequency(261.63)
|
||||
middleC
|
||||
```
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
PlayNotes(middleC.hz)
|
||||
```
|
||||
|
||||
Great! Now, if you're a composer, you can play this note and make music out
|
||||
of it. Except, music made with just one note is a bit boring, just like saying
|
||||
the same word over and over again won't make for an interesting story.
|
||||
No big deal -- we can construct a whole variety of notes by picking any
|
||||
other frequency.
|
||||
|
||||
```{python}
|
||||
g4 = Frequency(392.445)
|
||||
g4
|
||||
```
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
PlayNotes(g4.hz)
|
||||
```
|
||||
|
||||
```{python}
|
||||
fSharp4 = Frequency(370.000694) # we write this F#
|
||||
fSharp4
|
||||
```
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
PlayNotes(fSharp4.hz)
|
||||
```
|
||||
|
||||
This is pretty cool. You can start making melodies with these notes, and sing
|
||||
some jingles. However, if your friend sings along with you, and happens to
|
||||
sing F# while you're singing the middle C, it's going to sound pretty awful.
|
||||
So awful does it sound that somewhere around the 18th century, people started
|
||||
calling it _diabolus in musica_ (the devil in music).
|
||||
|
||||
Why does it sound so bad? Let's take a look at the
|
||||
{{< sidenote "right" "superposition-note" "superposition" >}}
|
||||
When waves combine, they follow the principle of superposition. One way
|
||||
to explain this is that their graphs are added to each other. In practice,
|
||||
what this means is that two peaks in the same spot combine to a larger
|
||||
peak, as do two troughs; on the other hand, a peak and a trough "cancel out"
|
||||
and produce a "flatter" line.
|
||||
{{< /sidenote >}} of these two
|
||||
notes, which is what happens when they are played at the same time. For reason I'm
|
||||
going to explain later, I will multiply each frequency by 4. These frequencies
|
||||
still sound bad together, but playing them higher lets me "zoom out" and
|
||||
show you the bigger picture.
|
||||
|
||||
```{python}
|
||||
Superimpose(Frequency(middleC.hz*4), Frequency(fSharp4.hz*4))
|
||||
```
|
||||
```{python}
|
||||
#| echo: false
|
||||
PlayNotes(middleC.hz, fSharp4.hz)
|
||||
```
|
||||
|
||||
Looking at this picture, we can see that it's far more disordered than the
|
||||
pure sine waves we've been looking at so far. There's not much of a pattern
|
||||
to the peaks. This is interpreted by our brain as unpleasant.
|
||||
|
||||
{{< dialog >}}
|
||||
{{< message "question" "reader" >}}
|
||||
So there's no fundamental reason why these notes sound bad together?
|
||||
{{< /message >}}
|
||||
{{< message "answer" "Daniel" >}}
|
||||
That's right. We might objectively characterize the combination of these
|
||||
two notes as having a less clear periodicity, but that doesn't mean
|
||||
that fundamentally it should sound bad. Them sounding good is a purely human
|
||||
judgement.
|
||||
{{< /message >}}
|
||||
{{< /dialog >}}
|
||||
|
||||
If picking two notes whose frequencies don't combine into a nice pattern
|
||||
makes for a bad sound, then to make a good sound we ought to pick two notes
|
||||
whose frequencies *do* combine into a nice pattern.
|
||||
Playing the same frequency twice at the same time certainly will do it,
|
||||
because both waves will have the exact same peaks and troughs.
|
||||
|
||||
```{python}
|
||||
Superimpose(middleC, middleC)
|
||||
```
|
||||
|
||||
In fact, this is just like playing one note, but louder. The fact of the matter
|
||||
is that *playing any other frequency will mean that not all extremes of the graph align*.
|
||||
We'll get graphs that are at least a little bink wonky. Intuitively, let's say
|
||||
that our wonky-ish graph has a nice pattern when they repeat quickly. That way,
|
||||
there's less time for the graph to do other, unpredictable things.
|
||||
|
||||
What's the soonest we can get our combined graph to repeat? It can't
|
||||
repeat any sooner than either one of the individual notes --- how could it?
|
||||
We can work with that, though. If we make one note have exactly twice
|
||||
the frequency of the other, then exactly at the moment the less frequent
|
||||
note completes its first repetition, the more frequent note will complete
|
||||
its second. That puts us right back where we started. Here's what this looks
|
||||
like graphically:
|
||||
|
||||
```{python}
|
||||
twiceMiddleC = Frequency(middleC.hz*2)
|
||||
VerticalStack(
|
||||
middleC,
|
||||
twiceMiddleC,
|
||||
Superimpose(middleC, twiceMiddleC)
|
||||
)
|
||||
```
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
PlayNotes(middleC.hz, twiceMiddleC.hz)
|
||||
```
|
||||
|
||||
You can easily inspect the new graph to verify that it has a repeating pattern,
|
||||
and that this pattern repeats exactly as frequently as the lower-frequency
|
||||
note at the top. Indeed, these two notes sound quite good together. It turns
|
||||
out, our brains consider them the same in some sense. If you have ever tried to
|
||||
sing a song that was outside of your range (like me singing along to Taylor Swift),
|
||||
chances are you sang notes that had half the frequency of the original.
|
||||
We say that these notes are _in the same pitch class_. While only the first
|
||||
of the two notes I showed above was the _middle_ C, we call both notes C.
|
||||
To distinguish different-frequency notes of the same pitch class, we sometimes
|
||||
number them. The ones in this example were C4 and C5.
|
||||
We can keep applying this trick to get C6, C7, and so on.
|
||||
|
||||
```{python}
|
||||
C = {}
|
||||
note = middleC
|
||||
for i in range(4, 10):
|
||||
C[i] = note
|
||||
note = Frequency(note.hz * 2)
|
||||
|
||||
C[4]
|
||||
```
|
||||
|
||||
To get C3 from C4, we do the reverse, and halve the frequency.
|
||||
```{python}
|
||||
note = middleC
|
||||
for i in range(4, 0, -1):
|
||||
C[i] = note
|
||||
note = Frequency(note.hz / 2)
|
||||
|
||||
C[1]
|
||||
```
|
||||
|
||||
You might've already noticed, but I set up this page so that individual
|
||||
sine waves in the same pitch class have the same color.
|
||||
|
||||
All of this puts us almost right back where we started. We might have
|
||||
different pithes, but we've only got one pitch _class_. Let's try again.
|
||||
Previously, we made it so the second repeat of one note lined up with the
|
||||
first repeat of another. What if we pick another note that repeats _three_
|
||||
times as often instead?
|
||||
|
||||
```{python}
|
||||
thriceMiddleC = Frequency(middleC.hz*3)
|
||||
VerticalStack(
|
||||
middleC,
|
||||
thriceMiddleC,
|
||||
Superimpose(middleC, thriceMiddleC)
|
||||
)
|
||||
```
|
||||
```{python}
|
||||
#| echo: false
|
||||
PlayNotes(middleC.hz, thriceMiddleC.hz)
|
||||
```
|
||||
|
||||
That's not bad! These two sound good together as well, but they are not
|
||||
in the same pitch class. There's only one problem: these notes are a bit
|
||||
far apart in terms of pitch. That `triceMiddleC` note is really high!
|
||||
Wait a minute --- weren't we just talking about singing notes that were too
|
||||
high at half their original frequency? We can do that here. The result is a
|
||||
note we've already seen:
|
||||
|
||||
```{python}
|
||||
print(thriceMiddleC.hz/2)
|
||||
print(g4.hz)
|
||||
```
|
||||
|
||||
```{python}
|
||||
#| echo: false
|
||||
PlayNotes(middleC.hz, g4.hz)
|
||||
```
|
||||
|
||||
In the end, we got G4 by multiplying our original frequency by $3/2$. What if
|
||||
we keep applying this process to find more notes? Let's not even worry
|
||||
about the specific frequencies (like `261.63`) for a moment. We'll start
|
||||
with a frequency of $1$. This makes our next frequency $3/2$. Taking this
|
||||
new frequency and again multiplying it by $3/2$, we get $9/4$. But that
|
||||
again puts us a little bit high: $9/4 > 2$. We can apply our earlier trick
|
||||
and divide the result, getting $9/16$.
|
||||
|
||||
```{python}
|
||||
from fractions import Fraction
|
||||
|
||||
note = Fraction(1, 1)
|
||||
seen = {note}
|
||||
while len(seen) < 6:
|
||||
new_note = note * 3 / 2
|
||||
if new_note > 2:
|
||||
new_note = new_note / 2
|
||||
|
||||
seen.add(new_note)
|
||||
note = new_note
|
||||
```
|
||||
|
||||
For an admittedly handwavy reason, let's also throw in one note that we
|
||||
get from going _backwards_: dividing by $2/3$ instead of multiplying.
|
||||
This division puts us below our original frequency, so let's double it.
|
||||
|
||||
```{python}
|
||||
# Throw in one more by going *backwards*. More on that in a bit.
|
||||
seen.add(Fraction(2/3) * 2)
|
||||
fractions = sorted(list(seen))
|
||||
fractions
|
||||
```
|
||||
|
||||
```{python}
|
||||
frequencies = [middleC.hz * float(frac) for frac in fractions]
|
||||
frequencies
|
||||
```
|
||||
|
||||
```{python}
|
||||
steps = [frequencies[i+1]/frequencies[i] for i in range(len(frequencies)-1)]
|
||||
minstep = min(steps)
|
||||
print([math.log(step)/math.log(minstep) for step in steps])
|
||||
```
|
||||
|
||||
Since peaks and troughs
|
||||
in the final result arise when peaks and troughs in the individual waves align,
|
||||
we want to pick two frequencies that align with a nice pattern.
|
||||
|
||||
But that begs the question: what determines
|
||||
how quickly the pattern of two notes played together repeats?
|
||||
|
||||
We can look at things geometrically, by thinking about the distance
|
||||
between two successive peaks in a single note. This is called the
|
||||
*wavelength* of a wave. Take a wave with some wavelength $w$.
|
||||
If we start at a peak, then travel a distance of $w$ from where we started,
|
||||
there ought to be another peak. Arriving at a distance of $2w$ (still counting
|
||||
from where we started), we'll see another peak. Continuing in the same pattern,
|
||||
we'll see peaks at distances of $3w$, $4w$, and so on. For a second wave
|
||||
with wavelength $v$, the same will be true: peaks at $v$, $2v$, $3v$, and so on.
|
||||
|
||||
If we travel a distance that happens to be a multiple of both $w$ and $v$,
|
||||
then we'll have a place where both peaks are present. At that point, the
|
||||
pattern starts again. Mathematically, we can
|
||||
state this as follows:
|
||||
|
||||
$$
|
||||
nw = mv,\ \text{for some integers}\ n, m
|
||||
$$
|
||||
|
||||
As we decided above, we'll try to find a combination of wavelengths/frequencies
|
||||
where the repetition happens early.
|
||||
|
||||
__TODO:__ Follow the logic from Digit Sum Patterns
|
||||
|
||||
The number of iterations of the smaller wave before we reach a cycle is:
|
||||
|
||||
$$
|
||||
k = \frac{w}{\text{gcd}(w, v)}
|
||||
$$
|
||||
|
||||
|
||||
```{python}
|
||||
Superimpose(Frequency(261.63), Frequency(261.63*2))
|
||||
```
|
||||
|
||||
```{python}
|
||||
Superimpose(Frequency(261.63*4), Frequency(392.445*4))
|
||||
```
|
||||
|
||||
```{python}
|
||||
VerticalStack(
|
||||
Frequency(440*8),
|
||||
Frequency(450*8),
|
||||
Superimpose(Frequency(440*8), Frequency(450*8))
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
```{python}
|
||||
from enum import Enum
|
||||
class Note(Enum):
|
||||
C = 0
|
||||
Cs = 1
|
||||
D = 2
|
||||
Ds = 3
|
||||
E = 4
|
||||
F = 5
|
||||
Fs = 6
|
||||
G = 7
|
||||
Gs = 8
|
||||
A = 9
|
||||
As = 10
|
||||
B = 11
|
||||
|
||||
def __add__(self, other):
|
||||
return Note((self.value + other.value) % 12)
|
||||
|
||||
def __sub__(self, other):
|
||||
return Interval((self.value - other.value + 12) % 12)
|
||||
```
|
||||
|
||||
```{python, echo=false}
|
||||
class MyClass:
|
||||
def _repr_svg_(self):
|
||||
return """<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="120" height="120" viewBox="0 0 120 120">
|
||||
<circle cx="60" cy="60" r="50" fill="red"/>
|
||||
</svg>"""
|
||||
|
||||
MyClass()
|
||||
```
|
||||
@@ -1,15 +0,0 @@
|
||||
window.addEventListener("load", (event) => {
|
||||
for (const elt of document.getElementsByClassName("mt-sound-play-button")) {
|
||||
elt.addEventListener("click", (event) => {
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
for (const freq of event.target.getAttribute("data-sound-info").split(",")) {
|
||||
const oscillator = audioCtx.createOscillator();
|
||||
oscillator.type = "sine"; // waveform: sine, square, sawtooth, triangle
|
||||
oscillator.frequency.value = parseInt(freq); // Hz
|
||||
oscillator.connect(audioCtx.destination);
|
||||
oscillator.start();
|
||||
oscillator.stop(audioCtx.currentTime + 2); // stop after 1 second
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
function Image(el)
|
||||
local src = el.src
|
||||
if src:match("%.svg$") then
|
||||
local alt = el.caption and pandoc.utils.stringify(el.caption) or ""
|
||||
local obj = string.format(
|
||||
'{{< inlinesvg "%s" "%s" >}}',
|
||||
src, alt
|
||||
)
|
||||
return pandoc.RawInline("html", obj)
|
||||
else
|
||||
return el
|
||||
end
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
function Math(el)
|
||||
if el.mathtype == "InlineMath" then
|
||||
return pandoc.RawInline("markdown", "\\(" .. el.text .. "\\)")
|
||||
end
|
||||
end
|
||||
44
content/writing/onspiders.md
Normal file
44
content/writing/onspiders.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "On Spiders"
|
||||
date: 2026-03-22T01:03:00-05:00
|
||||
type: onspiders
|
||||
---
|
||||
|
||||
```
|
||||
Whenever I stay still,
|
||||
I feel the spiders weave their webs around me.
|
||||
Their tiny legs entangle me in silk,
|
||||
traverse my body as they seem to proudly
|
||||
inspect their work and into darkness sink.
|
||||
|
||||
|
||||
There're times I see the spiders' threads on others,
|
||||
They shimmer gently in the purple dusk,
|
||||
Or sway with zephyrs in the hair of lovers,
|
||||
stuck.
|
||||
|
||||
To listen to the webs is to hear echoes
|
||||
of social butterflies ensnared within
|
||||
arachnid galleries of human ethos,
|
||||
whose pieces come and go upon a whim.
|
||||
|
||||
Their curators are blind and want for stories,
|
||||
delivered as vibrations through the links,
|
||||
for righteous anger, anguish, joy and worries,
|
||||
superimposing frequencies that mix,
|
||||
reveberating through the sprawling complex
|
||||
of radial lines and newly captured prey,
|
||||
resisting first but ultimately hopeless,
|
||||
these signals grow in urgency and they
|
||||
form standing waves that make the victims sing
|
||||
and even from myself bring out a resonant ring.
|
||||
```
|
||||
|
||||
|
||||
<!--
|
||||
the signals cause a standing wave
|
||||
intensify with every passing moment
|
||||
arachnid galleries of human drama,
|
||||
temples to the human mind
|
||||
(traverse my body all while I sleep soundly,)
|
||||
They shimmer gently in the setting sun, -->
|
||||
11
convert.rb
11
convert.rb
@@ -68,13 +68,12 @@ files.each do |file|
|
||||
t.replace(rendered)
|
||||
end
|
||||
|
||||
found_any ||= document.at('meta[name="needs-latex"]')
|
||||
|
||||
# If we didn't find any mathematical equations, no need to include KaTeX CSS.
|
||||
# Disabled here because Bergamot technically doesn't require math blocks
|
||||
# on the page but does need the CSS.
|
||||
#
|
||||
# unless found_any
|
||||
# document.css('link[href$="katex.css"], link[href$="katex.min.css"]').each(&:remove)
|
||||
# end
|
||||
unless found_any
|
||||
document.css('link[href$="katex.css"], link[href$="katex.min.css"]').each(&:remove)
|
||||
end
|
||||
|
||||
File.write(file, document.to_html(encoding: 'UTF-8'))
|
||||
end
|
||||
|
||||
25
layouts/onspiders/baseof.html
Normal file
25
layouts/onspiders/baseof.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{{- /* Note: changing the baseof template because the title, tags, etc. of a regular post are still valid. */ -}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Site.Language.Lang }}">
|
||||
{{- partial "head.html" . -}}
|
||||
<body>
|
||||
{{ $writingcss := resources.Get "scss/writing.scss" | css.Sass | resources.Minify }}
|
||||
<link rel="stylesheet" href="{{ $writingcss.Permalink }}">
|
||||
{{ $spidercss := resources.Get "scss/onspiders.scss" | css.Sass | resources.Minify }}
|
||||
<link rel="stylesheet" href="{{ $spidercss.Permalink }}">
|
||||
<svg class="spiderweb" viewBox="0 0 197.21727 106.16592">
|
||||
<use href="{{ (resources.Get "svg/spiderweb.svg").Permalink }}#mainlayer"></use>
|
||||
</svg>
|
||||
{{- partial "header.html" . -}}
|
||||
<div class="container"><hr class="header-divider"></div>
|
||||
<main class="container">
|
||||
|
||||
{{- if .Draft -}}
|
||||
{{- partial "warning.html" (i18n "postDraft") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- block "main" . }}{{- end }}
|
||||
</main>
|
||||
{{- block "after" . }}{{- end }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
<object type="image/svg+xml" data="{{ .Get 0 }}" aria-label="{{ .Get 1 }}"></object>
|
||||
@@ -1 +0,0 @@
|
||||
<button class="mt-sound-play-button" data-sound-info="{{ .Get 0 }}">Play</button>
|
||||
@@ -3,6 +3,8 @@
|
||||
<html lang="{{ .Site.Language.Lang }}">
|
||||
{{- partial "head.html" . -}}
|
||||
<body>
|
||||
{{ $writingcss := resources.Get "scss/writing.scss" | css.Sass | resources.Minify }}
|
||||
<link rel="stylesheet" href="{{ $writingcss.Permalink }}">
|
||||
{{ $voidcss := resources.Get "scss/thevoid.scss" | css.Sass | resources.Minify }}
|
||||
<link rel="stylesheet" href="{{ $voidcss.Permalink }}">
|
||||
{{- partial "header.html" . -}}
|
||||
|
||||
Submodule themes/vanilla updated: 952502e690...312d0d37c6
Reference in New Issue
Block a user