For future reference: https://daboross.net/. Blog & RSS feed not yet built as of 2024-09-13.
This is why I hate the "born in the wrong generation" cranks. Imagine not being at the right time and place to experience this.
This is not a bit. No irony here. High art. Beautiful. Elevated by both the wild context behind why it exists and the fact that it's deeply moving even without.
I do not know why a four page comic about a dog that is a sink makes me feel like I am falling through the world, but it does. I wound up rambling about this on a server, about media that makes me feel this way. In particular, I was talking about how Post-Op Androgyne (PWYW, very worth it) describes how What Right Have I views transition in a way that Hold My Name does not. And then I was thinking about compulsions versus chosen actions and the ecstasy of identity and the intersection of intentionality and the numinous, and then I was just completely lost.
I do not know why a four page comic about a dog that is a sink makes me feel like I am falling through the world, but it does, and that is where art lies.
Specifically, people don't appreciate the commitment they're making by exposing an ADT's implementation.
This commitment is
Whenever this implementation changes in any way, I want every usage that matches on it to break
That's a lot!
This isn't to say that ADTs with exposed implementations are bad. They're perfectly fine if you either don't care (the definition of Maybe is not going to change any time soon) or if breaking downstream code is something you want. E.g. if you're writing a compiler, you want every function that consumes the AST to break until it handles the newly added cases.
But it means that ADTs are just not a good choice if want to be able to add new cases or change the implementation details of existing ones in the future.
And I wish functional programmers recognized this! It already starts with the most basic examples we give people.
data Shape
= Circle Float Float Float
| Rectangle Float Float Float Float
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)This particular one is from Learn You A Haskell, but there are many similar ones in many other beginner resources.
This is a terrible way to model a shape! Your program is almost certainly going to want to handle more shapes than just circles and rectangles at some point, but with this definition you can't add more without breaking every consumer!
And it's not like Haskell doesn't have the tools to deal with this! This case could absolutely be defined more reasonably like this.
data Circle = Circle Float Float Float
data Rectangle = Rectangle Float Float Float Float
class Surface shape where
surface :: shape -> Float
instance Surface Circle where
surface (Circle _ _ r) = pi * r ^ 2
instance Surface Rectangle where
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)If this reminds you of object oriented programming, that's because the OOP solution is just plain better here and I wish functional programmers would stop rejecting anything that reminds them of it.
Algebraic data types are great but they're not a panacea.