Monthly Downloads: 7
Programming language: Haskell
License: BSD 3-clause "New" or "Revised" License
Tags: Data Structures    
Latest version: v0.1.0.0

knit alternatives and similar packages

Based on the "Data Structures" category.
Alternatively, view knit alternatives based on common mentions on social networks and blogs.

Do you think we are missing an alternative of knit or a related project?

Add another 'Data Structures' Package




knit ties the knot on data structures that reference each other by unique keys. Above all it aims to be easy to use - boilerplate is kept to a minimum and its API is as simple as it gets.


data Person model m = Person
  { name        :: Id model m String
  , loves       :: ForeignId model m "persons" "name" --
  , isPresident :: Bool                               --
  } deriving (Generic, KnitRecord Model)              --
data Model m = Model                                  --
  { persons :: Table Model m Person -- <----------------
  } deriving (Generic, KnitTables)

Let's break that down: when defining a domain type, like Person, we'll need two additional type parameters that will determine the final shape of that type: the model Person belongs to (it may belong to multiple models), and its "mode" (m) - whether it's resolved or unresolved. Additionally, we need to derive KnitRecord for every domain type, supplying it with a concrete model type.

Id model m t will define a key this type is referenced by (multiple keys are possible).

ForeignId is where the magic happens - in addition to the two generic parameters from above it takes a "table" name (which is just a field in the model) and a field name in the referenced domain type; the final type of the ForeignId field (both resolved and unresolved) can then be inferred from this information alone!

To define a model, wrap each domain type with a Table and autoderive the KnitTables typeclass.

Let's take a look:

alice :: Person Model 'Unresolved
alice = Person
  { name        = Id "Alice"
  , loves       = ForeignId "Bob"  -- this must be a String, since Model.persons.name is a String!
  , isPresident = False

bob :: Person Model 'Unresolved
bob = Person
  { name        = Id "Bob"
  , loves       = ForeignId "Alice"
  , isPresident = False

model :: Model 'Unresolved
model = Model
  { persons = [alice, bob]  -- `Table` is just a regular list

So far so good. Resolving an unresolved model is just a matter of calling knit:

knitModel :: Model Resolved
knitModel = case knit model of
  Right resolved -> resolved
  Left e -> error (show e)

(knit may fail due to invalid or duplicate keys). If all goes well, we'll get the following resolved model, if we were to do it by hand:

manualAlice :: Person Model 'Resolved
manualAlice = Person
  { name        = "Alice"
  , loves       = Lazy manualBob
  , isPresident = False

manualBob :: Person Model 'Resolved
manualBob = Person
  { name        = "Bob"
  , loves       = Lazy manualAlice
  , isPresident = False

manualModel :: Model 'Resolved
manualModel = Model
  { persons = [manualAlice, manualBob]

Lazy is just a simple wrapper with a get field:

data Lazy a = { get :: a }

And here it is, a nicely knit model:

name $ get $ loves (persons knitModel !! 0) -- "Bob"

The test directory contains more examples, with multiple domain types.

Cascading deletes

By supplying a Remove key instead the regular Id a record is marked for deletion:

alice :: Person Model 'Unresolved
alice = Person
  { name        = Remove "Alice"  -- mark the record for deletion
  , loves       = ForeignId "Bob"
  , isPresident = False

This will remove the record from the resolved result, as well as all other records that depend transitively on it. Invalid keys (i.e. ForeignIds that reference non-existent Ids) will still throw an error when knit-ting a model.