{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiftpyet7n7laxrcagssh742pf4augmlomtjb7p3ofb7pyezyxhpbe",
"uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mgxjyv72saw2"
},
"path": "/t/sneak-peek-bolt-math/13766#post_13",
"publishedAt": "2026-03-13T16:29:56.000Z",
"site": "https://discourse.haskell.org",
"tags": [
"@1"
],
"textContent": "I have had to convalesce for a few days (expending energy on personal matters, nothing to worry about) so I have not been able to respond to the recent comments yet, but in my absence I don’t want everyone to go down a rabbit hole of worrying - so I will say this, to give you some idea as to my thought process:\n\n * Keep the feedback coming - this is a sneak peak, so these concerns are precisely what I want to hear, and after all, _this thread is a negotiation of the API contract_ with its prospective userbase, no? I will still be making my decisions, but I want them to be informed and deliberate decisions\n * It is easy to narrow a heterogenous interface by restricting it, but difficult to expand a homogenous one in the other direction, so for this reason I think the heterogenous foundation is necessary\n * There is a suite of homogenous operators, which is something I think some people may have missed, as I only mentioned it in text and did not paste the code.\n * Fundeps require propagating those arrows through subclasses which becomes rather untenable at scale - I don’t want to end up with typeclasses with 10 type parameters, I think that’d be worse\n * Definitely, a prime concern is people are worried about leaky laws - I am glad to know this, because I also have some law typeclasses / constraints related stuff (well, more like 3 different approaches that I am still toying with) that I didn’t want to distract from what I had, but it sounds as though there is interest so I shall make sure to include some more details on this next time\n * Regarding the requirement of large hierarchy implementations, that has been driving me towards some design changes that alleviate that particular issue - a better / more thorough breakdown of the root class / functions seems to be effective along with appropriate choices of default implementations - here the heterogenous / homogenous separation has actually been incredibly valuable towards making that happen\n\n\n\nSomething worth noting on its own, I have found a large amount of success with very specific design pattern to solve a very specific problem:\n\n * What do we do about mutually definable type classes - eg, the typeclass equivalent of `{-# MINIMAL ... #-}`?\n\n\n\nMath is filled with them - for example: lattices vs boolean algebra vs logic, it is the same exact 5 functions, but in 3 different domains, and any one implements the other two). Anyway, that is the problem, and the solution has been to leave the classes as completely distinct but to choose an appropriate default definition from one of the other typeclasses, such that you still need eg all 3 typeclass instances, but only one has a definition and the other two are empty because we get them for free. The result in practice is that implementing the large hierarchy requires very little actual implementation, because the chosen defaults propagate nicely.\n\nHowever, type errors can be quite horrible, and specifically:\n\njackdk:\n\n> Not being able to reason from the type of a result of basic algebraic operation back through to the argument types could force a lot of manual type signatures in intermediate `let`s in complex numeric code, ballooning contexts in your type signatures, or other syntactic noise.\n\nIt does, and I absolutely agree; this library makes heavy use of `AllowAmbiguousTypes` and `UndecideableInstances` (safely though!), but this means chaining ambiguous things together requires type annotation - but I _think that this is the cost_ , it sort of derives from the same reasons why mathematicians define things the way they do. Annoyingly, for higher level concepts like grades and blades and wedges, type annotation is **not optional** (eg dimensionally-annotated types and functions such as `grade @1 @1`), and many critical operations are heterogenous by nature (eg, `wedge :: v -> v -> Bivector v`) or downright polymorphic (because also `wedge :: v -> Bivector v -> Trivector v`), and that sort of makes the argument a moot point because it would require manually implementing an infinite number of classes eg instead of a single `wedge :: Grade i v -> Grade j v -> Grade (i + j) v` (and I’m being real nice here not bringing up multivectors here because would you like even more definitions of wedge?)\n\n_Luckily_ , as mentioned before, it is easier to ‘seal’ a heterogenous function with a subclass to constraint it, and, if using the homogenous functions or operators, it does only require one annotation to propagate - so I think the solution is, I keep the heterogenous foundation, but do more work to promote use of the homogenous operators in the contexts where they do apply. If I can do the messy grunt work of linking together heterogenous ops, then _only implementers need deal with the rough stuff_ , leaving a clean surface interface for the users. Like, for instance, I provide a convenient.\n\n* * *\n\nSo! Lawlessness, and type inference is bumped up in priority. Keep the feedback coming!",
"title": "Sneak Peek: Bolt Math"
}