https://github.com/palatable/shoki
Purely functional data structures in Java
https://github.com/palatable/shoki
data-structures hamt hash-array-mapped-trie hashmap hashset immutable immutable-datastructures java multiset okasaki persistent-data-structure purely-functional-data-structures purelyfunctionaldatastructures queue stack
Last synced: 7 days ago
JSON representation
Purely functional data structures in Java
- Host: GitHub
- URL: https://github.com/palatable/shoki
- Owner: palatable
- License: mit
- Created: 2017-07-22T21:12:36.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2021-05-14T18:24:24.000Z (over 4 years ago)
- Last Synced: 2025-08-05T07:06:39.365Z (6 months ago)
- Topics: data-structures, hamt, hash-array-mapped-trie, hashmap, hashset, immutable, immutable-datastructures, java, multiset, okasaki, persistent-data-structure, purely-functional-data-structures, purelyfunctionaldatastructures, queue, stack
- Language: Java
- Homepage: https://palatable.github.io/shoki/
- Size: 642 KB
- Stars: 39
- Watchers: 7
- Forks: 11
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
Shōki (正気)
======
[](https://travis-ci.com/github/palatable/shoki)
[](http://search.maven.org/#search%7Cga%7C1%7Ccom.jnape.palatable.shoki)
[](https://oss.sonatype.org/content/repositories/snapshots/com/jnape/palatable/shoki/)
Purely functional, persistent data structures for the JVM.
#### Table of Contents
- [Background](#background)
- [Installation](#installation)
- [Hierarchy](#hierarchy)
- [Implementations](#implementations)
- [`StrictStack`](#implementations-StrictStack)
- [`StrictQueue`](#implementations-StrictQueue)
- [`HashMap`](#implementations-HashMap)
- [`HashSet`](#implementations-HashSet)
- [`HashMultiSet`](#implementations-HashMultiSet)
- [`TreeMap`](#implementations-TreeMap)
- [`TreeSet`](#implementations-TreeSet)
- [`TreeMultiSet`](#implementations-TreeMultiSet)
- [License](#license)
Background
----------
_Shōki_ is a library offering purely functional, persistent data structures for the JVM.
So why another data structures library? Because writing correct code is hard, and it's especially hard if your data
structures consistently advertise false interfaces. The data structures provided by _Shōki_ are built on top of
interfaces that promote type-checker participation by leveraging rich types, and encourage correct-by-construction
software with obvious usage semantics.
Standard usage of Shōki interfaces should never throw: there are no `IndexOutOfBoundsException`s that result from
seemingly innocuous `get` calls; no `UnsupportedOperationException`s because the interface you're presented with is an
_absolute fiction_, and somewhere under the covers is an immutable collection masquerading as a mutable one; no
`ClassCastExceptions` because a data structure that fundamentally only works in the presence of comparability fails
to enforce this essential constraint at compile time. Any of these things happening is considered an error in _Shōki_,
not in user code.
The constraints of the inputs and the outputs are also richly specified via their type signatures. If a lookup cannot
be guaranteed to yield a value, it will return a
[`Maybe`](https://github.com/palatable/lambda/blob/master/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java)
rather than `null` so the type-checker can remind you that you may not have received what you wanted. If a value
fundamentally cannot be zero, it will very likely be represented by a type that does not even include a "zero" term, so
you don't waste mental energy writing code to handle impossible scenarios. If the total size of a collection can
logically exceed `Integer.MAX_VALUE`, its `sizeInfo` will advertise a type that can realistically represent its size
instead of one that might represent a negative value due to overflow.
The target audience for _Shōki_ considers these characteristics not to be fine luxuries, but rather basic
quality-of-life essentials. If you think your time is too valuable to be spent staring at four methods with four
identical type signatures, trying to remember whether `peek` or `poll` throws or returns `null`, or `element` or
`remove` alters its underlying structure or doesn't; then you're in good company. Welcome!
Installation
------------
_Shōki_ alpha releases are currently available in Maven Central. These alpha releases are meant to be of high enough
quality as to be reliable in a production environment, while still allowing significant API changes to occur before a
`1.0` release.
Add the following dependency to your:
`pom.xml` ([Maven](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)):
```xml
com.jnape.palatable
shoki
1.0-alpha-2
```
`build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)):
```gradle
compile group: 'com.jnape.palatable', name: 'shoki', version: '1.0-alpha-2'
```
Hierarchy
------------
One of the core design tenants of _Shōki_'s API is that a data structure is modeled as the incremental composition of
its orthogonal capabilities and constraints.
#### Top-level orthogonal building blocks
- `Sequence`: an `Iterable` interface that offers the methods `Maybe head()` and `Sequence tail()`
- `SizeInfo`: a coproduct of `Unknown` or `Known`, where `Known` offers a method `N getSize()`
- `Sizable`: an interface that offers a method `SizeInfo sizeInfo()`
- `Membership`: an interface that offers a membership test method `boolean contains(A)`
- `RandomAccess`: `Membership` with an additional lookup method `V get(Index)`
- `Natural`: a `Number` sum type of `Zero` or `NonZero` that never overflows and offers basic type-safe arithmetic
#### Refined data structure interfaces
- `Collection`: a `Sequence` that supports `SizeInfo` yielding a `Known`
- `OrderedCollection`: a `Collection` that offers a `reverse()` method
- `SortedCollection`: an `OrderedCollection` that offers `Maybe min()`,
`Maybe max`, and `SortedCollection sort(Comparator super Ordering> comparator)` methods
- `Stack`: an `OrderedCollection` with a method `Stack cons(A)` that adds an
element to the top of the `Stack`
- `Queue`: an `OrderedCollection` with a method `Queue snoc(A)` that adds an
element to the bottom of the `Queue`
- `Set`: a `Collection` that supports `Membership`
- `MultiSet`: a `Collection` of `Tuple2` that supports `RandomAccess`
- `Map`: a `Collection>` that supports `RandomAccess>`
_Shōki_ is still very much in alpha development, so these specific interfaces are subject to change, but they should at
least offer an intuition about the way in which the design is being approached.
Implementations
------------
#### `StrictStack`
A `StrictStack` is a strictly-evaluated `Stack` that offers worst-case `O(1)` space/time for `cons`,
`head`, and `tail`.
```java
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.shoki.impl.StrictStack;
import static com.jnape.palatable.shoki.impl.StrictStack.strictStack;
public class Example {
public static void main(String[] args) {
StrictStack empty = strictStack();
boolean _true = empty.isEmpty();
Maybe nothing = empty.head();
StrictStack alsoEmpty = empty.tail();
StrictStack fooBarBaz = empty.cons("baz").cons("bar").cons("foo");
boolean _false = fooBarBaz.isEmpty();
Maybe justFoo = fooBarBaz.head();
StrictStack barBaz = fooBarBaz.tail();
Maybe justBar = barBaz.head();
StrictStack baz = barBaz.tail();
Maybe justBaz = baz.head();
StrictStack bazBarFooBaz = baz.consAll(fooBarBaz);
boolean __true = bazBarFooBaz.equals(strictStack("baz", "bar", "foo", "baz"));
}
}
```
#### `StrictQueue`
```java
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.shoki.impl.StrictQueue;
import static com.jnape.palatable.shoki.impl.StrictQueue.strictQueue;
public class Example {
public static void main(String[] args) {
StrictQueue empty = strictQueue();
boolean _true = empty.isEmpty();
Maybe nothing = empty.head();
StrictQueue alsoEmpty = empty.tail();
StrictQueue fooBarBaz = empty.snoc("foo").snoc("bar").snoc("baz");
boolean _false = fooBarBaz.isEmpty();
Maybe justFoo = fooBarBaz.head();
StrictQueue alsoFooBarBaz = empty.cons("baz").cons("bar").cons("foo");
StrictQueue barBaz = fooBarBaz.tail();
Maybe justBar = barBaz.head();
StrictQueue baz = barBaz.tail();
Maybe justBaz = baz.head();
StrictQueue bazFooBarBaz = baz.snocAll(fooBarBaz);
StrictQueue bazBarFooBaz = baz.consAll(fooBarBaz);
boolean __true = bazFooBarBaz.equals(strictQueue("baz", "foo", "bar", "baz"));
}
}
```
#### `HashMap`
A `HashMap` is an [ideal hash tree](https://lampwww.epfl.ch/papers/idealhashtrees.pdf) implementation of a
`Map` that offers amortized `O(1)` space/time for `get`, `put`, `remove`, and `contains`, and supports
custom `EquivalenceRelation`s and `HashingAlgorithm`s.
```java
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
import com.jnape.palatable.shoki.impl.HashMap;
import com.jnape.palatable.shoki.impl.HashSet;
import com.jnape.palatable.shoki.impl.StrictQueue;
import static com.jnape.palatable.shoki.api.EquivalenceRelation.objectEquals;
import static com.jnape.palatable.shoki.api.HashingAlgorithm.objectHashCode;
import static com.jnape.palatable.shoki.impl.HashMap.hashMap;
public class Example {
public static void main(String[] args) {
// same as hashMap()
HashMap empty = hashMap(objectEquals(), objectHashCode());
boolean _true = empty.isEmpty();
Maybe> nothing = empty.head();
HashMap alsoEmpty = empty.tail();
HashMap fooBarBaz = empty.put(0, "foo").put(1, "bar").put(2, "baz");
boolean _false = fooBarBaz.isEmpty();
Maybe> just0Foo = fooBarBaz.head();
HashMap alsoFooBarBaz = empty.put(2, "baz").put(1, "bar").put(0, "foo");
boolean __true = fooBarBaz.contains(0);
boolean __false = fooBarBaz.contains(-1);
// This HashSet uses the same EquivalenceRelation and HashingAlgorithm
HashSet keys = fooBarBaz.keys(); // HashSet[0, 1, 2]
StrictQueue values = fooBarBaz.values(); // StrictQueue["foo", "bar", "baz"]
HashMap barBaz = fooBarBaz.tail();
Maybe> just1Bar = barBaz.head();
HashMap baz = barBaz.tail();
Maybe> just2Baz = baz.head();
// HashMap[(2=bazbaz)]
HashMap _2bazbaz = baz.merge(baz, (s1, s2) -> s1 + s2);
}
}
```
A `HashSet` is a `Set` that is backed by a `HashMap` and offers similar space/time
complexities. Like `HashMap`, a `HashSet` supports custom `EquivalenceRelation`s and `HashingAlgorithm`s.
```java
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.shoki.impl.HashSet;
import static com.jnape.palatable.shoki.api.EquivalenceRelation.objectEquals;
import static com.jnape.palatable.shoki.api.HashingAlgorithm.objectHashCode;
import static com.jnape.palatable.shoki.impl.HashSet.hashSet;
public class Example {
public static void main(String[] args) {
// same as hashSet()
HashSet empty = hashSet(objectEquals(), objectHashCode());
boolean _true = empty.isEmpty();
Maybe nothing = empty.head();
HashSet alsoEmpty = empty.tail();
HashSet _012 = empty.add(0).add(1).add(2);
boolean _false = _012.isEmpty();
Maybe just0 = _012.head();
HashSet also012 = empty.add(2).add(1).add(0);
boolean __true = _012.contains(0);
boolean __false = _012.contains(-1);
HashSet _12 = _012.tail();
Maybe just1 = _12.head();
HashSet _2 = _12.tail();
Maybe just2 = _2.head();
HashSet _01234 = _012.union(hashSet(2, 3, 4));
HashSet _01 = _012.difference(hashSet(2, 3, 4));
HashSet _0134 = _012.symmetricDifference(hashSet(2, 3, 4));
HashSet __2 = _012.intersection(hashSet(2, 3, 4));
}
}
```
#### `HashMultiSet`
A `HashMultiSet` is a `MultiSet` that is backed by a `HashMap` and offers similar space/time
complexities. Like `HashMap`, a `HashMultiSet` supports custom `EquivalenceRelation`s and `HashingAlgorithm`s.
```java
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
import com.jnape.palatable.shoki.api.Natural;
import com.jnape.palatable.shoki.api.Natural.NonZero;
import com.jnape.palatable.shoki.impl.HashMultiSet;
import com.jnape.palatable.shoki.impl.HashSet;
import static com.jnape.palatable.shoki.api.EquivalenceRelation.objectEquals;
import static com.jnape.palatable.shoki.api.HashingAlgorithm.objectHashCode;
import static com.jnape.palatable.shoki.impl.HashMultiSet.hashMultiSet;
public class Example {
public static void main(String[] args) {
// same as hashMultiSet()
HashMultiSet empty = hashMultiSet(objectEquals(), objectHashCode());
boolean _true = empty.isEmpty();
Maybe> nothing = empty.head();
HashMultiSet alsoEmpty = empty.tail();
HashMultiSet _0x1_1x2_2x1 = empty.inc(0).inc(1).inc(2).inc(1);
boolean _false = _0x1_1x2_2x1.isEmpty();
Maybe> just0x1 = _0x1_1x2_2x1.head();
HashMultiSet also_0x1_1x2_2x1 = empty.inc(1).inc(2).inc(1).inc(0);
boolean __true = _0x1_1x2_2x1.contains(0);
boolean __false = _0x1_1x2_2x1.contains(-1);
Natural one = _0x1_1x2_2x1.get(0);
Natural two = _0x1_1x2_2x1.get(1);
HashMultiSet _1x2_2x1 = _0x1_1x2_2x1.tail();
Maybe> just_1x2 = _1x2_2x1.head();
HashMultiSet _2x1 = _1x2_2x1.tail();
Maybe> just_2x1 = _2x1.head();
HashMultiSet _2x1_3x1_4x1 = hashMultiSet(2, 3, 4);
HashMultiSet _0x1_1x2_2x1_3x1_4x1 = _0x1_1x2_2x1.union(_2x1_3x1_4x1);
HashMultiSet _0x1_1x2 = _0x1_1x2_2x1.difference(_2x1_3x1_4x1);
HashMultiSet _0x1_1x2_3x1_4x1 = _0x1_1x2_2x1.symmetricDifference(_2x1_3x1_4x1);
HashMultiSet __2x1 = _0x1_1x2_2x1.intersection(_2x1_3x1_4x1);
HashMultiSet _0x1_1x2_2x2_3x1_4x1 = _0x1_1x2_2x1.sum(_2x1_3x1_4x1);
HashSet _012 = _0x1_1x2_2x1.unique();
}
}
```
#### `TreeMap`
A `TreeMap` is a [red-black tree](https://www.cs.tufts.edu/~nr/cs257/archive/chris-okasaki/redblack99.pdf)
implementation of a `Map` that is also a `SortedCollection, K>` offering amortized
`O(log(n))` space/time for `get`, `put`, `remove`, and `contains`, and supports custom `Comparator`s.
```java
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
import com.jnape.palatable.shoki.impl.StrictQueue;
import com.jnape.palatable.shoki.impl.TreeMap;
import com.jnape.palatable.shoki.impl.TreeSet;
import static com.jnape.palatable.shoki.impl.TreeMap.treeMap;
import static java.util.Comparator.naturalOrder;
public class Example {
public static void main(String[] args) {
// same as treeMap()
TreeMap empty = treeMap(naturalOrder());
boolean _true = empty.isEmpty();
Maybe> nothing = empty.head();
TreeMap alsoEmpty = empty.tail();
TreeMap fooBarBaz = empty.put(0, "foo").put(1, "bar").put(2, "baz");
boolean _false = fooBarBaz.isEmpty();
Maybe> just0Foo = fooBarBaz.head();
Maybe> alsoJust0Foo = fooBarBaz.min();
Maybe> just2Baz = fooBarBaz.max();
TreeMap alsoFooBarBaz = empty.put(2, "baz").put(1, "bar").put(0, "foo");
boolean __true = fooBarBaz.contains(0);
boolean __false = fooBarBaz.contains(-1);
// This TreeSet uses the same Comparator
TreeSet keys = fooBarBaz.keys(); // TreeSet[0, 1, 2]
StrictQueue values = fooBarBaz.values(); // StrictQueue["foo", "bar", "baz"]
TreeMap barBaz = fooBarBaz.tail();
Maybe> just1Bar = barBaz.head();
TreeMap baz = barBaz.tail();
Maybe> alsoJust2Baz = baz.head();
// TreeMap[(2=bazbaz)]
TreeMap _2bazbaz = baz.merge(baz, (s1, s2) -> s1 + s2);
}
}
```
A `TreeSet` is a `Set` that is also a `SortedCollection` and is backed by a
`TreeMap`, offering similar space/time complexities. Like `TreeMap`, a `TreeSet` supports custom
`Comparator`s.
```java
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.shoki.impl.TreeSet;
import static com.jnape.palatable.shoki.impl.TreeSet.treeSet;
import static java.util.Comparator.naturalOrder;
public class Example {
public static void main(String[] args) {
// same as treeSet()
TreeSet empty = treeSet(naturalOrder());
boolean _true = empty.isEmpty();
Maybe nothing = empty.head();
TreeSet alsoEmpty = empty.tail();
TreeSet _012 = empty.add(0).add(1).add(2);
boolean _false = _012.isEmpty();
Maybe just0 = _012.head();
Maybe alsoJust0 = _012.min();
Maybe just2 = _012.max();
TreeSet also012 = empty.add(2).add(1).add(0);
boolean __true = _012.contains(0);
boolean __false = _012.contains(-1);
TreeSet _12 = _012.tail();
Maybe just1 = _12.head();
TreeSet _2 = _12.tail();
Maybe alsoJust2 = _2.head();
TreeSet _01234 = _012.union(treeSet(2, 3, 4));
TreeSet _01 = _012.difference(treeSet(2, 3, 4));
TreeSet _0134 = _012.symmetricDifference(treeSet(2, 3, 4));
TreeSet __2 = _012.intersection(treeSet(2, 3, 4));
}
}
```
#### `TreeMultiSet`
A `TreeMultiSet` is a `MultiSet` that is also a `SortedCollection, A>`, and is backed
by a `TreeMap`, offering similar space/time complexities. Like `TreeMap`, a `TreeMultiSet` supports
custom `Comparator`s.
```java
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
import com.jnape.palatable.shoki.api.Natural;
import com.jnape.palatable.shoki.api.Natural.NonZero;
import com.jnape.palatable.shoki.impl.TreeMultiSet;
import com.jnape.palatable.shoki.impl.TreeSet;
import static com.jnape.palatable.shoki.impl.TreeMultiSet.treeMultiSet;
import static java.util.Comparator.naturalOrder;
public class Example {
public static void main(String[] args) {
// same as treeMultiSet()
TreeMultiSet empty = treeMultiSet(naturalOrder());
boolean _true = empty.isEmpty();
Maybe> nothing = empty.head();
TreeMultiSet alsoEmpty = empty.tail();
TreeMultiSet _0x1_1x2_2x1 = empty.inc(0).inc(1).inc(2).inc(1);
boolean _false = _0x1_1x2_2x1.isEmpty();
Maybe> just0x1 = _0x1_1x2_2x1.head();
Maybe> alsoJust0x1 = _0x1_1x2_2x1.min();
Maybe> just2x1 = _0x1_1x2_2x1.max();
TreeMultiSet also_0x1_1x2_2x1 = empty.inc(1).inc(2).inc(1).inc(0);
boolean __true = _0x1_1x2_2x1.contains(0);
boolean __false = _0x1_1x2_2x1.contains(-1);
Natural one = _0x1_1x2_2x1.get(0);
Natural two = _0x1_1x2_2x1.get(1);
TreeMultiSet _1x2_2x1 = _0x1_1x2_2x1.tail();
Maybe> just_1x2 = _1x2_2x1.head();
TreeMultiSet _2x1 = _1x2_2x1.tail();
Maybe> just_2x1 = _2x1.head();
TreeMultiSet _2x1_3x1_4x1 = treeMultiSet(2, 3, 4);
TreeMultiSet _0x1_1x2_2x1_3x1_4x1 = _0x1_1x2_2x1.union(_2x1_3x1_4x1);
TreeMultiSet _0x1_1x2 = _0x1_1x2_2x1.difference(_2x1_3x1_4x1);
TreeMultiSet _0x1_1x2_3x1_4x1 = _0x1_1x2_2x1.symmetricDifference(_2x1_3x1_4x1);
TreeMultiSet __2x1 = _0x1_1x2_2x1.intersection(_2x1_3x1_4x1);
TreeMultiSet _0x1_1x2_2x2_3x1_4x1 = _0x1_1x2_2x1.sum(_2x1_3x1_4x1);
TreeSet _012 = _0x1_1x2_2x1.unique();
}
}
```
License
-------
_shōki_ is part of [palatable](http://www.github.com/palatable), which is distributed under
[The MIT License](http://choosealicense.com/licenses/mit/).