Manikin is an embedded Domain Specific Language (DSL) that implements 'Worldly' Objects - Objects that participate and interact in the scope of Worlds. With Manikin, you can guard Object states with pre- and post- Conditions, and optionally track all stateful Effects and dispatched Messages.
Manikin is heavily inspired by the Eiffel programming language, Software Transactional Memory and Worlds that have similar goals.
A Message is dispatched to an Object via a World, which is then functionally updated and passed through. Because Worlds can track all intermediate and previous Object states, it is very easy to rollback state in case of failure, or to retry work after conflicts.
All Objects, Messages and Worlds should be immutable: they always create new states based on old states. The core API is carefully designed for this exact purpose: don't share mutable state!
For the Purists out there: Manikin enables Purely Functional Object Orientated Programming (PFOOP 🙃) without resorting to Monads. (Monadic programming in Java is very verbose, as Java lacks convenient flatMap syntax).
Manikin can also be configured to run on top of multi-threaded, concurrent or distributed Worlds - backed by databases such as CockroachDB - with strong Serializability guarantees.
You can succinctly specify Objects, Identities, Messages, Conditions and Effects with Manikin and statically type them (making heavy use of Java Generics).
Additionally, Manikin reduces the amount of Java boilerplate code to the absolute minimum by providing a fluent builder pattern to specify Messages. Java boilerplate can be reduced even more with Records or project Lombok.
The core abstract API is developed in Java 1.8 and has NO dependencies. There are also Scala and Kotlin versions available that are build on top of the core Java API but require less boilerplate.
Conformance tests can be found at org.jmanikin.test. You can call these tests from your favourite test library.
If you like higher order state, but shy away from mutable shared state, you should try Manikin!
Here is a minimal Java example to get a feel of Manikin. Please also have a look at the Scala and Kotlin version.
public class SimpleTransfer {
public static void main(String[] args) {
AccountModule.ID a1 = new AccountModule.ID("A1");
AccountModule.ID a2 = new AccountModule.ID("A2");
TransferModule.ID t1 = new TransferModule.ID(1L);
Value<SimpleWorld, Void> result = new SimpleWorld().
send(a1, new AccountModule.Open(50.0)).
send(a2, new AccountModule.Open(80.0)).
send(t1, new TransferModule.Book(a1, a2, 30.0));
System.out.println(result.obj(a1).value().balance); // 20.0
System.out.println(result.obj(a2).value().balance); // 110.0
}
}
public interface AccountModule {
class ID implements Id<Account> {
public final String id;
public ID(String id) { this.id = id; }
@Override public Account init() { return new Account(0.0); }
}
class Account {
public final Double balance;
public Account(Double balance) { this.balance = balance; }
}
interface AccountMsg extends LocalMessage<ID, Account, Void> { }
class Open implements AccountMsg {
public final Double initial;
public Open(Double initial) { this.initial = initial; }
@Override public Msg<ID, Account, Void> local() { return
pre(() -> initial >= 0.0).
app(() -> new Account(initial)).
eff(() -> null).
pst(() -> obj().balance == initial);
}
}
class Deposit implements AccountMsg {
public final Double amount;
public Deposit(Double amount) { this.amount = amount; }
@Override public Msg<ID, Account, Void> local() { return
pre(() -> amount > 0.0).
app(() -> new Account(obj().balance + amount)).
eff(() -> null).
pst(() -> obj().balance == old().balance + amount);
}
}
class Withdraw implements AccountMsg {
public final Double amount;
public Withdraw(Double amount) { this.amount = amount; }
@Override public Msg<ID, Account, Void> local() { return
pre(() -> amount > 0.0 && obj().balance >= amount).
app(() -> new Account(obj().balance - amount)).
eff(() -> null).
pst(() -> obj().balance == old().balance - amount);
}
}
}
public interface TransferModule {
class ID implements Id<Transfer> {
public final Long id;
public ID(Long id) {
this.id = id;
}
@Override public Transfer init() {
return new Transfer(null, null, 0.0);
}
}
class Transfer {
public final AccountModule.ID from;
public final AccountModule.ID to;
public final Double amount;
public Transfer(AccountModule.ID from, AccountModule.ID to, Double amount) {
this.from = from;
this.to = to;
this.amount = amount;
}
}
interface TransferMsg extends LocalMessage<ID, Transfer, Void> { }
class Book implements TransferMsg {
public final AccountModule.ID from;
public final AccountModule.ID to;
public final Double amount;
public Book(AccountModule.ID from, AccountModule.ID to, Double amount) {
this.from = from;
this.to = to;
this.amount = amount;
}
@Override public Msg<ID, Transfer, Void> local() { return
pre(() -> amount > 0.0 && from != to).
app(() -> new Transfer(from, to, amount)).
eff(() -> {
send(from, new AccountModule.Withdraw(amount));
return send(to, new AccountModule.Deposit(amount));
}).
pst(() -> obj(from).balance + obj(to).balance == old(from).balance + old(to).balance);
}
}
}