Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ekmett/structs

Exploring how to make a strict imperative universe in the GHC runtime system.
https://github.com/ekmett/structs

Last synced: 6 days ago
JSON representation

Exploring how to make a strict imperative universe in the GHC runtime system.

Awesome Lists containing this project

README

        

structs
==========

[![Hackage](https://img.shields.io/hackage/v/structs.svg)](https://hackage.haskell.org/package/structs) [![Build Status](https://github.com/ekmett/structs/workflows/Haskell-CI/badge.svg)](https://github.com/ekmett/structs/actions?query=workflow%3AHaskell-CI)

This package explores strict mutable data structures in Haskell.

In particular, pointer-based data structures are effectively 'half price' due to the encoding used.

However, the result is that if you use the `slot` and `field` system wrong, you can and will `SEGFAULT`.

This means the `Internal` modules are very much internal.

Some documentation is available at
[http://ekmett.github.io/structs/Data-Struct.html](http://ekmett.github.io/structs/Data-Struct.html)

Examples
--------

## Non-recursive data types

We use the template haskell helper `makeStruct` to automatically convert
a Haskell `data` definition to a `Struct`.

As an example, we create a type that mimics a tuple of integers.

```hs
makeStruct [d|
data TupleInts a s = TupleInts
{ tupleLeft, tupleRight :: a
}
|]
```
This declaration uses `makeStruct`, which will generate a bunch of
helper functions for us to use.

Notice the extra type parameter `s` in `TupleInts a s`. This is used to
carry around state information by `structs`, and so is mandatory.

```hs
-- Create a new tuple of ints.
mkTupleInts :: PrimMonad m => Int -> Int -> m (TupleInts a (PrimState m))
mkTupleInts a b = st newTupleInts a b
```

`newTupleInts` is a function that was auto-generated by `makeStructs`, whose
parameters are all the fields, which returns a `TupleInts` within a
`PrimMonad` context. Notice the use of `PrimState m` for the state
type parameter of `TupleInts`, which is used to carry the state around.

```hs
-- set the left element of the tuple
setTupleLeft :: PrimMonad m => TupleInts a (PrimState m) -> a -> m ()
setTupleLeft tup val = setField tupleLeft tup val

-- get the left element of the tuple
getTupleLeft :: PrimMonad m => TupleInts a (PrimState m) -> m a
getTupleLeft tup = getField tupleLeft tup
```

The Template Haskell generates `tupleLeft, tupleRight :: Field (TupleInts a) a`, which
can be used to get and set fields with `getField, setField`. The type signature
indicates that `tupleLeft, tupleRight` extract an `a` from a `TupleInts a`.

## Recursive data types

We identify recursive members of a struct with `Slot`s. These are like

```hs
makeStruct [d|
data LinkedList a s = LinkedList
{ val :: a,
next :: !(LinkedList a s) }
|]
```

for this definition, `makeStruct` auto-generates
`next :: Slot (LinkedList a s) (LinkedList a s)`.
Similar to the case of `Field`, the type tells us that `next` extracts
a `LinkedList a s` from a `LinkedList a s`

```
-- Make an empty linked list
mkEmptyLinkedList :: LinkedList a s
mkEmptyLinkedList = Nil
```

`Nil` is a special value which can be assigned to any `Struct`.

```hs
-- Make a linked list node with a value
mkLinkedListNode :: PrimMonad m => a -> m (LinkedList a (PrimState m))
mkLinkedListNode a = newLinkedList a Nil
```
Once again, `newLinkedList` is auto-generated by `makeStruct` which we
use to initialize the linked list.

```
-- Append a node to a linked list.
appendLinkedList :: PrimMonad m =>
LinkedList x (PrimState m)
-> x
-> m (LinkedList x (PrimState m))
appendLinkedList xs x = do
isend <- isNil <$> (get next xs)
if isend
then do
nodex <- mkLinkedListNode x
set next xs nodex
return xs
else do
xs' <- get next xs
appendLinkedList xs' x
makeStruct [d|
data LinkedList a s = LinkedList
{ val :: a,
next :: !(LinkedList a s) }
|]

-- Make an empty linked list
mkEmptyLinkedList :: LinkedList a s
mkEmptyLinkedList = Nil

-- Make a linked list node with a value
mkLinkedListNode :: PrimMonad m => a -> m (LinkedList a (PrimState m))
mkLinkedListNode a = newLinkedList a Nil

-- Append a node to a linked list.
appendLinkedList :: PrimMonad m =>
LinkedList x (PrimState m)
-> x
-> m (LinkedList x (PrimState m))
appendLinkedList xs x = do
isend <- isNil <$> (get next xs)
if isend
then do
nodex <- mkLinkedListNode x
set next xs nodex
return xs
else do
xs' <- get next xs
appendLinkedList xs' x
```

The rest is straightforward uses of `get`, `set`, `getField`, and `setField` to
manipulate the linked list as usual.

FAQ
---

1. Why can fields not be strict? (compiler error)
2. How do I free memory once `alloc`d?

Contact Information
-------------------

Contributions and bug reports are welcome!

Please feel free to contact me through github or on the #haskell IRC channel on irc.freenode.net.

-Edward Kmett