DEV Community

loading...
Cohere

Haskell, Vectors, and Implicit Knowledge

Betsy Haibel
Autodidactic to a fault.
Originally published at betsyhaibel.com ・6 min read

Hey folks!

This piece is a few years old now, but I'm republishing it on dev.to as I move more of my stuff here. It's kind of personal. Unlike a lot of my other work, it's not about teaching folks how to tech better -- it's about why it's so important to me to do so in an accessible way.

This winter I was teaching myself Haskell.

I'd tried before, with everyone's darling Learn You a Haskell for Great Good. My old boss had compared its "offbeat" approach to _why's Poignant Guide to Ruby, which I love. Both have an informal tone, but the similarities stop there: where the Poignant Guide has adorable cartoon foxes, Learn You a Haskell has upsetting bro humor. I think I snapped at one of the (many) fat jokes.

This time around I tried Real World Haskell, and at first things went much better. The material was more usefully organized, and the examples and exercises better suited to my learning style.
Then I came to The Problem.

The Problem

The problem set at the end of Real World Haskell’s third chapter contains the following sequence:

  1. Consider three two-dimensional points a, b, and c. If we look at the angle formed by the line segment from a to b and the line segment from b to c, it either turns left, turns right, or forms a straight line. Define a Direction data type that lets you represent these possibilities.
  2. Write a function that calculates the turn made by three 2D points and returns a Direction.
  3. Define a function that takes a list of 2D points and computes the direction of each successive triple. Given a list of points [a,b,c,d,e], it should begin by computing the turn made by [a,b,c], then the turn made by [b,c,d], then [c,d,e]. Your function should return a list of Direction.
  4. Using the code from the preceding three exercises, implement Graham's scan algorithm for the convex hull of a set of 2D points. You can find good description of what a convex hull is, and how the Graham scan algorithm should work, on Wikipedia.

Problem 9 was pretty simple:

data Direction = Left | Right | Straight

Problem 10 took me a month.

I figured it had to be simple; if it were a difficult geometry problem, it wouldn’t be in a beginners' Haskell book, it would be in a "Haskell as applied to tricky math" book. But approach after approach after approach failed me. I compared slopes. I tried complicated conditionals based on what quadrant each line was in when its base was set at origin.

Did you know that Haskell has two Data.Vector modules? There's one in the standard library that deals with Vectors like the array-like CS concept. There's one on Hackage that deals with Vectors like the geometry and second-year algebra concept. They are called the same thing. I know this now, because I found the docs for the latter, imported Data.Vector like a good girl, and spent aeons of subjective time banging my head against arity mismatch errors before giving up on a dot-product-based solution.

Eventually I finally hammered out all of the special cases.

I’d been pushing myself to finish, rather than cutting my losses and moving on in the book, because I figured it would feel good when I finally did. It didn’t. I just felt stupid, because it had taken me a month to figure out what the authors of the book had clearly expected to be simple.

I moved on to Problem 12. Looked up the Graham Scan algorithm on Wikipedia, as ordered.

Again, determining whether three points constitute a "left turn" or a "right turn" does not require computing the actual angle between the two line segments, and can actually be achieved with simple arithmetic only. For three points p1=(x1,y1), p1=(x2,y2) and p1=(x3,y3), simply compute the z-coordinate of the cross product of the two vectors p1p2 and p1p3, which is given by the expression (x2 - x1)(y3 - y1) - (y2 - y1)(x3 - x1). If the result is 0, the points are collinear; if it is positive, the three points constitute a "left turn" or counter-clockwise orientation, otherwise a "right turn" or clockwise orientation (for counter-clockwise numbered points).

Reader, I saw fire.

At first I was angry at myself, because there had been a straightforward answer and '’d "just been too stupid" to get it. Then I was angry at the authors for not putting that "simple arithmetic" answer in the text for problem 10. Then I was furious at the authors — because I realized why they hadn't.

They thought it was too simple to explain. They thought that anyone learning Haskell would have retained all the random topics that are contained in high school precalculus. They thought that anyone learning Haskell would be the Kind Of Person who just "naturally" remembers that sort of stuff.

And now, because it's a woman writing this, you're probably assuming that this is a rant about how you don't need to know math to computer.

Think again. I am That Kind Of Person. I started programming at either twelve or eight, depending on whether you count HyperCard. I learned Scheme in ninth grade. I am That Kind of Person, and the problem still made me feel like a fucking idiot. Like an impostor. Like I ought to just give up.

Over whether I could remember offhand that vector cross products were not commutative, and what implications this had for turn direction problems.

This could be a rant about the arrogance of the functional programming ivory tower. But a lot of people have made that rant already, and I'm more interested in self-reflection than in being yet another Ruby programmer who’s snotty about non-Rubyists.

Closer to Home

I teach people to program in my free time. I co-organize a drop-in group called Learn Ruby in DC, so every other week I walk people of all different skill levels through how to code. This includes honest-to-god raw beginners, as well as apprentices and juniors.

There is one understanding gap that every single one of them runs into at some point. They mix up local variables and instance variables, or variables and strings, or ivars and symbols — the exact mixup doesn't matter. They respond by randomly adding and subtracting @s and colons and quotation marks until it works, and then they sigh relief. This happens because they don’t have mental scaffolding for the differences between variables and symbols, between locals and ivars — to them, they’re just words that may or may not have magic before them, and so you fiddle with the magic until it runs.

It always takes me a few minutes of observation to figure out that that's what's happening in their heads. I’ve been writing Ruby since 2008; the scope differences between raw ivars and accessors and local variables are like breathing now. My intuitive brain assumes that everyone knows this, and I need to back up and remind it that that’s not true.

I need to do this even though in 2008, as a woman who’d worked with various conventional languages for 8 years, I'd struggled like hell to internalize that difference myself.

Am I making my students feel stupid? I hope not, but… sometimes I probably am.

Where do we go?

The educational materials we create encode our values -- often accidentally. _why's Poignant Guide reflects an ethos where programming is a joyful, creative activity. Learn You a Haskell conveys a worldview that fat jokes are hilarious fun. Real World Haskell assumes that all truly educated people remember vector-math intricacies off the top of their heads.

The values of our educational materials create our community values. People whose assumptions mesh with theirs proceed onward to community participation; people whose assumptions and values don't leak out of the pipeline. I think Haskell is a really pretty language, but I can't tell if the Haskell community wants me.

It’s human to forget that knowledge one has internalized is not common knowledge. It's human to be a poor teacher. It's human to confuse students; to make them feel terrible. It's still cruel. And when we encode that cruelty into our educational materials -- however accidentally -- we turn surviving that cruelty into something we value above all.

What implicit knowledge is needed in your language of choice? How do you deal with that around juniors?


If you found this post helpful, I'd love it if you subscribed to my company's monthly newsletter! We share some of the things we've found valuable each month, as well as exclusive insights into being a more effective programmer and engineering leader.

Discussion (3)

Collapse
jvanbruegge profile image
Jan van Brügge • Edited

Have you tried "Haskell from first Principle"?
I really like getting the first impressions of others on Haskell. For me it is hard to estimate what parts are hard to understand for other beginners if I did not had a problem with it (or forgot that I did). This probably obvious in my Haskell by example series

Collapse
drbearhands profile image
DrBearhands

This piece is a few years old now

I'm curious what your current thoughts are on this experience in particular and Haskell in general.

Collapse
bhaibel profile image
Betsy Haibel Author

I still think that it's educational malpractice to assume nontrivial working knowledge of a different, unrelated* field without highlighting that assumption for the reader.

(* "unrelated": yes, Haskell is "math-y," but category theory and linear algebra are not closely related.)

On Haskell in general? In theory I like the concision and abstraction-friendliness it enables, but its affordances for prying open those abstractions and observing how data flows through a system are pretty bad. I think if it were more tooling-friendly, and if that tooling provided strong observability, I would like it a lot; but as is, I'd rather program in languages that optimize for faster programmer feedback loops.