{"id":32161645,"url":"https://github.com/nico-rodriguez/type-safe-avl","last_synced_at":"2026-07-13T02:32:20.969Z","repository":{"id":40956748,"uuid":"202446455","full_name":"nico-rodriguez/type-safe-avl","owner":"nico-rodriguez","description":"Several implementations of type-safe binary search trees (BST) and balanced binary search trees (AVL).","archived":false,"fork":false,"pushed_at":"2022-12-08T04:32:12.000Z","size":5810,"stargazers_count":1,"open_issues_count":4,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-05-14T20:09:03.479Z","etag":null,"topics":["data-structures","functional-programming","haskell","type-safety"],"latest_commit_sha":null,"homepage":"https://hackage.haskell.org/package/type-safe-avl-1.0.0.1/","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nico-rodriguez.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-08-15T00:36:42.000Z","updated_at":"2022-08-14T21:45:31.000Z","dependencies_parsed_at":"2023-01-24T15:45:12.027Z","dependency_job_id":null,"html_url":"https://github.com/nico-rodriguez/type-safe-avl","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/nico-rodriguez/type-safe-avl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-rodriguez%2Ftype-safe-avl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-rodriguez%2Ftype-safe-avl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-rodriguez%2Ftype-safe-avl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-rodriguez%2Ftype-safe-avl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nico-rodriguez","download_url":"https://codeload.github.com/nico-rodriguez/type-safe-avl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nico-rodriguez%2Ftype-safe-avl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35408466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-13T02:00:06.543Z","response_time":119,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-structures","functional-programming","haskell","type-safety"],"created_at":"2025-10-21T13:56:51.164Z","updated_at":"2026-07-13T02:32:20.954Z","avatar_url":"https://github.com/nico-rodriguez.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Type-safe BST and AVL trees\n\nSeveral implementations of type-safe binary search trees (BST) and balanced binary search trees (AVL).\n\nThese differ on how the structural invariants are implemented at the type level.\n\nEach of them have their own advantages and disadvantages.\n\nThis library shows different ways of implementing invariants at the type level, each of them providing different features, all of them enforced at compile time:\n\n- Data invariant verification: assert at compile time if any given tree is BST/AVL.\n\n- Program certification: verify at compile time if the implementation of the operations over BST/AVL trees preserves the order of the keys and the balance in the heights.\n\n- Type-safe data constructors: implementation of tree constructors that throw an error at compile time if the tree being constructed is not BST/AVL.\n\n## Summary\n\n- [Prerequisites](#prerequisites)\n- [Installing](#installing)\n- [Project Structure](#project-structure)\n- [Interface](#interface)\n- [Examples](#examples)\n- [Benchmark](#benchmark)\n\n## Prerequisites\n\n```Shell\nghc (\u003e= 8.10.2)     # GHC Haskell compiler\ncabal (\u003e=3.0.0.0)   # Haskell package manager\npython3 (\u003e=3.6.8)   # Python interpreter (only needed for running the benchmarks)\nSome python package manager (e.g., pip)\n```\n\nNo external Haskell libraries are needed.\n\n## Installing\n\nIf you only want to use the library (without benchmarks), the easiest way to install it is through [Hackage](https://hackage.haskell.org/). The following command will download the library and install it:\n\n```Shell\ncabal install --lib type-safe-avl\n```\n\nIf you also want to see or run the benchmarks, clone the repository:\n\n```Shell\ngit clone https://github.com/nico-rodriguez/type-safe-avl.git\n```\n\nThe project may be used locally (inside the folder with `cabal repl`) or it may be install for system wide use:\n\n```Shell\ncd type-safe-avl\ncabal install --lib\n```\n\nFor usage examples see the [Examples](#examples) section.\n\n## Project Structure\n\n```Shell\ntype-safe-avl\n│   README.md\n└───benchmark\n|    │\n│    └── ...\n└───src/Data/Tree\n    │   ITree.hs\n    │   Node.hs\n    └───AVL\n    │   │   FullExtern.hs\n    │   │   Extern.hs\n    │   │   Intern.hs\n    │   │   Unsafe.hs\n    │   │   Invariants.hs\n    │   └───FullExtern\n    │   │   │   Examples.hs\n    │   └───Extern\n    │   │   │   Constructor.hs\n    │   │   │   Balance.hs, BalanceProofs.hs\n    │   │   │   Insert.hs, InsertProofs.hs\n    │   │   │   Lookup.hs\n    │   │   │   Delete.hs, DeleteProofs.hs\n    │   │   │   Examples.hs\n    │   └───Intern\n    │   │   │   Constructor.hs\n    │   │   │   Balance.hs\n    │   │   │   Insert.hs\n    │   │   │   Lookup.hs\n    │   │   │   Delete.hs\n    │   │   │   Examples.hs\n    │   └───Unsafe\n    │       │   Examples.hs\n    └───BST\n        │\n        └── ...\n```\n\n- For details on the `benchmark` directory, see the [Benchmark](#benchmark) section.\n\n- `ITree.hs` implements the `Tree` and `ITree` data types.\n\n- `Node.hs` implements the nodes of the trees.\n\n- Both `ITree.hs` and `Node.hs` are used in both BST and AVL type-safe trees.\n\n- The structure of `Data/Tree/AVL/` and `Data/Tree/BST/` is similar.\n\n- `Data/Tree/{BST,AVL}/Invariants.hs` implements the BST/AVL invariants.\n\n- `Data/Tree/{BST,AVL}/Unsafe.hs` contains an unsafe implementation of BST/AVL trees. The code was extracted and refactored from `Data/Tree/AVL/Extern/{Balance,Insert,Lookup,Delete}.hs`, 'un-lifting' the type level computations to the value level and removing all proof functions and terms.\n\n- `Data/Tree/{BST,AVL}/FullExtern.hs` contains the implementation of the full externalist approach. It provides functionality for performing several operations in a row over trees and checking the invariants at the end.\n\n- `Data/Tree/{BST,AVL}/Extern/` contains the implementation of BST/AVL trees and its operations for the externalist approach; likewise, `Data/Tree/{BST,AVL}/Intern/` folder contains the implementation for the internalist approach. Notice that there are no `*Proofs.hs` source files inside `Data/Tree/{BST,AVL}/Intern/`. That's because the proofs and operations in the internalist approach are implemented together.\n\n- All four approaches, `Data/Tree/{BST,AVL}/{Unsafe,FullExtern,Extern,Intern}/`, have an `Examples.hs` with usage examples of the BST/AVL operations.\n\n- In order to use BST/AVL trees, only one of `Data/Tree/{BST,AVL}/{Unsafe,FullExtern,Extern,Intern}.hs` need to be imported. They all export the basic functionality for inserting, looking and deleting on the corresponding BST/AVL tree. See the [Interface](#interface) section for exploring the functions exported by each of them.\n\n## Interface\n\nBoth externalist and internalist approaches have a common interface for manipulating BST/AVL trees. The difference in their implementation is the approach taken when defining the structural invariants that represent the conditions for a tree to be BST/AVL.\n\nThe interface for the unsafe and the full externalist approaches are only slightly different.\n\n### AVL trees (some function constraints omitted)\n\nFor the unsafe approach, the interface is\n\n- `emptyAVL :: AVL a`, an empty ITree.\n\n- `insertAVL :: Show a =\u003e Int -\u003e a -\u003e AVL a -\u003e AVL a`, inserts a node in a ITree. If the tree already has a node with the given key, the value is updated.\n\n- `lookupAVL :: Int -\u003e AVL a -\u003e Maybe a`, returns the value stored in the node with the given key. Returns `Nothing` if the key is not found.\n\n- `deleteAVL :: Int -\u003e AVL a -\u003e AVL a`, delete a node with a given key. If the tree doesn't have a node with that key, it just returns the original tree.\n\nFor the full externalist approach, the interface is:\n\n- `EmptyITree :: ITree 'EmptyTree`, an empty ITree.\n\n- `insert :: Node x a -\u003e ITree t -\u003e ITree (Insert x a t)`, inserts a node in a ITree. If the tree already has a node with key `x`, the value is updated.\n\n- `lookup :: (t ~ 'ForkTree l (Node n a1) r, Member x t ~ 'True) =\u003e Proxy x -\u003e ITree t -\u003e a`, lookup a key in a ITree. The constraint `t ~ 'ForkTree l (Node n a1) r` checks at compile time if the tree is not empty; the constraint `Member x t ~ 'True` checks at compile time that the tree `t` has a node with key `x` (ensuring that the lookup will return some value).\n\n- `delete :: Proxy x -\u003e ITree t -\u003e  (Delete x t)`, delete a node with a given key in a ITree. If the tree doesn't have a node with key `x`, it just returns the original tree.\n\n- `AVL :: ITree t -\u003e IsBSTT t -\u003e IsBalancedT t -\u003e AVL t`, constructor for type-safe AVL trees.\n\n- `mkAVL :: (IsBSTC t, IsBalancedC t) =\u003e ITree t -\u003e AVL t`, constructor for type-safe AVL from an ITree, with automatic proof term construction.\n\nFor the externalist and internalist approaches, the interface is the same and is as follows:\n\n- `emptyAVL :: AVL 'EmptyTree`, an empty AVL tree.\n\n- `insertAVL :: Proxy x -\u003e a -\u003e AVL t -\u003e AVL (Insert x a t)`, inserts a value of type `a` with key `x` in an AVL of type `AVL t`. If the tree already has a node with key `x`, the value is updated.\n\n- `lookupAVL :: (t ~ 'ForkTree l (Node n a1) r, Member x t ~ 'True) =\u003e Proxy x -\u003e AVL t -\u003e a`, returns the value, of type `a`, that is associated to the key `x` in the AVL tree of type `AVL t`. The constraint `t ~ 'ForkTree l (Node n a1) r` checks at compile time if the tree is not empty; the constraint `Member x t ~ 'True` checks at compile time that the tree `t` has a node with key `x` (ensuring that the lookup will return some value).\n\n- `deleteAVL :: Proxy x -\u003e AVL t -\u003e AVL (Delete x t)`, deletes the node with key `x` in an AVL of type `AVL t`. If the tree doesn't have a node with key `x`, it just returns the original tree.\n\n### BST trees\n\nThe interfaces are analogous to those for AVL trees. Just replace \"AVL\" for \"BST\" in the functions and modules names.\n\n## Examples\n\nFor more usage examples see the `Examples.hs` file for each approach.\n\n### Unsafe\n\n```haskell\nimport           Data.Tree.AVL.Unsafe (deleteAVL, emptyAVL, insertAVL,\n                                       lookupAVL)\nimport           Prelude              (Int, Float, Bool(True,False))\n\n\n-- | Test Balanced Binary Tree\navle = emptyAVL\navlt1 = insertAVL 20 'f' avle\navlt2 = insertAVL 60 'o' avlt1\navlt3 = insertAVL 30 'l' avlt2\n\n-- | Just 'l'\nl1 = lookupAVL 30 avlt3\n\n-- | Nothing: key 10 is not in the tree avlt8\nn = lookupAVL 10 avlt3\n\navlt4 = deleteAVL 20 avlt3\navlt5 = deleteAVL 60 avlt3\n-- | There's no error. Just return the same tree\navlt6 = deleteAVL 40 avlt3\n```\n\n### Full Extern\n\nA full externalist approach means grouping the operations over `ITree`, only constructing the BST/AVL and verifying the invariants at the end:\n\n```haskell\n{-# LANGUAGE DataKinds          #-}\n{-# LANGUAGE TypeFamilies       #-}\n\nimport           Data.Proxy               (Proxy (Proxy))\nimport           Data.Tree.AVL.FullExtern (AVL (AVL), mkAVL, ITree (EmptyITree),\n                                           delete, insert, lookup)\nimport           Data.Tree.ITree          (ITree(EmptyITree,ForkITree))\nimport           Data.Tree.Node           (mkNode)\nimport           Prelude                  (Bool (False, True), Float, Int, ($))\n\n\n-- | Proxies for the node keys\np0 = Proxy :: Proxy 0\np1 = Proxy :: Proxy 1\np2 = Proxy :: Proxy 2\np3 = Proxy :: Proxy 3\np4 = Proxy :: Proxy 4\np5 = Proxy :: Proxy 5\np6 = Proxy :: Proxy 6\np7 = Proxy :: Proxy 7\n\n-- | Insert several values in a row and check the BST and AVL invariants at the end\navl = mkAVL t\n  where\n    t = insert (mkNode p4 'f') $ insert (mkNode p2 (4::Int)) $\n        insert (mkNode p6 \"lala\") $ insert (mkNode p3 True) $\n        insert (mkNode p5 ([1,2,3]::[Int])) $ insert (mkNode p0 (1.8::Float)) $\n        insert (mkNode p7 [False]) EmptyITree\n\n-- | For performing a lookup, it's necessary to take the ITree 't' out of the AVL constructor\nl1' = case avl of\n    AVL t _ _ -\u003e lookup (Proxy::Proxy 3) t\n\n-- | Compile time error: key 1 is not in the tree avl and left subtree at node with key 4 has height +2 greater than the right subtree\n-- avlError = mkAVL $\n--   ForkITree (ForkITree\n--             (ForkITree\n--               EmptyITree (mkNode p0 'a') EmptyITree)\n--               (mkNode p7 4)\n--               EmptyITree)\n--             (mkNode p4 'f')\n--             EmptyITree\n\n-- | Delete several values in a row and check the BST and AVL invariants at the end\navlt2 = case avl of\n  AVL t _ _ -\u003e mkAVL t'\n    where\n      t' =  delete p7 $ delete p4 $ delete p1 $ delete p0 $\n            delete p2 $ delete p6 $ delete p5 $ delete p3 t\n```\n\n### Extern and Intern\n\nIn the externalist and internalist approaches, the invariants are checked after every operation performed over the tree.\n\nSome examples for the externalist approach:\n\n```haskell\n{-# LANGUAGE DataKinds          #-}\n\nimport           Data.Proxy           (Proxy (Proxy))\nimport           Data.Tree.AVL.Extern (deleteAVL, emptyAVL, insertAVL,\n                                       lookupAVL)\nimport           Prelude              (Bool (False, True), Float, Int, String)\n\n\n-- | Proxies for the node keys\np2 = Proxy :: Proxy 2\np4 = Proxy :: Proxy 4\np7 = Proxy :: Proxy 7\n\n\n-- Insert value 'f' with key 4\navlt1 = insertAVL p4 'f' emptyAVL\n-- Insert value [1,2] with key 2\navlt2 = insertAVL p2 [1,2] avlt1\n\n-- list = [1,2]\nlist = lookupAVL p2 avlt2\n\n-- | Error: key 7 is not in the tree avlt2\n-- err = lookupAVL p7 avlt2\n\n-- Delete node with key 4\navlt3 = deleteAVL p4 avlt2\n-- Delete node with key 2. Returns the empty AVL\navlt4 = deleteAVL p2 avlt3\n```\n\nFor using the internalist approach, the code example for the externalist approach works with the only difference in the import list:\n\n```haskell\nimport Data.Tree.AVL.Intern (emptyAVL,insertAVL,lookupAVL,deleteAVL)\n-- Instead of import Data.Tree.AVL.Extern (emptyAVL,insertAVL,lookupAVL,deleteAVL)\n```\n\n### Examples: BST trees\n\nThe previous examples used AVL trees. For using AVL trees just replace the imported modules along with the functions:\n\n```Shell\nmkAVL     -\u003e mkBST\nemptyAVL  -\u003e emptyBST\ninsertAVL -\u003e insertBST\nlookupAVL -\u003e lookupBST\ndeleteAVL -\u003e deleteBST\n```\n\nNotice that operations for BST may only be applied to BST trees, and operations for AVL trees may only be applied for AVL trees. For instance, this is not possible if `bst2` is not an AVL tree:\n\n```haskell\ndeleteAVL (Proxy::Proxy 5) bst2\n```\n\nIt gives an error at compile time.\n\n## Benchmark\n\n### Structure\n\n```Shell\ntype-safe-avl\n│   README.md\n└───benchmark\n     └───AVL\n     │   └───FullExtern\n     |   |   |   Benchmark.hs\n     │   │   │\n     |   |   └───Insert\n     │   │   │\n     |   |   └───Lookup\n     │   │   │\n     |   |   └───Delete\n     │   └───Extern\n     │   │   └── ...\n     │   └───Intern\n     │   │   └── ...\n     │   └───Unsafe\n     │       └── ...\n     └───BST\n     │\n     └── ...\n\n```\n\nThere are benchmarks for both BST and AVL trees for each approach (clone the repository for viewing or using the benchmarks). For instance, in the folder `benchmark/AVL/FullExtern/`\nthere are three folders and one source file: `Insert/`, `Lookup/`, `Delete/` and `Benchmark.hs`.\n\nInside each folder there are different source files for benchmarking each operation under several tree sizes; they're split in different files\nin order to be able to measure not only the running times but also the compile times.\n\nThe source files `Benchmark.hs` performs all of the running time benchmarks defined inside the folders `Insert/`, `Lookup/` and `Delete/`.\n\n### Running the benchmark\n\nGiven the compilation effort for running the benchmarks, the Hackage package comes without them.\n\nIn order to run the benchmarks, you should clone the repository and switch to `benchmark` branch.\n\nFor running all the benchmarks, use `benchmark/avg-bench.py` script. There's a `requirements.txt` file with all the python dependencies needed to run the script.\n\nIf you're using `pip`, just run `pip install -r requirements.txt`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnico-rodriguez%2Ftype-safe-avl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnico-rodriguez%2Ftype-safe-avl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnico-rodriguez%2Ftype-safe-avl/lists"}