Skip to content

Commit

Permalink
Version 0.5.0 - major refactorings and added DCI specifications
Browse files Browse the repository at this point in the history
Also changed group id to "org.scaladci" at Sonatype repositories
  • Loading branch information
marcgrue committed Jan 29, 2014
1 parent ef8a615 commit da5cd0b
Show file tree
Hide file tree
Showing 17 changed files with 1,009 additions and 210 deletions.
76 changes: 29 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Scala DCI

###Let runtime objects play Roles in a DCI Context!
###Enabling true object oriented programming in Scala

The [Data Context Interaction (DCI)](http://en.wikipedia.org/wiki/Data,_context_and_interaction) paradigm by Trygve Reenskaug and James Coplien embodies true object-orientation where
The [Data Context Interaction (DCI)](http://en.wikipedia.org/wiki/Data,_context_and_interaction)
paradigm by Trygve Reenskaug and James Coplien embodies true object-orientation where
runtime Interactions between a network of objects in a particular Context
is understood _and_ coded as first class citizens.

Expand All @@ -13,19 +14,16 @@ case class Account(name: String, var balance: Int) {
def decreaseBalance(amount: Int) { balance -= amount }
}
```
This is what we in DCI sometimes call a "dumb" data class. It only "knows" about its own data and how to manipulate that.
The concept of a transfer between two accounts is outside its responsibilities and we delegate this to a Context - the MoneyTransfer context class. In this way we can keep the Account class very slim and avoid that it gradually takes on more and more responsibilities for each use case it participates in.

From a users point of view we might think of a money transfer as

- "Move money from one account to another"

and after some more thought specify it further:

- "Withdraw amount from a source account and deposit the amount in a destination account"

That could be our "Mental Model" of a money transfer. Interacting "concepts" like our "Source" and "Destination" accounts of our mental model we call "Roles" in DCI and we can define them and what they do to accomplish the money transfer in a DCI Context:

This is a primitive data class only knowing about its own data and how to manipulate that.
The concept of a transfer between two accounts we can leave outside its responsibilities and instead
delegate to a "Context" - the MoneyTransfer Context class. In this way we can keep the Account class
very slim and avoid that it gradually takes on more and more responsibilities for each use case
it participates in.

Our Mental Model of a money transfer could be "Withdraw amount from a source account and deposit the
amount in a destination account". Interacting concepts like our "Source"
and "Destination" accounts we call "Roles" in DCI. And we can define how they can interact in our
Context to accomplish a money transfer:
```Scala
@context
class MoneyTransfer(Source: Account, Destination: Account, amount: Int) {
Expand All @@ -48,12 +46,15 @@ class MoneyTransfer(Source: Account, Destination: Account, amount: Int) {
```

We want our source code to map as closely to our mental model as possible so that we can confidently and easily
overview and reason about _how the objects will interact at runtime_! We want to expect no surprises at runtime. With DCI we have all runtime interactions right there! No need to look through endless convoluted abstractions, tiers, polymorphism etc to answer the reasonable question _where is it actually happening, goddammit?!_
overview and reason about _how the objects will interact at runtime_! We want to expect no surprises at runtime.
With DCI we have all runtime interactions right there! No need to look through endless convoluted abstractions,
tiers, polymorphism etc to answer the reasonable question _where is it actually happening, goddammit?!_

At compile time, our @context macro annotation transforms the abstract syntax tree (AST) of our code to enable our
_runtime data objects_ to "have" those extra Role Methods. Well, I'm half lying to you; the
objects won't "get new methods". Instead we call Role-name prefixed Role methods that are
lifted into Context scope which accomplishes what we intended in our source code. Our code gets transformed as though we had written this:
lifted into Context scope which accomplishes what we intended in our source code. Our code gets transformed as
though we had written this:

```Scala
class MoneyTransfer(Source: Account, Destination: Account, amount: Int) {
Expand Down Expand Up @@ -109,10 +110,8 @@ which will now turn into
```
Role methods will always take precedence over instance methods when they have the same signature.

## `self` and `this` references to Role Players
As an alternative to using the Role name to reference a Role Player we can also use `self` or `this`.

If we use `self` our Role definitions would look like this:
## `self` reference to a Role Player
As an alternative to using the Role name to reference a Role Player we can also use `self`:
```Scala
role Source {
def withdraw {
Expand All @@ -127,26 +126,7 @@ If we use `self` our Role definitions would look like this:
}
}
```

If we choose, we can also use `this` as a reference inside Role methods to refer to the Role Player.
This is not Scala-idiomatic though since `this` would normally point to the Context instance!
We have allowed this special DCI-idiomatic meaning of `this` since we want to be able to think of
"this role" (or "this Role Player") while we define our Role methods:
```Scala
role Source {
def withdraw {
this.decreaseBalance(amount)
Destination.deposit
}
}

role Destination {
def deposit {
this.increaseBalance(amount)
}
}
```
Using `self` or `this` doesn't change how Role methods take precedence over instance methods.
Using `self` doesn't change how Role methods take precedence over instance methods.


## Multiple roles
Expand Down Expand Up @@ -182,7 +162,7 @@ class MyContext(SomeRole: MyData) {
```
As you see in line 3, OtherRole is simply a reference pointing to the MyData instance (named SomeRole).

Inside each role definition we can still use `self` and `this`.
Inside each role definition we can still use `self`.

We can add as many references/role definitions as we want. This is a way to
allow different Roles of a Use Case each to have their own meaningful namespace for defining their
Expand Down Expand Up @@ -272,23 +252,25 @@ which is not the intention and result after source code transformation.

## Scala DCI demo application

In the [Scala DCI Demo App](https://github.com/DCI/scaladci/tree/master/demo) you can see an example of how to create a DCI project.
In the [Scala DCI Demo App](https://github.com/DCI/scaladci/tree/master/demo) you can see an example of
how to create a DCI project.


## Using Scala DCI in your project

ScalaDCI is available for Scala 2.10.3 at [Sonatype](https://oss.sonatype.org/index.html#nexus-search;quick%7Eshapeless). To start coding with DCI in Scala add the following to your SBT build file:
ScalaDCI is available for Scala 2.10.3 at [Sonatype](https://oss.sonatype.org/index.html#nexus-search;quick%7Eshapeless).
To start coding with DCI in Scala add the following to your SBT build file:

libraryDependencies ++= Seq(
"com.marcgrue" %% "scaladci" % "0.4.1"
"org.scaladci" %% "scaladci" % "0.5.0"
),
addCompilerPlugin("org.scalamacros" % "paradise" % "2.0.0-M2" cross CrossVersion.full)


## Building Scala DCI
- Scala DCI is built with SBT 0.13.1.
- Latest release is version 0.4.1
- Ongoing development of 0.5.0-SNAPSHOT continues in the master branch
- Latest release is version 0.5.0
- Ongoing development of 0.6.0-SNAPSHOT continues in the master branch
- Pull requests and comments are welcome! :-)

To build Scala DCI on your local machine:
Expand Down
6 changes: 0 additions & 6 deletions core/src/main/scala/scaladci/util/MacroHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,6 @@ trait MacroHelper[C <: MacroContext] {
}
}


type W[T] = c0.WeakTypeTag[T]
type PFT[A] = PartialFunction[Tree, A]
type PF = PartialFunction[Tree, Tree]
type ToExpr[A] = PFT[Expr[A]]

object Name {
def apply(s: String) = newTermName(s)
def unapply(name: Name): Option[String] = Some(name.decoded)
Expand Down
18 changes: 15 additions & 3 deletions core/src/test/Scala/scaladci/semantics/Context.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import util._

class Context extends DCIspecification {

// A Context ...
// Context ...

"Cannot define a nested DCI Context" >> {

Expand Down Expand Up @@ -47,16 +47,28 @@ class Context extends DCIspecification {
success
}

"Can instantiate a nested Context" >> {

"Todo: Can instantiate a nested Context" >> {

// Todo
ok
}


"Only one DCI Context can be active at a time" >> {
"Todo: Can play a Role in another Context" >> {

// Todo
ok
}



// "Only one DCI Context can be active at a time"
// Isn't that only possible with parallel execution ??

"Todo: Cannot be active at the same time as another Context" >> {

// Todo ??
ok
}
}
58 changes: 58 additions & 0 deletions core/src/test/Scala/scaladci/semantics/Interactions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package scaladci
package semantics
import util._

class Interactions extends DCIspecification {

// Interactions ...

"Are preferably distributed between Roles" >> {

@context
case class Context(a: Data) {
val b = a
val c = a

def distributedInteractions = a.foo

role a {
def foo = 2 * b.bar
}

role b {
def bar = 3 * c.baz
}

role c {
def baz = 4 * self.i
}
}
Context(Data(5)).distributedInteractions === 2 * 3 * 4 * 5
}


"Can occasionally be centralized in Context" >> {

@context
case class Context(a: Data) {
val b = a
val c = a

def centralizedInteractions = a.foo * b.bar * c.baz * c.number

role a {
def foo = 2
}

role b {
def bar = 3
}

role c {
def baz = 4
}
}
Context(Data(5)).centralizedInteractions === 2 * 3 * 4 * 5
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package scaladci
package semantics
import util._

class RolePlayer extends DCIspecification {
class ObjectInstantiation extends DCIspecification {

// An object can play a Role when ...
// Object instantiation ...

"Object is passed to Context" >> {
"In environment (passed to Context)" >> {

@context
case class Context(Foo: Data) {

def trigger = Foo.foo

role Foo {
Expand All @@ -21,11 +22,14 @@ class RolePlayer extends DCIspecification {
Context(obj).trigger === 42 * 2
}

"Object is instantiated in Context" >> {

"In Context" >> {

@context
case class Context(i: Int) {

val Foo = Data(i)

def trigger = Foo.foo

role Foo {
Expand All @@ -36,12 +40,14 @@ class RolePlayer extends DCIspecification {
Context(42).trigger === 42 * 2
}

"Object is a reference to another object/RolePlayer in Context" >> {

"As new variable referencing already instantiated object/RolePlayer" >> {

@context
case class Context(Foo: Data) {

val Bar = Foo

def trigger = Bar.bar

role Bar {
Expand Down
62 changes: 59 additions & 3 deletions core/src/test/Scala/scaladci/semantics/RoleBinding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,67 @@ import util._

class RoleBinding extends DCIspecification {

// Role binding ...

"All Roles in a Context are bound to objects in a single, atomic operation" >> {
"Through identifier of environment object" >> {

// Todo
ok
// Data object `Foo` binds to Role `Foo` since they share name
@context
case class Context(Foo: Data) {
def trigger = Foo.foo

role Foo {
def foo = Foo.i * 2
}
}

val obj = Data(42)
Context(obj).trigger === 42 * 2
}


"Through variable name identifying new object" >> {

@context
case class Context(i: Int) {
// Variable `Foo` identifies instantiated `Data` object in Context
// Variable `Foo` binds to Role `Foo` since they share name
val Foo = Data(i)
def trigger = Foo.foo

role Foo {
def foo = Foo.i * 2
}
}

Context(42).trigger === 42 * 2
}


"Dynamically through new variable name identifying another object/RolePlayer in Context" >> {

@context
case class Context(Foo: Data) {

// Variable `Bar` references `Foo` object
// Variable `Bar` binds to Role `Bar` since they share name
val Bar = Foo

def trigger = Bar.bar

role Bar {
def bar = Bar.i * 2
}
}

val obj = Data(42)
Context(obj).trigger === 42 * 2
}


"Todo: All Roles in a Context are bound to objects in a single, atomic operation" >> {

// Todo: How to demonstrate/reject un-atomic bindings?
ok
}
}
3 changes: 2 additions & 1 deletion core/src/test/Scala/scaladci/semantics/RoleBody.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import util._

class RoleBody extends DCIspecification {

// A Role body ...
// Role body ...

"Can define role method(s)" >> {

Expand Down Expand Up @@ -116,6 +116,7 @@ class RoleBody extends DCIspecification {
success
}


"Cannot define other types" >> {

expectCompileError(
Expand Down
Loading

0 comments on commit da5cd0b

Please sign in to comment.