Skip to content

Commit

Permalink
event world with efficient common postfix
Browse files Browse the repository at this point in the history
  • Loading branch information
Robbert van Dalen committed Mar 9, 2021
1 parent 65f22ef commit 190905f
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 103 deletions.
69 changes: 33 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,67 @@ See also the Kotlin [version](https://github.com/odipar/kmanikin).

### Bank example
```scala
package org.jmanikin.scala.example.bank

// The Bank example with less boilerplate
object Bank {

import org.jmanikin.core._
import org.jmanikin.scala.message.ScalaMessage._
import org.jmanikin.world._
import org.jmanikin.scala.world.EventWorld.EventWorld

case class AccountId(iban: String) extends Id[Account] {def init = Account()}
case class AccountId(iban: String) extends Id[Account] { def init = Account() }
case class Account(balance: Double = 0.0)
trait AccountMsg[W <: World[W]] extends ScalaMessage[W, AccountId, Account, Unit]
trait AccountMsg extends ScalaMessage[AccountId, Account, Unit]

case class Open[W <: World[W]](initial: Double) extends AccountMsg[W] {
case class Open(initial: Double) extends AccountMsg {
def scala =
pre { initial > 0.0 }.
app { obj.copy(balance = initial) }.
eff { }.
pst { obj.balance == initial }
pre { initial > 0.0 }.
app { obj.copy(balance = initial) }.
eff { }.
pst { obj.balance == initial }
}

case class Deposit[W <: World[W]](amount: Double) extends AccountMsg[W] {
case class Deposit(amount: Double) extends AccountMsg {
def scala =
pre { amount > 0.0 }.
app { obj.copy(balance = obj.balance + amount) }.
eff { }.
pst { obj.balance == old.balance + amount }
pre { amount > 0.0 }.
app { obj.copy(balance = obj.balance + amount) }.
eff { }.
pst { obj.balance == old.balance + amount }
}

case class Withdraw[W <: World[W]](amount: Double) extends AccountMsg[W] {
case class Withdraw(amount: Double) extends AccountMsg {
def scala =
pre { amount > 0.0 }.
app { obj.copy(balance = obj.balance - amount) }.
eff { }.
pst { obj.balance == old.balance - amount }
pre { amount > 0.0 && obj.balance > amount }.
app { obj.copy(balance = obj.balance - amount) }.
eff { }.
pst { obj.balance == old.balance - amount }
}

case class TransferId(id: Long) extends Id[Transfer] {def init = Transfer()}
case class TransferId(id: Long) extends Id[Transfer] { def init = Transfer() }
case class Transfer(from: AccountId = null, to: AccountId = null, amount: Double = 0.0)
trait TransferMsg[W <: World[W]] extends ScalaMessage[W, TransferId, Transfer, Unit]
trait TransferMsg extends ScalaMessage[TransferId, Transfer, Unit]

case class Book[W <: World[W]](from: AccountId, to: AccountId, amount: Double) extends TransferMsg[W] {
case class Book(from: AccountId, to: AccountId, amount: Double) extends TransferMsg {
def scala =
pre { amount > 0.0 && from != to }.
app { Transfer(from, to, amount) }.
eff { send(from, Withdraw(amount)); send(to, Deposit(amount)) }.
pst { obj(from).balance + obj(to).balance == old(from).balance + old(to).balance }
}
pre { amount > 0.0 && from != to }.
app { Transfer(from, to, amount) }.
eff { send(from, Withdraw(amount)); send(to, Deposit(amount)) }.
pst { obj(from).balance + obj(to).balance == old(from).balance + old(to).balance }
}

def main(args: Array[String]): Unit = {
val a1 = AccountId("A1")
val a2 = AccountId("A2")
val t1 = TransferId(1)

val r = new SimpleWorld().
send(a1, Open(50)).
send(a2, Open(80)).
send(t1, Book(a1, a2, 30))
val result = EventWorld().
send(a1, Open(50)).
send(a2, Open(80)).
send(t1, Book(a1, a2, 30))

println("a1: " + r.obj(a1).value) // a1: Account(20.0)
println("a2: " + r.obj(a2).value) // a2: Account(110.0)
println(result.obj(a1).value().balance) // 20.0
println(result.obj(a2).value().balance) // 110.0
}
}

```


4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name := "jmanikin-scala"

resolvers += Resolver.mavenLocal

version := "0.2.2"
version := "0.3"

scalaVersion := "2.13.3"

libraryDependencies += "org.jmanikin" % "jmanikin-core" % "0.2.3"
libraryDependencies += "org.jmanikin" % "jmanikin-core" % "0.3"
187 changes: 187 additions & 0 deletions src/main/scala/data/RAList.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package data

/**
* Purely functional Random Access (RA) List.
*
* An RAList is very easy to implement and understand.
*
* Another very nice (and underrated!) property is their canonical/unique representation.
* We leverage that property to calculate the common postfix between two RALists in O(log(n)).
*
* See https://dl.acm.org/doi/abs/10.1145/224164.224187.
*/
object RAList {
import scala.util.hashing.MurmurHash3._

trait RATree[A] {
def hash: Int
def first: A
def second: RATree[A]
def third: RATree[A]
def toList: List[A]
override def hashCode = hash
}

case class RAEmptyTree[A]() extends RATree[A] {
def hash = 0
def first = err
def second = err
def third = err
def toList = List()
def err = sys.error("EMPTY TREE")
}

case class RALeafTree[A](first: A) extends RATree[A] {
def hash = mix(first.hashCode, -first.hashCode)
def second = RAEmptyTree()
def third = RAEmptyTree()
def toList = List(first)
}

case class RABinTree[A](first: A, second: RATree[A], third: RATree[A]) extends RATree[A] {
val hash = mix(third.hash, mix(second.hash, first.hashCode()))
def toList = first +: (second.toList ++ third.toList)
}

case class RANode[A](size: Long, tree: RATree[A])

object RAList {
def apply[A](): RAList[A] = RAEmpty()
}

sealed trait RAList[A] {
def head: RANode[A]
def tail: RAList[A]
def depth: Int
def size: Long
def isEmpty: Boolean
def toList: List[A] = reverse(this).reverse.map(_.tree.toList).fold(List[A]())(_ ++ _)
def +:(a: A): RAList[A] = {
if(isEmpty || tail.isEmpty) RACons(RANode(1, RALeafTree(a)), this)
else {
val n1 = head
val n2 = tail.head

if (n1.size != n2.size) RACons(RANode(1, RALeafTree[A](a)), this)
else RACons(RANode(1 + n1.size + n2.size, RABinTree(a, n1.tree, n2.tree)), tail.tail)
}
}

private def reverse(l: RAList[A]): List[RANode[A]] = {
var result = List[RANode[A]]()
var self = l
while (!self.isEmpty) { result = self.head +: result ; self = self.tail }
result
}

private def reverse(l: List[RANode[A]]): RAList[A] = {
var result = RAList[A]()
var self = l
while (self.nonEmpty) { result = RACons(self.head, result) ; self = self.tail }
result
}

def postfix(p: Long): RAList[A] = {
if (isEmpty || p >= size) this
else {
var pst = List[RANode[A]]()
var rvs = reverse(this)
var pp = p

while (pp >= rvs.head.size) { pst = rvs.head +: pst ; pp = pp - rvs.head.size ; rvs = rvs.tail }

reverse(pst.reverse ++ postfix(pp, rvs.head))
}
}

private def postfix(p: Long, node: RANode[A]): List[RANode[A]] = postfix(p, node.size, node.tree)
private def postfix(p: Long, size: Long, tree: RATree[A]): List[RANode[A]] = {
if (p == 0) List()
else if (p >= size) List(RANode(size, tree))
else {
val half = size / 2

if (p == (size - 1)) List(RANode(half, tree.third), RANode(half, tree.second))
else {
if (p <= half) postfix(p, half, tree.third)
else RANode(half, tree.third) +: postfix(p - half, half, tree.second)
}
}
}

def commonPostfix(other: RAList[A]): Long = {
if (other.size > size) other.commonPostfix(this)
else {
val self = postfix(other.size)
assert(self.size == other.size)
commonPostfix(self, other)
}
}

private def commonPostfix(l1: RAList[A], l2: RAList[A]): Long = {
var r1 = reverse(l1)
var r2 = reverse(l2)
var s = 0L
while (r1.nonEmpty && r2.nonEmpty && r1.head.tree == r2.head.tree) {
assert(r1.head.size == r2.head.size)
s = s + r1.head.size
r1 = r1.tail
r2 = r2.tail
}
if (r1.isEmpty) s
else {
assert(r1.head.size == r2.head.size)
commonPostfix(r1.head.tree, r2.head.tree, r1.head.size) + s
}
}

private def commonPostfix(l1: RATree[A], l2: RATree[A], size: Long): Long = {
if (size == 0) 0
else if (size == 1) {
if (l1.first == l2.first) 1
else 0
}
else {
val halve = size / 2

if (l1.third != l2.third) commonPostfix(l1.third, l2.third, halve)
else if (l1.second != l2.second) halve + commonPostfix(l1.second, l2.second, halve)
else if (l1.first != l2.first) size - 1
else size
}
}
}

case class RAEmpty[A]() extends RAList[A] {
def head = err
def tail = err
def depth = 0
def size = 0
def isEmpty = true
def err = sys.error("Empty RAList")
}

case class RACons[A](head: RANode[A], tail: RAList[A]) extends RAList[A] {
def depth = 1 + tail.depth
def size = head.size + tail.size
def isEmpty = false
}

def main(arg: Array[String]): Unit = {
val x = 40
var l = RAList[Int]()

for (i <- 0 until x) l = i +: l

var r = l
while (!r.isEmpty) {
println(r.head.size)
r = r.tail
}

val r1 = 999 +: 1000 +: 1001 +: 2 +: 1 +: 0 +: RAEmpty[Int]()
val r2 = 1000 +: 1001 +: l
val pst = r1.commonPostfix(r2)
println("pst: " + l.toList)
}
}
31 changes: 15 additions & 16 deletions src/main/scala/org/jmanikin/scala/example/bank/Bank.scala
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
package org.jmanikin.scala.example.bank

import org.jmanikin.scala.world.EventWorld.EventWorld

// The Bank example with less boilerplate
object Bank {

import org.jmanikin.core._
import org.jmanikin.scala.message.ScalaMessage._
import org.jmanikin.world._
import org.jmanikin.scala.world.EventWorld.EventWorld

case class AccountId(iban: String) extends Id[Account] {def init = Account()}
case class AccountId(iban: String) extends Id[Account] { def init = Account() }
case class Account(balance: Double = 0.0)
trait AccountMsg[W <: World[W]] extends ScalaMessage[W, AccountId, Account, Unit]
trait AccountMsg extends ScalaMessage[AccountId, Account, Unit]

case class Open[W <: World[W]](initial: Double) extends AccountMsg[W] {
case class Open(initial: Double) extends AccountMsg {
def scala =
pre { initial > 0.0 }.
app { obj.copy(balance = initial) }.
eff { }.
pst { obj.balance == initial }
}

case class Deposit[W <: World[W]](amount: Double) extends AccountMsg[W] {
case class Deposit(amount: Double) extends AccountMsg {
def scala =
pre { amount > 0.0 }.
app { obj.copy(balance = obj.balance + amount) }.
eff { }.
pst { obj.balance == old.balance + amount }
}

case class Withdraw[W <: World[W]](amount: Double) extends AccountMsg[W] {
case class Withdraw(amount: Double) extends AccountMsg {
def scala =
pre { amount > 0.0 }.
pre { amount > 0.0 && obj.balance > amount }.
app { obj.copy(balance = obj.balance - amount) }.
eff { }.
pst { obj.balance == old.balance - amount }
}

case class TransferId(id: Long) extends Id[Transfer] {def init = Transfer()}
case class TransferId(id: Long) extends Id[Transfer] { def init = Transfer() }
case class Transfer(from: AccountId = null, to: AccountId = null, amount: Double = 0.0)
trait TransferMsg[W <: World[W]] extends ScalaMessage[W, TransferId, Transfer, Unit]
trait TransferMsg extends ScalaMessage[TransferId, Transfer, Unit]

case class Book[W <: World[W]](from: AccountId, to: AccountId, amount: Double) extends TransferMsg[W] {
case class Book(from: AccountId, to: AccountId, amount: Double) extends TransferMsg {
def scala =
pre { amount > 0.0 && from != to }.
app { Transfer(from, to, amount) }.
Expand All @@ -53,13 +52,13 @@ object Bank {
val a2 = AccountId("A2")
val t1 = TransferId(1)

val r = new EventWorld().
val result = EventWorld().
send(a1, Open(50)).
send(a2, Open(80)).
send(t1, Book(a1, a2, 30))

println("r: " + r.world.actions)
println("a1: " + r.obj(a1).value) // a1: Account(20.0)
println("a2: " + r.obj(a2).value) // a2: Account(110.0)
println(result.world().events.toList.reverse.map(_.prettyPrint).mkString(""))
println(result.obj(a1).value().balance) // 20.0
println(result.obj(a2).value().balance) // 110.0
}
}
Loading

0 comments on commit 190905f

Please sign in to comment.