"Code must be Chunkable"

B

Brian Candler

Thomas said:

Interesting.

Part 2 explores some ideas in Ruby with a simple bank transfer example.
What I don't get is why you'd want to inject the logic of transferring
money between two accounts into one of the accounts and then call it
there. Surely you could just do it all in the context object itself? In
that case, the transfer-money context would just become what I'd call a
'controller'.

So I'd find it useful to see a more extensive example which shows the
benefits of working this way.

Then at the end, it says that an account isn't really an object at all -
but all the previous code has shown it as a concrete object (e.g.
Account.find(id)). So an example of what an account role *should* look
like in code would be good.
 
I

Intransition

Interesting.

Part 2 explores some ideas in Ruby with a simple bank transfer example.
What I don't get is why you'd want to inject the logic of transferring
money between two accounts into one of the accounts and then call it
there. Surely you could just do it all in the context object itself? In
that case, the transfer-money context would just become what I'd call a
'controller'.

So I'd find it useful to see a more extensive example which shows the
benefits of working this way.

I just finished watching the 2nd video. I agree with you. Coplien does
an awful job of explaining things. Trygve, despite his age, does a
much better job.
Then at the end, it says that an account isn't really an object at all -
but all the previous code has shown it as a concrete object (e.g.
Account.find(id)). So an example of what an account role *should* look
like in code would be good.

I don't know what he is talking about. It's as if he thinks, if
something isn't solid it isn't an object. And his whole speel about
logging-in is not a usecase because there's no business goal, is silly
too. He's splitting hairs over words and as much as he thinks DCI is
so cool, I'm not sure he actually "gets it" himself. However, at the
very beginning he does point out the main point of the whole pursuit
-- code readability.

His Ruby code, btw, wasn't very well written, would not run and worse,
I don't think represents DCI well either. So I threw together a fix
that I think represents it at least a little better. Still a simple
bank transfer, but it works, so that in it's self is an
improvement ;)

One thing I would point out, Coplien's TransferMoneyContext is a
Command pattern --a class that encapsulates a single action. I don't
think it's necessary to go that far. While my example follows his, if
I were doing it otherwise, I would probably make it an
AccountInteractions class and define methods within it for all the
ways in which two accounts could interact.

#
class Account
# simple account db
def self.accounts
@@accounts ||= {}
end

def self.find(accountID)
accounts[accountID]
end

attr :accountID
attr :balance

def initialize(accountID, initialBalance)
Account.accounts[accountID] = self

@accountID = accountID
@balance = initialBalance
end
end

#
class SavingsAccount < Account
def initialize(accountID, initialBalance)
super(accountID, initialBalance)
end

def availableBalance; @balance; end
def decreaseBalance(amount); @balance -= amount; end
def increaseBalance(amount); @balance += amount; end

def updateLog(message, time, amount)
puts "%s %s #%s $%.2f" % [message, time, accountID, amount.to_f]
end
end

# Use Case (Context)
class MoneyTransfer
attr :amount
attr :source_account
attr :destination_account

def initialize(amt, sourceID, destID)
@amount = amt
@source_account = Account.find(sourceID)
@destination_account = Account.find(destID)
end

def execute
source_account.extend TransferSource
destination_account.extend TransferDestination

source_account.withdraw(amount)
destination_account.deposit(amount)

#source_account.unextend TransferSource
#destination_account.unextend TransferDestination
end
end

# Account Role
module TransferSource
def withdraw(amount)
raise "Insufficiant Funds" if balance < amount
decreaseBalance(amount)
updateLog "Transfer Out", Time.now, amount
end
end

# Account Role
module TransferDestination
def deposit(amount)
increaseBalance(amount)
updateLog "Transfer In", Time.now, amount
end
end

# try it out

SavingsAccount.new(1, 500)
SavingsAccount.new(2, 100)

transfer_case = MoneyTransfer.new(50, 1, 2)
transfer_case.execute


Notice the remarked #unextend lines. For a real implementation of DCI,
we would want to remove these roles once we used them, but Ruby's
extend doesn't allow that, of course.

So the bottom line I think is this. You work out usecases (i.e.
contexts) for actually doing things. You make your objects pretty dumb
--primarily state bags. You figure out the roles your objects must
play to satisfy those use cases and code those. Then you code the
usecases with the roles and objects so as to get the job done. The
whole programs then becomes easier to read b/c you are reading
usecases first, which explains things as the interaction of roles
played by simple objects. And presto the "Code is Chunkable".

(P.S. I also think this is much more like AOP then Coplien is willing
to admit.)
 
B

Brian Candler

Thomas said:
def execute
source_account.extend TransferSource
destination_account.extend TransferDestination

source_account.withdraw(amount)
destination_account.deposit(amount)

#source_account.unextend TransferSource
#destination_account.unextend TransferDestination
end
end

Thank you. That was pretty much what I was thinking. After all, in a
real bank transfer, the "source account" isn't responsible for carrying
out the transfer, the bank clerk is.

In a play, there's a single script. And if either Romeo or Juliet
forgets their lines, it's the prompter at the front of the stage who
tells them what to say next. (OK, perhaps that's taking the analogy too
far :)

I can see a specific case where this context/role split would work well.
In Rails-type apps, I've wondered before how best to implement logic
which clearly belongs in the model, but which is affected by properties
of the controller. Behaviour dependent on the user's timezone preference
is one example; adding updated_by and updated_ip stamps is another.

Rails solves the timezone problem by just stuffing it into a
thread-local variable, which is horrible.

Having a 'context' object available to the model at execution time makes
total sense. And as long as you inject the context at the same time as
you inject the methods which make use of that context, then you know the
two are aligned; it's safe because you know that code can't be used
elsewhere.

In practice this might mean you eschew the model's own 'save' method in
favour of a ModelUpdater context and a UpdatableModel role.
 
B

Brian Candler

Thomas said:
Coplien does
an awful job of explaining things. Trygve, despite his age, does a
much better job.

I'd say "Trygve, because of his age, does a much better job" :)

(I also started by toggling in binary machine-code. Admittedly that was
switches and LEDs rather than switches and lamps)
 
M

Michel Demazure

Thomas said:
Notice the remarked #unextend lines. For a real implementation of DCI,
we would want to remove these roles once we used them, but Ruby's
extend doesn't allow that, of course.

Could you not extend again by a Module which would undefine the added
methods ?
 
B

Brian Candler

Michel said:
Could you not extend again by a Module which would undefine the added
methods ?

It's a moot point in the common case where objects don't persist (i.e.
Account.find(id) creates a new object from info in the database)

I think it could be done more cleanly with a facade/proxy object. This
would have an added advantage that concrete methods in the underlying
object could not call back to the context (which they should not be able
to do; only the injected methods should do this)
 
M

Michel Demazure

Brian said:
It's a moot point in the common case where objects don't persist (i.e.
Account.find(id) creates a new object from info in the database)

I think it could be done more cleanly with a facade/proxy object. This
would have an added advantage that concrete methods in the underlying
object could not call back to the context (which they should not be able
to do; only the injected methods should do this)

I agree.
M.
 
I

Intransition

It's a moot point in the common case where objects don't persist (i.e.
Account.find(id) creates a new object from info in the database)

I think it could be done more cleanly with a facade/proxy object. This
would have an added advantage that concrete methods in the underlying
object could not call back to the context (which they should not be able
to do; only the injected methods should do this)

I played around with the concepts a bit more. You can see what I came
up with here:

http://gist.github.com/301909

I did a couple of interesting things (though I suppose I may be taking
it too far) I thought of a Context as a Scene in a play, in which I
defined the roles upfront (ie. at the class level) -- I use the Anise
gem to do this, btw. And, despite what was said in the lecture, I was
able to use polymorphism with regard to the roles. This approach seems
very interesting. I was able to define two methods of the same name
that can act on the same object, but dependent on the role it plays.
Thus the Context has a method that is dispatched to all the roles.
While my code is from perfect the approach itself does seem like it
could be useful for large applications. (It feels like overkill for
small libraries though).
 
B

Brian Candler

I was able to define two methods of the same name
that can act on the same object, but dependent on the role it plays.

I like that. Your base class Role is exactly what I was thinking of as a
proxy.
Thus the Context has a method that is dispatched to all the roles.

Hmm, that's very clever, but it's a bit too magic for me. It's
multicasting (pun not intended); I'd probably just iterate in the
context to make it explicit.
 
I

Intransition

Here's a noddy version (minus annotations)

http://gist.github.com/302016

Very nice --very clean. That close to how first thought about it too,
but some of those other ideas came to mind in the processes and I
wanted to experiment with them to see how they would play out.

I think you are right that the "role dispatching" is too magic. I like
it in the sense that it feels like a natural fit for concurrent
processing. However, at the very least, there needs to be a way to do
it explicitly as you have done.

The class level casting on the other hand, I am finding very
appealing. The reason being that it provides a very natural limiting
structure to scope of a context, i.e. one role per attribute per
context. By casting at the instance level, a context can do anything
whatsoever, each method could take actions completely unrelated. But
having the casting the at the class level ensures the methods will
have a interrelated coherence.
 
J

Jörg W Mittag

Brian said:
Thomas said:
[...]
Then at the end, it says that an account isn't really an object at all -
but all the previous code has shown it as a concrete object (e.g.
Account.find(id)). So an example of what an account role *should* look
like in code would be good.

I have been following DCI on and off ever since James's JAOO 2008
interview and more closely since James's and Trygve's March 2009
article. I won't even try and pretend that I understand as little as
1% of this stuff, but there is one important idea that I have carried
around with me, ever since I read all those "it's just traits"
comments on the Artima article: one thing that I always need to remind
myself of, is that doing DCI in Ruby is like doing OO in C: it's only
a *very* rough approximation which lacks much of the expressive power
and often confuses the idea with the implementation.

Whenever I think "it's just traits/aspects/mixins/responsibilities and
why does he say this isn't an object when it clearly is?" I picture
myself trying to explain OO to a C programmer, in C, and constantly
answering questions like "it's just structs and function pointers and
why do you keep calling it an object when it clearly is a struct?" and
yelling back at him "because there are no objects in C, idiot, structs
is all I have!"

I believe that, like OO or logic programming, DCI is only going to
start to *really* shine (or, as the case may be, bomb spectacularly)
when we have DCI languages. There were many logic systems in Lisp
before Prolog, and as Alan Kay recently pointed out, there were object
systems in assembly going back as far as 1952, but before Prolog,
Simula and Smalltalk nobody cared, and nobody understood. It's hard to
see the real value of the idea behind the implementation, if the
implementation leaks all over the place as structs+function pointers
as objects and methods do in C and objects, classes and mixins as
data, context and roles do in Ruby.

Unfortunately, for this dynamic role injection stuff, we run into one
of the (very few) limitations of Ruby. We cannot just, as Trygve says,
"subclass the compiler" and "add to and delete methods from the method
dictionary" in Ruby (although we probably *can* in Rubinius) like
Trygve's Squeak implementation does or subvert the compiler like
James's C++ template metaprogramming implementation does.

Ironically, this stuff, which looks like a match made in heaven for
Ruby, is one of the *very few* instances where C++'s static
metaprogramming outshines Ruby's dynamic metaprogramming and the leaky
abstraction of ECMAScript actually *helps* rather than hurts.
(Although I *do* have a seed of a spark of a hunch on how to fake
dynamic class composition.)

Anyway, I don't really have anything useful to contribute to this
discussion other than the tip that it helps to constantly remind
myself that DCI in a non-DCI language is always only an approximation.

jwm
 
J

Jörg W Mittag

Jörg W Mittag said:
[...]
I believe that, like OO or logic programming, DCI is only going to
start to *really* shine (or, as the case may be, bomb spectacularly)
when we have DCI languages.

BTW: I *do* realize that this contradicts what James and Trygve have
been saying, that DCI is a paradigm that enables "good OO" in
*existing mainstream* languages.

jwm
 
M

Michel Demazure

Jörg W Mittag said:
Jörg W Mittag said:
[...]
I believe that, like OO or logic programming, DCI is only going to
start to *really* shine (or, as the case may be, bomb spectacularly)
when we have DCI languages.

BTW: I *do* realize that this contradicts what James and Trygve have
been saying, that DCI is a paradigm that enables "good OO" in
*existing mainstream* languages.

jwm

I am not familiar enough with the DCI paradigm. But from times to times,
I feel that Ruby does not go far enough with duck typing. Modules are
not really objects ("everything is an object", they say), but a way to
assign methods to objects "from outside".

Yes, but you can quack like a duck ("include duck"), then miauw like a
cat ("include cat"), but you cannot come back to quacking. "Once a duck,
always a duck !", ODAD !

Is there a deep reason forbidding a more clever dispatching allowing to
de-include modules and/or re-include modules ?

md
 
M

Michel Demazure

Michel said:
Is there a deep reason forbidding a more clever dispatching allowing to
de-include modules and/or re-include modules ?

This would avoid the use of ad-hoc classes or singletons to disguise
modules.

md
 
R

Ryan Davis

Modules are=20
not really objects ("everything is an object", they say), but a way to=20=
assign methods to objects "from outside".

how are modules not objects? do they not have state? do they not have =
behavior? are they not instances of a class?
Is there a deep reason forbidding a more clever dispatching allowing = to=20
de-include modules and/or re-include modules ?

the 'un' gem enables this.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top