Tab CompletionTab Atkins Jr.jackalmage@gmail.comhttp://www.xanthir.comhttp://www.xanthir.com/2020-01-21T14:56:17+00:00All content is published in the public domain, or optionally may be licensed under the CC0 license.http://www.xanthir.com/b54c0Handling Time in Base 62020-01-21T14:56:17+00:002020-01-16T00:07:19+00:00<p>In previous posts (<a href="http://www.xanthir.com/b4y30" title="">here</a> and <a href="http://www.xanthir.com/b5400" title="">here</a>) I lay out arguments for why base 6 (heximal) is superior to base 10 (decimal) or any other base, due to its numerical properties.<p>Arithmetic is all well and good, but I think heximal actually has some great arguments for its superiority in the real world, too. Let's go over some of them.<h2>"Decimal" Time Kinda Sucks</h2><p>We already don't <i>really</i> use decimal for time; we use a bastardized mostly-base-60 system: 60 seconds makes 1 minutes, 60 minutes makes 1 hour, and then (breaking the pattern!), 24 hours makes 1 day. Like the Babylonians did in their time, we <i>write</i> this using alternating base-6 and base-10: the 1s digit cycles thru all ten values 0-9, while the tens digit only cycles thru the six values 0-5.<p>All this adds up to a lot of confusion for children first learning how to read time, and persistent confusion thru adulthood for people needing to do time math; it's not unusual for someone, at first glance, to read a timer set to 1:20 as "120 seconds left", when it's actually 80 seconds. (Similarly, it's sometimes confusing to type "120" into a microwave and have it immediately converted into "2:00".) Or take movies, which usually have their runtimes reported in minutes - how long is a 132 minute movie? (2h 12m, but that takes a moment of thinking to calculate.)<p>That 60/24 inconsistency also contributes to more time confusion in the form of clock faces generally being labeled 1-12, with children having to memorize that "3" only means 3 for the hour hand; it means 15 for the minute or second hand.<h2>How Does Heximal Help?</h2><p>It turns out that we can avoid all that by switching to heximal time! Let's go over the details.<p>First, the scale. We can use a nice, simple 100₆:1 ratio for all three time units: 100₆ (36⏨) hours to the day, 100₆ (36⏨) minutes to the hour, and 100₆ (36⏨) seconds to the minute. This gives us time periods very close to decimal time: the hex hour is 40 dec minutes, a little shorter than the dec hour but still able to serve a similar purpose as "a large unit of time"; the hex minute is almost exactly a decimal minute (about 1m4s in dec); and the hex second is just under two dec seconds, still fairly reasonable to use for counting off times. (Bonus: 1/10₆ of a hex second is 1/3 of a dec second, which is actually countable by humans, unlike 1/10⏨ of a dec second.)<p>More importantly, time math is now trivial. Quick, what's 250 seconds in minutes? In hex, it's 2m50s; in decimal it's [does some quick math] 4m10s. Scale that up to hours, too: 3 hours 10 minutes is 310₆ minutes or 3,1000₆ seconds in hex.<p>All this means that children, when first learning time, will no longer struggle with the inconsistencies of base-10 numbering with base-60/base-24 time. Counting and time all work the same exact way, mutually reinforcing each other in learning. And this carries over to adulthood, making time math much simpler overall.<h2>A Heximal Clock</h2><p>Interestingly, a heximal clock would feel pretty similar. Minutes and seconds are already counted 00-59⏨; in hex they're 00-55₆, and your intuitions work the same. For example, "half past" is :30 in both hex and dec time. It's only the hour that would significantly change, from either a 1-12⏨ double count (in the US) or a 0-23⏨ count (elsewhere) to a 0-55₆ count in hex.<p>Actually rendering a hex clock is an interesting challenge. Due to the aforementioned minute/second closeness, the minute and second hands of a hex clock can work identically, going once around the circle for each hour/minute. It's probably not reasonable to do the same for hours, however, because of the loss of precision. It's not very important whether you can tell at quick glance whether it's :11 or :12 past the hour, but it's <i>very</i> important whether your alarm clock is showing the hour itself as 11₆ or 12₆; one is 7:20am in decimal time, the other is 8am, and the difference can be whether you're late to work or not!<p>There's a few possible ways to resolve this, but the one I think I prefer overall is to have <i>two</i> hour hands, one pointing to the tens digit and the other pointing to the ones digit. I've created a live example of this at <a href="https://heximal-clock.glitch.me">https://heximal-clock.glitch.me</a> that you can check out. Reading it is almost identical to reading a normal clock, you just have to take into account the pair of hour hands, instead of reading a single hand and remembering which half of the day you're in.<h2>Heximal Dates</h2><p>We can take this nice system a little further, into dates. The year is 365⏨ days, remarkably close to 36⏨×10⏨; in hex that's 100₆×14₆ or 1400₆ days (exactly 1405₆).<p>First, if we're using heximal, clearly we want to use a six-day week; the seven-day week is a weird numerological artifact in our time. That means the year is 140₆ weeks, with 5 days leftover.<p>(This has some knock-on benefits; sticking with a 2-day weekend means a 4-day workweek, which is honestly better for humans, but not a <i>huge</i> change in overall working days. (It's definitely not a 20% reduction, just ~6.5% reduction, from ~260 working days to ~244.) We can drop Wednesday, that dumb-named day, and have a solid Su-Mo-Tu-Th-Fr-Sa week.)<p>With that down, we've got some choices. We could stick close to the existing calendar, and do a dozen (20₆) months in a year, each with 50₆ (30⏨) days. That's not too bad, but we're <i>so close</i> to even rounder numbers: we can alternately go with a six-week month (100₆ or 36⏨ days), then we have ten (14₆) months in the year.<p>This feels really good - 100₆ seconds in a minute, 100₆ minutes in an hour, 100₆ hours in a day, and 100₆ days in a month. The perfect scale-up only breaks when you finally reach a whole year, and even then, ten isn't a bad number in heximal. (Certainly no worse than twelve is in decimal.)<p>This gives us some <i>beautiful</i> day numbering, too - each month starts on Sunday the 1st, and subsequent Sundays are the 11th, 21st, 31st, etc.<p>(Since we only need ten month names, I presume we'd drop July/August as the most recently-altered month names, making Sept-Dec's 7-10-derived names make sense again.)<h3>Years Aren't 360 Days Tho</h3><p>"But Tab!" I hear you cry, "The year isn't 360 days, it's 365! You just dropped those last five days!" You're right, we have to deal with those 5 (6 on leap years) days. I think there's only two reasonable possibilities.<p>The first possibility comes from my friend Tantek, with his <a href="http://tantek.pbworks.com/w/page/19402947/NewCalendar" title="">NewCalendar</a> project, a proposal to regularize the existing calendar with minimal disruption into 12⏨ 30⏨-day months, each of six 5-day weeks. He deals with the leftover days by distributing them thruout the year - every second month has an extra day at the end. It's technically outside the week/month system, to avoid disrupting the day numberings, but it basically just means that the months alternate between 30⏨/31⏨ days. (December is the exception, with 30⏨ days in a normal year, and only getting its 31⏨st on leap years.)<p>We can adopt the same, giving every second month an additional intercalary day ("intercalary" = not technically part of the calendar, so month/week numbering isn't interrupted). Because we're starting from ten months, the pattern for a normal year is kept absolutely; it's a perfect 100₆/101₆ alternation. In leap years the leap day goes at the end, giving the tenth month 102₆ days.<p>I like this solution because it keeps the year more regular overall. Every 200₆ days there's a single bonus day, making each "quint" of the year exactly 201₆ days long (the final one is 202₆ days on a leap year, an unavoidable complication). This helps things like corporate finance, as you don't have to normalize quinterly earnings by the number of days in each to make them comparable, like you do in our calendar (or else make the "quarters" not line up with the calendar exactly, which has its own problems). It also minimizes disruption to the overall week - every two months you just have a three-day weekend, easy-peasy. (It does weight the work/end ratio slightly further toward weekends, tho.)<p>The big downside of this is that it breaks nice date math - the distance between the 2nd of one month and the 2nd of the next is either 100₆ or 101₆, depending on which month you started from. Ordinary people would have to worry about off-by-one errors all the time when doing mental date arithmetic. And this doesn't just affect mental math; anything based on weekly schedules has to account for every dozenth week effectively having seven days - medication, work schedules, etc.<p>So, the second solution is to just insert an entire <i>week</i> at the end of the calendar - nine perfect 100₆-day months, and then December is 105₆ days (110₆ on leap years). In work schedules, I presume this would just be treated as a 3-day work week (or on leap years, just a normal week), a nice little treat at the end of the year. No reason to make this week intercalary, just drop it in as a normal seventh week to the month.<p>This means that the fifth quint does need to be normalized when comparing cross-quint numbers, but that's still an improvement over our current "every single quarter is a different length" situation.<p>On the plus side, date math remains <i>absolutely trivial</i> as long as the dates are within a single year - January 5th and May 10th are separated by exactly 405₆ days, a number which requires no more thought than realizing "May is four months after January, and the 10th is five days after the 5th". Anything which relies on weekly schedules has to adjust itself only <i>once a year</i> (and only on three out of every four years, at that). Dates crossing a year need some thought, but that's already the case today, so it's not a new problem.<p>Overall I think I prefer the second solution, adding a week to the end of the year. Minimizing disruption to weekly schedules seems slightly more valuable than keeping quints regular.<h2>Next Time</h2><p>Next, I overthink the other numeric systems in our lives, and argue why they'd be better in heximal.http://www.xanthir.com/b5400Pronouncing Base 6 Numbers2019-12-31T16:20:17+00:002019-12-10T22:15:01+00:00<p>What's the best way to pronounce heximal numbers?<p>One could just read them out like they're decimal: "21" being pronounced "twenty one", etc. But that leads to some confusion when mixing heximal and decimal numbers in conversation, and more generally just confuses people who are currently used to only using decimal numbers. It also makes phrases like "base ten" confusing/ambiguous - which base is that 10 in???<p>Instead I'll propose two simple possibilities, both of which I like for different reasons. One sticks close to decimal while remaining distinguishable; the other is much more foreign but has fun and interesting numerical and syntactic qualities.<h2>How To Pronounce It Like Decimal</h2><p>(Several aspects of this are inspired by <a href="https://www.seximal.net/" title="">jan Misali's method</a>, but I don't like all the details of their proposal.)<p>All the individual digits are pronounced normally, as in decimal English: zero, one, two, three, four, five.<p>All the "teens" (two-digit numbers with a 1 in the tens digit) are <i>also</i> pronounced like in decimal English: 10 is "six", 11 is "seven", 12 is "eight", 13 is "nine", 14 is "ten", and 15 is "eleven".<p>Higher numbers are pronounced akin to how decimal does it, just with a different suffix. Rather than appending -ty, append -sy: 20 is "twosy", 21 is "twosy one", 32 is "threesy two", etc with "foursy" and "fivesy". (Note: English phonotactics dictates that all of these S's become voiced, pronouncing as "z".)<p>After two digits, use "quat" to indicate the third digit: 123 is "one quat twosy three".<p><i>Always</i> group quats into two digits: 1234 is "eight quat threesy four".<p>Then use thousand/million/billion/etc for larger numbers, grouping by <i>4</i> digits (rather than by 3 like in decimal; 4 heximal digits are roughly equal to 3 decimal digits): 1,2345 is "one thousand twosy-three quat foursy-five"; 1,0203,0405 is "one million, two quat three thousand, four quat and five".<h3>Justification</h3><p>Keeping the single digits same as in decimal is obvious; all bases do that already.<p>Pronouncing 10₆ as "six" isn't too unusual, from what I understand of other base-pronunciation schemes. Having a unique word for your 10 value helps in talking about it, so you don't constantly have to mutter <small>"base six"</small> after every mention of "ten".<p>For the rest of the 1X values, continuing to use the decimal names makes them simple and easy to remember. Also, the 1X's don't yet have the ability to do the unifying suffix pattern that the higher values do (which I'll get to in a sec), so they've gotta be weird somehow anyway; this is why decimal has the unique "X-teen" variant (shortened from "X and ten"). As a bonus, decimal has unique weird words for all the values we need; it doesn't start systematizing them (which would feel weird carrying over into heximal) until thirteen.<p>For 2X and after, I use a naming scheme distinguishable from but obviously inspired by decimal. 20⏨ in decimal was originally "two tens"; English shortened the "tens" to a -ty suffix (and lightly modifies "two" and "three" to sound better with the suffix). If we follow the same pattern in heximal, 20₆ is "two sixes", shortening to "twosy".<p>An accidental nice feature of this over decimal is that it lacks the "thirteen/thirty"/etc confusion that decimal has; a final "n" is hard to hear in some circumstances. In heximal, "nine" and "thirty" are totally different-sounding.<p>For higher values, "quat" was chosen because (a) it sounds good and unique, not easily confusable with any other numbers, and (b) it's a shortening of "quarter gross", because 100₆ is indeed one quarter of 144⏨.<p>For even higher values, I'm just reusing thousand/etc because it's not that important; if you're actually saying values that large you're probably already in a context where base 6 is assumed.<h2>How To Pronounce It Not Like Decimal</h2><p>(This system was suggested to me by <a href="https://twitter.com/BerenRyan/status/1204454514713071616" title="">@berenryan</a>, apparently cribbed from their old notes talking to fellow conlang nerds. )<p>The previous method has the nice benefit of being familiar; everyone will understand you with an absolute minimum of confusion or required teaching. But it inherits the arbitrary historical structure of English numbering, which is OK but not great for several reasons.<p>If you're willing to get a little weird and deal with the fact that nobody will understand your numbers (but have the satisfaction that in a theoretical world where everyone learned it as kids, it would be better than the first method), here's a much better system.<p>First, the digits. 0 is "pa" (like in "papa"), 1 is "be" (like "bay"), 2 is "ti" (like "tea"), 3 is "do" (like "d'oh!"), 4 is "ku" (like "coup"), and 5 is "gr" (like "grrrr" but not drawn out). Note that all of the vowels are Spanish-style, not English-style.<p>When speaking numbers larger than a single digit, group them into pairs and pronounce each pair as follows:<p><table class=nums><thead><tr><td></td><th>+0</th><th>+1</th><th>+2</th><th>+3</th><th>+4</th><th>+5</th></tr></thead><tbody><tr><th>00</th><td>pama</td><td>befa</td><td>tiva</td><td>dona</td><td>kusa</td><td>gaza</td></tr><tr><th>10</th><td>peŋe</td><td>bime</td><td>tofe</td><td>duve</td><td>kane</td><td>gese</td></tr><tr><th>20</th><td>pizi</td><td>boŋi</td><td>tumi</td><td>dafi</td><td>kevi</td><td>gini</td></tr><tr><th>30</th><td>poso</td><td>buzo</td><td>taŋo</td><td>demo</td><td>kifo</td><td>govo</td></tr><tr><th>40</th><td>punu</td><td>basu</td><td>tezu</td><td>diŋu</td><td>komu</td><td>gufu</td></tr><tr><th>50</th><td>pavr</td><td>benr</td><td>tisr</td><td>dozr</td><td>kuŋr</td><td>gamr</td></tr></tbody></table><p>(ŋ is pronounced "ng")<style>
.nums td { text-align: center; }
</style><p>So the basic patterns:<ul><li>the first letter corresponds to the ones digit; anything with a 0 ones digit starts with a "p", etc.<li>the last letter corresponds to the tens digit; all the 1_ values end in an "e", etc.<li>the second and third letters cycle between five vowels and seven consonants, respectively, so they correspond to the value of the number modulo 5 or 7.</ul><p>Larger numbers work the same as in the previous system; use "quat" after two digits and then thousand/million/etc after 4/8/etc digits.<p>So, 1,2345 is "befa thousand, dafi quat gufu".<h3>Justification</h3><p>This is actually a pretty brilliant setup for several reasons.<ul><li><p>First, the digit names are obviously chosen to just use the Nth first/last letter; they also (aside from gr/gaza) are the first syllable of the 0_ numbers, since the second and last letters cycle thru the same vowels for the first five values.<li><p>Second, the sounds are well-chosen to be regular but distinguishable. The first consonants are all plosives, alternating between unvoiced and voiced; the second consonants are a collection of nasals and fricatives. Choosing consonants this way gives each word a sharp, distinguishable start that won't blend into other words, and then softens into a blending sound in the middle that carries stress well.<li><p>Third, the words all have significant redundancy, which helps with hearing. It's common for invented systems like this to be minimally redundant; it would have been easy to just produce two-letter words from the first and last letters of these words. They'd all be unique, so that technically works, right? In real conversation tho, where people might mumble, or the room might be noisy, or a radio might be crackling, distinguishing between some of those sounds can be quite difficult; this is why military jargon pronounces 5 and 9 as "fiver" and "niner", because "five" and "nine" sound <i>remarkably</i> similar over a low-bandwidth radio and the addition of the "er" sound forces us to emphasize the "v" versus "n" and makes them more distinct.<p>In here, the first and fourth letters cover 6×6 unique combinations, for the 36 possible values, but the second and third letters cover 5×7, or 35, possible values, meaning they're <i>also</i> almost completely unique across the set of numbers. (Only 0₆ and 55₆, pama and gamr, share their center letters.) As well, the two sets tile their combinations in different ways, so if two sounds are kinda similar they won't show up together multiple times. If someone is shouting across the room and you're not sure if they said "pizi" or "bizi", well, only one of those is the name of a number. If you can correctly hear <i>any two letters</i> in the word, you can almost always tell which number it was; you've got to fuck up <i>really bad</i> to mishear it.<p>(Of the six possible pairs of letters you could hear, three of the pairs uniquely identify a number in every instance; one (the center two letters) has a single collision, between 0₆ (pama) and 55₆ (gama); and two (the first and second letter, or the second and fourth letter) have six collisions, as each <i>0₆/_5₆ or 0</i>₆/5_₆ number collides.)<li><p>Fourth, it puts divisibility right into the name of the number: numbers divisible by 2 start with "p", "t", or "k"; numbers divisible by 3 start with "p" or "d"; divisible by 5 has an "a" in the second position; divisible by 7 (11₆) has an m in the third position.<p>This scales above two-digit numbers, using the divisibility trick for values one above a base: if you add together all the digit pairs, the original number is divisible by 11₆ if the result is. So 1,2345 becomes 1+23+45, or 113, which is 1+13, or 14; that's kane, which is |5 (due to the second letter being "a") but not |7 (since the third letter isn't "m"), so 1,2345 is |5 but not |7. (The trick happens to work for the value one below the base, too; it's an elaboration of the simpler "add all the digits" trick that works for <i>only</i> the value one below the base.)<p>(This ties into the ease of identifying primes in base 6; all the primes past 6 start with "b" or "g", and among the two-digit numbers, <i>every single</i> b- or g- word is prime with only two exceptions, 41₆ and 55₆. In this system those are basu and gamr, both of which are <i>obviously</i> (to someone raised in this system) divisible by five because of the "a" sound.)<li><p>Fifth, some studies have shown an inverse correlation between the number of syllables in the names for numbers and the amount of numbers a person can remember at once. That is, in languages where digits are shorter to say (like Chinese, where they're all single-syllable), people can remember longer numbers than in languages with longer digit names (like English, with "seven" being two-syllable and several of the digits being four or five letters, and almost of the names for the tens digits being two or three syllables). In this system, each digit is a single syllable; all two-digit numbers are two syllables; writing out a pronounced number requires exactly twice as many letters as writing it out in digits. That's about as small as you can get things!<li><p>Sixth, having a dedicated and very short system for pronouncing all the two-digit values is convenient for "senary compression", the act of grouping heximal digits into pairs and treating them as units. Since base 6 is a little on the small side, this can be useful when talking about or remembering long numbers (this is the same reason octal and hexadecimal are useful, as compression methods for reading/discussing long binary numbers).</ul>http://www.xanthir.com/b53d0Molek-Syntez Unstated Priorities2019-11-18T18:20:19+00:002019-11-18T18:20:19+00:00<p><a href="https://store.steampowered.com/app/1168880/MOLEKSYNTEZ/" title="">Molek-Syntez</a> is a fun new game from Zachtronics, in the classic Zachtronics mold - program a simple machine to do something vaguely chemistry-related, in this case synthesizing drugs in your shitty Romanian apartment.<p>Optimizing these machines requires some careful knowledge of <i>precisely</i> how various commands are prioritized, so you can "stack up" commands in the same turn sometimes that would otherwise require separate turns. Unfortunately per usual for Zach's games, the priority information is never stated anywhere and has to be divined from gameplay. Molek-Syntez is particularly complicated in its simplicity, however, making it more difficult than usual to figure these things out. Some people have, tho, and I'm going to reproduce the information here, for my own future use and that of future searchers.<h2>Command Priorities</h2><ol><li>Delete & output. Note that while this fires first (and thus you can safely move things into the way of the command during the same turn), the action actually takes the entire turn, and so you can't move or fire thru the space the disappearing molecule is taking up.<li>Hydrogen shunting & removal.<li>Hydrogen addition.<li>Movement and rotation.<li>Bond creation.<li>Input.</ol><p>If multiple emitters are taking actions in the same step, they fire in numeric order; emitter 1 fires first, then emitter 2, etc.<h2>Bonding Priorities</h2><p>Whenever an atom has the opportunity to bond (it loses a hydrogen, or gets hit with a bond-creator action) or debond, it chooses which neighboring ion to de/bond with in a particular order as well.<ol><li>Bond multiplicity. It prefers to <i>add</i> bonds to the smallest channel possible, making a single bond over a double bond, etc. When <i>removing</i> bonds it's the opposite, breaking a double bond over a single bond, etc.<li>Atom type: C > N > O > S > Cl. I assume this is based on actual electronegativities, but I choose not to look this information up right now. So if you have "C C N" and tell the middle atom to make a bond, it'll always do "C-C N", never "C C-N" (unless the left C is already full of hydrogen).<li>Orientation: down-right (4 o'clock), then clockwise from there. (That is, 4 > 6 > 8 > 10 > 12 > 2.)</ol><hr><p>Hopefully this information will help you in your optimization!http://www.xanthir.com/b51z0Octonion Alchemy2019-10-07T03:44:21+00:002019-08-11T02:44:26+00:00<p>A few months ago, I stumbled on <a href="https://www.quantamagazine.org/the-octonion-math-that-could-underpin-physics-20180720/" title="">an article about a physicist who believes octonion math can underly physics</a>. It was a pretty interesting article in its own right, but what really grabbed me at the time was the diagram near the end, giving a brief explainer of complex, quaternion, and octonion math:<p><p><img src="/pictures/octonions.jpg" style="max-width: 100%"><p>Tho I'd seen quaternion and octonion math before, I'd never quite seen the quaternion units depicted in that rock-paper-scissors diagram. And then when I looked down at the octonion diagram of the unit interactions, I was struck: that looks like a (simple) goetic diagram! The sort of "mystic circles and triangles with some latin bullshit" thing you see in anime alchemy and other magic.<p>I couldn't get this out of my head, and after some <a href="https://twitter.com/tabatkins/status/1093978672380338176" title="">noodling on Twitter</a> (link goes to the bottom of the thread to ensure you see the whole thing, scroll to the top to start reading) and chatting with my brother, I'd come up with a whole little overcomplicated elemental transmutation alchemy system which I really liked.<p><a href="https://docs.google.com/drawings/d/16b-F4H-h1yC-XTuGNh9sfE1wAMdnMSGgbpYrRkYXY-g/" title="">Here's the diagram in Google Diagrams.</a> (Sorry, I haven't spent the time to make it an SVG yet.)<h2>lrn 2 alchemy nub</h2><p>Reading the diagram requires a little explanation.<p>First, the circle, and each of the six straight lines (with the end looping back to the beginning to make a circle as well) describe seven transmutation cycles; the elements in each cycle can be transmuted into each other.<p>To do so, you take an element from one "node", the <b>source</b>, and an element from another node, the <b>catalyst</b>. This will produce one of the two elements in the third node of the cycle. Which one depends on the chirality of your alchemy: if your source and catalyst are both the "top" elements of their nodes, and tracing the diagram from source to catalyst <i>follows</i> the arrow connecting them, then the result is the top element of the final node. For example, fire catalyzed with water produces lightning; fire catalyzed with life loops around and produces aether; etc.<p>However, choosing the "bottom" element in a node inverts the result: fire catalyzed with air (inverted water) produces earth. Running the transmutation in reverse also inverts the result: water catalyzed with fire (inverted order) produces earth. Two inversions goes back to normal: ice catalyzed with air (both inverted, normal order) produces lightning; so does water catalyzed with ice (one inverted, inverted order). All three inversions (both elements from the "bottom", with the reaction run against the arrows) is back to inverting: air catalyzed with ice produces earth.<p>There's a hidden node in here, too, representing non-elemental substance. (In octonions, it's the value 1; the real unit, as opposed to the 7 complex units e₁-e₇.) By default it's mundane; a lack of magic. Inverted, it represents wild magic, destructive and reactive without the guiding template of an element constraining it. You can produce these by choosing the source and catalyst from the <i>same</i> node: if they're opposing elements, like fire and ice, they produce mundanity as the elements cancel out (regardless of source/catalyst order); if they're the <i>same</i> element, like fire and fire, they produce wild magic as the element burn themselves out from the clash but leave behind their energy.<p>Mundanity combined with anything produces the other thing: mundane catalyzed with fire produces fire, wild magic catalyzed with mundane produces wild magic, etc. Wild magic combined with anything inverts the other: fire + wild magic produces ice, wild magic + wild magic produces mundanity, etc.<p>(This, if it's not obvious, perfectly replicates octonion math, if you're only allowed to use the eight units, their negatives, and multiplication.)<h2>And the point?</h2><p>I imagine that in a video game using these, a character aligned with an element would do more damage with and take less damage from attacks of that element, but take more damage from attacks with the opposing element. Aligning with mundane gives you weaker attacks, but a generalized minor defense against all elements; aligning with wild magic gives you stronger attacks, but a generalized minor weakness against all elements. There'd probably be some environmental interactions, too, like using fire abilities on a fire-aligned hex would give some bonus, etc.<p>I could also imagine a game starting with just the central circle, representing the "classical" elements, and perhaps the druidic wood/metal center. You don't know about the triangle points, so you can't use the druidic elements in alchemy, except for using wild magic to invert it. Then, later in the game, you meet some people from a different magical tradition that introduce you to the "astral" elements (the three on the triangle points), and the rest of alchemy unfolds - you can finally use the druidic element in alchemy!<p>Other than that, eh, this is just a fun thing to think about. Thank you, complicated mathematical structure, for giving me just enough rules to wrap some interesting stories around.http://www.xanthir.com/b51w0I Understand Contravariant Functors Better Now!2019-08-08T18:37:22+00:002019-08-08T17:52:40+00:00<p>Regular readers probably know that I enjoy some functional programming from time to time, and occasionally like to dive into Haskell and related stuff to teach myself something new. One thing that I've seen several times now, but not understood until now, is the idea of a "contravariant functor" or "cofunctor", and more generally the idea of something being covariant or contravariant over a type argument.<p>These concepts come from math, and I only vaguely understand what Wikipedia is talking about when they describe them. Translating over to category theory just obscures things further; it's made more difficult by the fact that both Abstract Math Wikipedia and Category Theory Wikipedia have fetishes for symbol-heavy statements that they apparently think make things clearer.<p>But at least within practical programming, I have a grasp on it, and I'll attempt to explain it to you, the abstract Reader.<h2>Review of Covariance</h2><p>So a quick review of "normal" functors. Say you've got a function that takes an integer and returns it "spelled out"; <code>numSpell(1)</code> returns <code>"one"</code>, etc. You want to call this on some integers you've got lying around, but alas, they're stored in an array and you don't know how many there are. Not to worry, Array is a functor, so you can call its <code>map()</code> method, passing <code>numSpell</code> to it, and you'll get back an Array of strings, having had <code>numSpell</code> applied to each of the integers inside the array.<p>In the abstraction of types, what happened is that you started with an Array<Int> and a function Int->String, and the map() function let you combine these two to get an Array<String>. That's a normal, covariant functor.<h2>Broken Intuition</h2><p>Now, a contravariant functor, or cofunctor (I'm so sorry, category theorists are just the absolute <b>worst</b> at terminology) is kinda similar, but different in a way that's very confusing at first.<p>While map() takes an F<A> functor and a A->B function, producing an F<B> functor, contramap() takes an F<A> cofunctor and an B->A function, and produces an F<B> cofunctor. That's... backwards??? How can you take an B->A function, and a thing of type A, and get an B out of it?<p>Turns out the problem is in our intuition, or at least, in my intuition. I'd gotten very used to thinking in terms of the "functor is a container" abstraction. That serves you well for many functors, like Array or Option, but it can lead you astray for more abstract functors. More importantly, tho, it led me to develop an intuition that the type parameter of a generic like, like Array<Int>, was telling me what sort of value was inside the object; Array<Int> is an Array containing Ints, after all.<p>But that's not it at all! The type param is just telling me that, in the methods for the object, some functions will have different type signatures depending on what I specify. For Array<Int>, it's telling me that array access, the <code>arr[i]</code> syntax, will produce an Int, whereas in Array<String> the exact same syntax will produce a String.<p>In this case, and in general for functors, the type parameter is always going to be dictating the <i>return value</i> of some methods on the object. In other words, the functor is a <i>producer</i> of some sort of value, and the type param tells me what type of value that will be.<p>With this minor insight, the fact that map() can use an A->B function to turn an F<A> into an F<B> makes sense; the F<A> will try to produce an A value normally, then you pass it thru the A->B function and get a B instead.<h2>Contravariance</h2><p>So then we can take that minor insight further: what happens when the type parameter is instead telling you an <i>argument type</i> for methods? That is, the object <i>consumes</i> values of that type?<p>For example, say you have a Serializer class, that converts things to bytes, for writing to files. It can take lots of different types of objects, so it's qualified as well: Serializer<Int>, Serializer<Bool>, etc.<p>So first, this can still be a functor (or technically, functor-adjacent) - it <i>produces</i> bytes, so you can <code>.map()</code> over those bytes to produce something else, by passing a Bytes->Whatever function.<p>But the type term, the Int or Bool or whatever, doesn't show up in that function, as it just handles Bytes. If you have a Serializer<Int>, you can't pass it the numSpell function, which is Int->String, and suddenly get a Serializer<String>, aka something which knows how to <i>consume</i> strings and <i>output</i> bytes. numSpell just doesn't help with that; it turns Ints into Strings, while our Serializer<String> is <i>given</i> Strings, and the Serializer<Int> it was based on only knows how to serialize Ints.<p>In order to transform a Serializer<Int> into a Serializer<String>, we obviously need a String->Int function. Pay attention there: with normal functors, turning F<A> into F<B> requires an A->B function. But turning Serializer<A> into Serializer<B> requires a B->A function. This makes perfect sense in concrete terms: your starting functor <i>produces</i> As, so an A->B function lets you transform that into a B so you can start producing Bs instead; but your Serializer <i>consumes</i> As, so you need a B->A function so you can start accepting Bs instead, transforming them into the As you already know how to deal with. This makes Serialize a <b>contravariant</b> functor, or <b>cofunctor</b>, and so it has a <b><code>.contraMap()</code></b> method instead, which takes the opposite-direction callback and uses it to transform itself.<h2>Profunctors, Etc.</h2><p>So that's the essence here. If a functor is covariant in one of its types, that means it is, in some sense, <i>producing</i> values of that type; they're return values for some method. If you want to transform it from F<Int> to F<String>, you need to provide an Int->String method to <code>.map()</code>. If a functor is contravariant in one of its types, it's the opposite: it <i>consumes</i> values of that type, taking them as arguments to some method. To transform it from F<Int> to F<String>, you need to provide a String->Int method to <code>.contraMap()</code>.<p>There's a two-typed structure called a Profunctor that does both at once; a Profunctor<Int, String> is contravariant in Int and covariant in String. The classic example of a Profunctor is... a function, like numSpell from earlier. In general, functions are characterized by two type parameters, A->B; for numSpell, that's Int->String. <br><p>If you want to change an A->B function to a C->D function, you need to provide two transformations, something to change the type A to C, and something to change the type B to D. Since the function <i>produces</i> a B, you want to give a B->D mapping function so it can start producing Ds instead; since it <i>consumes</i> an A, you want to give a C->A contramapping function so it can start consuming Cs instead.