HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
yairchu
red-black-tree
Haskell2011–2015
yairchu / red-black-tree

red-black-tree

Type-Guaranteed balanced search tree

3Distinct people
9Mentions
0Explicit recommenders
8GitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

Type-Guaranteed balanced search tree

GitHub

Where it is used

No documented product use has been verified yet.

What people are saying

No recommendation phrase was found in the indexed comments.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

No other repository is repeatedly linked in the same discussions.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 5:04:57 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year9 dated mentions

Observed mentions: 2011-04-10 16:03:06 UTC to 2015-12-22 21:31:28 UTC.

01232011-01-01 00:00:00 UTC to 2012-01-01 00:00:00 UTC (exclusive): 3 mentions; 0 submissions, 3 comments20112012-01-01 00:00:00 UTC to 2013-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2013-01-01 00:00:00 UTC to 2014-01-01 00:00:00 UTC (exclusive): 3 mentions; 0 submissions, 3 comments20132014-01-01 00:00:00 UTC to 2015-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2015-01-01 00:00:00 UTC to 2016-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2015

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20112011-01-01 00:00:00 UTC2012-01-01 00:00:00 UTC303
20122012-01-01 00:00:00 UTC2013-01-01 00:00:00 UTC101
20132013-01-01 00:00:00 UTC2014-01-01 00:00:00 UTC303
20142014-01-01 00:00:00 UTC2015-01-01 00:00:00 UTC000
20152015-01-01 00:00:00 UTC2016-01-01 00:00:00 UTC202

What Hacker News says

9 sources
> We do not know a whole lot about code just because it has type checked. It may feel like you do, but that's just a feeling. You can know a whole lot because it type-checked. What exactly you know depends on how you leveraged the type system. For example, if you define a red black tree as is defined here: https://github.com/yairchu/red-black-tree/blob/master/RedBla... You know code will never generate trees that break the red/black invariants. The tree is guaranteed to be balanced by the type checking process. > The name of the function might be `sort`, but it could actually fail to sort items, in spite of type checking Unless you use a dependently-typed language. Consider this `sort` definition: sort : (in:List n a) -> (out:SortedList n a, IsPermutation in out) "List n a" is a list of length "n" of values of type "a". "SortedList n
Read the rest
a" is a sorted list of length "n" of values of type "a". "IsPermutation a b" is a proof that a is a permutation of b (returned from sort). Here's how you can define a type like SortedList: type SortedList n a = exists min. SortedList' min n a data SortedList' min n a where Empty : SortedList' min 0 a Cons : LessOrEq x y -> (x:a) -> SortedList' y n a -> SortedList' x (1+n) a SortedList thus embeds a proof alongside each element that it is less than or equal to the lower-bound of the remaining list (i.e: the next element). Type-checking this guarantees that `sort` does the right thing, modulo resource-use/performance (or termination, depending on your type checker). Resource use (bounding) can also be type-checked, but requires leveraging the type system even more. These guarantees aren't free -- you have to write the types for them, and you have to prove them by instantiating values of these types. But you can opt in or out of each of these guarantees -- leverage the type system as much as you want. With dynamic typing, you just don't have this tool at your disposal at all. No flexibility to guarantee anything. You're forced to opt out completely.
In Is Sound Gradual Typing Dead? [pdf]
Code review time! https://github.com/yairchu/red-black-tree/blob/master/RedBla... First we create two differently typed tags for Red and Black. data Red data Black Note this differs from something like data Color = Red | Black in that Red and Black are distinct at the type level---the compiler will actually complain if one is used where the other ought to be. Now we state that Trees must have Black roots. This is done using a GADT (Generalized Algebraic Data Type) which allows us to build constructors of types which restrict some of the types. data Tree a where Tree :: Node Black n a -> Tree a For instance, compare PoorTree which would let us make Trees with Red roots data PoorTree c n a = PoorTree (Node c n a) It really does nothing more than wrap up a Node to hide it and change the interface (if you want)
Read the rest
. Now we need one last piece before digging in to the core. This is the existence of Type-level Naturals. The idea is that, like Red and Black, we introduce two new distinct types, one basic and one parametric. data Zero = Zero data Succ a = Succ a which lets us write (very verbose) naturals like (Succ (Succ (Succ Zero))) == 3 and have the type reflect this exact value (Succ (Succ (Succ Zero))) :: (Succ (Succ (Succ Zero))) (Note this is a little weird, since we can also form the value (Succ 'a' :: Succ Char) which doesn't make much sense. More powerful dependently typed features help here.) Now consider just a branch in the tree, we form that with a simple data type data NodeH l r n a = NodeH (Node l n a) a (Node r n a) which tracks the left and right colors in its type using the l and r type parameters. It also guarantees the constraint that both the left and right child trees have the same black-height. So, if (black3 :: Node Black (Succ Zero) Char) then (NodeH black3 'a' black3 :: NodeH Black Black (Succ Zero) Char). And if (black2 :: Node Black Zero Char) then (NodeH black3 'a' black2) is a compile time type error. So with all these pieces, we can encode the Red/Black criteria directly into our Nodes. Remember already that if we have a Tree it must have the first Node be Black. We can now construct new Nodes in three ways, each of these ways being constrained so as to enforce the balance rules. data Node t n a where -- If we construct a Node with Nil, it forms a leaf. -- Not only are we not allowed to associate left and -- right branches (we don't get to give it a NodeH type), -- we're forced to set the numerical type parameter to Zero ~= 0. Nil :: Node Black Zero a -- If we construct a Node with BlackNode, we can pass a -- subtree with the NodeH parameter, but the type of node we -- get back is necessarily going to increase its height -- since (Succ n) is like (n + 1). BlackNode :: NodeH t0 t1 n a -> Node Black (Succ n) a -- Or we can construct a Red node. In this case, our black-height -- remains unchanged, but we can ONLY do so when we've got a NodeH -- branch with two black subNodes. RedNode :: NodeH Black Black n a -> Node Red n a So, it's a type error to construct a tree in this format which doesn't obey the Red/Black balance rules. Any transformations you write on the tree will be rejected by the compiler if they break the rules.
In What I learned using unit tests/TDD
> Testing is, as far as I can tell, the reason that these problems don't come up. Testing is a cost. It is more code to write, more code to maintain. It gives no guarantees about correctness, even of the exact feature under test. Consider the 10 lines starting from https://github.com/yairchu/red-black-tree/blob/master/RedBla... They guarantee the correctness of the invariants of the Red Black Tree, and they easily replace hundreds of lines of test code which give no guarantee. We might still need to write tests, but a lot fewer of them. Also, those we write will give us far more "bang for buck" because we can use QuickCheck property testing. > Note that both of these languages allow shared mutable data, both allow null pointers. Scala shuns null - and only has it for Java interop. All Scala developers I've discussed this with program as i
Read the rest
f null did not exist, and never use it to signify lack of a value. > I think Go occupies a good point along this spectrum, where I can write robust code without arguing with a compiler. When "arguing with a compiler", you're really being faced with bugs now rather than later, when the code is no longer in your head - or worse, in production. If the type checker rejects your program, it is almost certainly broken, and it is better to "argue with a compiler" than to just compile and get a runtime error later. Availability of engineers is a good point, though as a Haskeller, I know both companies seeking Haskell employees and Haskellers seeking employment (preferably in Haskell). Consider the flip-side of the engineers availability is the "Python paradox".
In Frameworks Round 6
> I think this is the red herring at the heart of the problem. Those bugs really aren't a big deal, they happen rarely once you're proficient and they are quickly solved on the rare occasion when they do happen. This is simply not true. I don't only work in Haskell. I also work with many colleagues on C and on Python. Virtually every bug in C or Python that we encounter, including ones we have to spend a significant amount of time debugging is a bug that cannot happen in the presence of Haskell's type system. > I'm talking about logic bugs, the kind that your compiler isn't going to find, or even that a "sufficiently smart compiler" couldn't find because it's a misunderstanding in the specification that you have to bring back to the product owner for clarification. Or bugs that occur when 2 different services on different machines are tre
Read the rest
ating each other's invariants poorly. Those are the bugs I spend time on. If you had experience with advanced type systems -- your claims here would carry more weight. People who don't know advanced type systems tend to massively understate their assurance power. For example 2 different communicating services might use "session types" to verify that their protocol guarantees invariants. Or maybe the only type-checked programs are ones forced to reject invalid inputs that violate invariants. > I haven't spent any time at all with Haskell, really, but it seems like a poor trade off to have to learn a bunch and engineer things in a way that's more difficult in order to prevent the easiest bugs. They aren't the "easiest bugs" at all. For example, consider implementing a Red Black Tree. In Go, imagine you had a bug where you rotate the tree incorrectly, such that you end up with a tree of the wrong depth -- surely you would have considered this a "logic" bug. One of the harder bugs that you wouldn't expect to catch with a mere type system. In Haskell, I can write this (lines 27-37): https://github.com/yairchu/red-black-tree/blob/master/RedBla... to specify my red black tree, with type-level enforcement of all of the invariants of the tree. With just these 10 lines, I get a compile-time guarantee that the ~120 lines implementing the tree operations will never violate the tree's invariants. This logic bug simply cannot happen. Learning a bunch of techniques is a one-time investment. After that, you will build better software for the rest of your career. Debugging far fewer bugs for the rest of your career. How could you possibly reject this trade-off, unless you expect a very short programming career?
In On Go
OCaml has a powerful module system which can be used in a way quite similar to typeclasses (https://ocaml.janestreet.com/?q=node/37). The main difference is the lack of purity, which means that the side effects of a function aren't part of a function's type. It also doesn't have the wide variety of extensions which you can use to do really crazy stuff (like verifiably correct red-black trees, https://github.com/yairchu/red-black-tree/blob/master/RedBla...).
In Functional Programming For The Rest of Us
Haskell's type system can also express interesting things most languages' type systems can't. For example: https://github.com/yairchu/red-black-tree/blob/master/AvlTre... -- in lines 13..21, an AVLTree type is defined -- with its invariants encoded in the type system. If there's a mistake in these 9 lines, you may get a wrong program. But the nice thing is that if you get just these 9 lines right -- then the hundreds of lines below it that implement an AVL tree cannot get the AVL invariants wrong. The same is also true for Red Black Trees: https://github.com/yairchu/red-black-tree/blob/master/RedBla... Lines 26..36 inclusive encode the RBTree type such that the invariants are enforced by the type-checker. In case this is unclear: the type-checker enforcing the correctness of the invariants is at compile-time. A running program is a correc
Read the rest
t program, at least from the invariants' perspective.
In Trying Haskell

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.