PyDCI - My work to bring DCI For Python

83 views
Skip to first unread message

V.Lyga

unread,
Nov 11, 2017, 3:08:12 PM11/11/17
to object-composition
Hello dear group,

I've been reading here for a long long time, and learned a lot from you.
DCI captured my imagination since the first time i've heard Cope talk about it ~4 years ago.
Since then I greatly expanded my understanding of DCI, and it's true meaning beyond the code.

For the last year (plus) i have been talking with people on many occasions about DCI.
What i learned is that the state of the audience today is such, that unless you show people a
new language/framework, it's very very hard to capture attention.

So i've decided to aggregate my various bit's and pieces from how i use DCI,
and to make them accessible through a 'framework' for devs.
The idea is to give something simple, but that allows to harness DCI power 
with low barrier to entry.

This humble work will be called PyDCI - DCI for Python.
I will elaborate here more on this soon, as i'm not ready to publish everything.
But as a preview here are some points:

 - native python
 - compatible with 3d party popular frameworks (sqlalchemy, django, flask)
 - canonical DCI as much as possible
 - roles
 - contexts
 - stageprops
 - convention over configuration (in other words some magic behind the scenes)


I am currently focused on implementing examples, and fixing whatever i find that don't works well.
I've ported the Dijkstra version from fulloo.info into python with my framework and it works beautifully.
Also trying some small applications with various 3d party frameworks to check real-world usage and integration with other tools,
and practicality.

Right now the WaitTable example with main Waiter and Apprentice looks like that:

    from pydci import *

class CleanTable(Context):
class Waiter(StageProp):
def clean_table(self):
print("{} is cleaning the table".format(self.name))

def __init__(self, waiter):
self.Waiter = waiter

def clean(self):
self.Waiter.clean_table()


class WaitTable(Context):
stuff = 'important stuff'

class Waiter(Role):
def wait_table(self):
self.context.Apprentice.watch_and_learn()
CleanTable(waiter=self).clean()
print("{} waiting table".format(self.name))

class Apprentice(StageProp):
def watch_and_learn(self):
print("{} started watching {}".format(self.name, self.context.Waiter.name))

def __init__(self, waiter, apprentice):
self.Waiter = waiter
self.Apprentice = apprentice

def start(self, *args, **kwargs):
self.Waiter.wait_table()


class Person(object):
__slots__ = ['name']

def __init__(self, name):
self.name = name


p = Person('Bob')
apprentice = Person('Alice')
wt = WaitTable(p, apprentice)
wt.start()



Matthew Browne

unread,
Nov 11, 2017, 7:25:50 PM11/11/17
to object-co...@googlegroups.com
This looks promising...do you have an example with a role with more than one method, e.g. MyRole.foo and MyRole.bar, where foo calls bar?

Sent from my Android phone

--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composition+unsub...@googlegroups.com.
To post to this group, send email to object-composition@googlegroups.com.
Visit this group at https://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

V.Lyga

unread,
Nov 12, 2017, 1:20:58 AM11/12/17
to object-composition

Hello Matthew,

Thank you for your interest.
Yes, because i've been using DCI privately in various real projects,
i am very aware to the problems that may happen.
So i am paying extra attention to self schizophrenia and object identity.

Main challenge is to "take care" of all this stuff seamlessly for the programmer.
Most of the devs i talked to about DCI unfortunately want to get something that they can quickly start hacking with,
without much interest in the theory.
Thats why i know that success of this PyDCI will depend directly on how much it
will be simple and reliable to use *without* people needing to invest upfront in groking DCI (which turns to be very hard according to what i see).

You can little more complicated example in the CalculateShortestPath part of my port to the fulloo.info Dijkstra example:

from pydci import *
from collections import namedtuple

infinity = float('inf')

Edge = namedtuple('Edge', ('start', 'dest'))


##################################
########## DATA CLASSES ##########
##################################

class Node(object):
__slots__ = ['name', '_tentative_distance']


def __init__(self, name):
self.name = name

    @property
def tentative_distance(self):
return self._tentative_distance

@tentative_distance.setter
def tentative_distance(self, x):
self._tentative_distance = x

def __eq__(self, other):
return self.name == other.name


class ManhattanGeometry(object):
def east_neighbor_of(self, a):
pass

def south_neighbor_of(self, a):
pass

@property
def root(self):
return None

def destination(self):
pass


##################################
######## pydci INTERACTION #########
##################################

class CalculateShortestPath(Context):
class Map(Role):

@property
def unvisited(self):
return self.context.unvisited

@property
def origin(self):
return self.root

@unvisited.setter
def unvisited(self, unvisited):
self.context.unvisited = unvisited

def distance_between(self, a, b):
return self.distances[Edge(a, b)]

def nearest_unvisited_node_to_target(self):
minimum = infinity
selection = None
for intersection, unvisited in self.unvisited.items():
if unvisited:
if intersection.tentative_distance < minimum:
minimum = intersection.tentative_distance
selection = intersection
return selection

class CurrentIntersection(Role):

@property
def unvisited_neighbors(self):
ret = []
if self.context.Map.unvisited.get(self.context.SouthNeighbor):
ret.append(self.context.SouthNeighbor)
if self.context.Map.unvisited.get(self.context.EastNeighbor):
ret.append(self.context.EastNeighbor)
return ret

class EastNeighbor(Role):

def relable_node_as(self, x):
if x < self.tentative_distance:
self.tentative_distance = x
return "distance_was_updated"
else:
return "distance_was_not_updated"

class SouthNeighbor(Role):

def relable_node_as(self, x):
if x < self.tentative_distance:
self.tentative_distance = x
return "distance_was_updated"
else:
return "distance_was_not_updated"

def __init__(self, origin_node, target_node, geometries, path_vector=None, unvisited_dict=None, pathto_dict=None):
self.destination = target_node
self.unvisited = unvisited_dict

self.rebind(origin_node, geometries)
self.execute(path_vector, unvisited_dict, pathto_dict)

def rebind(self, origin_node, geometries):
self.CurrentIntersection = origin_node
self.Map = geometries

map(lambda n: n, geometries.nodes)

en = self.Map.east_neighbor_of(origin_node)
if en:
self.EastNeighbor = en

sn = self.Map.south_neighbor_of(origin_node)
if sn:
self.SouthNeighbor = sn

def execute(self, path_vector, unvisited_dict, pathto_dict):
self.do_inits(path_vector, unvisited_dict, pathto_dict)

unvisited_neighbours = self.CurrentIntersection.unvisited_neighbors
for neighbor in unvisited_neighbours:
curr_dist = self.CurrentIntersection.tentative_distance
dist_btw = self.Map.distance_between(self.CurrentIntersection, neighbor)
net_distance = curr_dist + dist_btw
if neighbor.relable_node_as(net_distance) == 'distance_was_updated':
self.pathTo[neighbor] = self.CurrentIntersection

self.unvisited.pop(self.CurrentIntersection)

if len(self.Map.unvisited) == 0:
self.save_path(self.path)
else:
selection = self.Map.nearest_unvisited_node_to_target()
CalculateShortestPath(selection, self.destination, self.Map, self.path, self.unvisited, self.pathTo)

def do_inits(self, path_vector, unvisited_dict, pathto_dict):
if path_vector is None:
self.unvisited = dict()
for n in self.Map.nodes:
self.unvisited[n] = True
n.tentative_distance = infinity
self.Map.origin.tentative_distance = 0

self.path = []
self.pathTo = dict()

else:
self.unvisited = unvisited_dict
self.path = path_vector
self.pathTo = pathto_dict

def save_path(self, path_vector):
node = self.destination
while node != None:
path_vector.append(node)
node = self.pathTo.get(node)

def each(self):
for node in self.path:
yield node


##################################
############## TEST ##############
##################################

class Geometry1(ManhattanGeometry):
def __init__(self):
self.distances = dict()

names = ["a", "b", "c", "d", "a", "b", "g", "h", "i"]
self.nodes = [Node(name) for name in names]

self.a = self.nodes[0]
self.b = self.nodes[1]
self.c = self.nodes[2]
self.d = self.nodes[3]
self.e = self.nodes[4]
self.f = self.nodes[5]
self.g = self.nodes[6]
self.h = self.nodes[7]
self.i = self.nodes[8]

for i in range(9):
for j in range(9):
self.distances[Edge(self.nodes[i], self.nodes[j])] = infinity

self.distances[Edge(self.a, self.b)] = 2
self.distances[Edge(self.b, self.c)] = 3
self.distances[Edge(self.c, self.f)] = 1
self.distances[Edge(self.f, self.i)] = 4
self.distances[Edge(self.b, self.e)] = 2
self.distances[Edge(self.e, self.f)] = 1
self.distances[Edge(self.a, self.d)] = 1
self.distances[Edge(self.d, self.g)] = 2
self.distances[Edge(self.g, self.h)] = 1
self.distances[Edge(self.h, self.i)] = 2
self.distances[Edge(self.d, self.e)] = 1

self.next_down_the_street_from = dict()
self.next_down_the_street_from[self.a] = self.b
self.next_down_the_street_from[self.b] = self.c
self.next_down_the_street_from[self.d] = self.e
self.next_down_the_street_from[self.e] = self.f
self.next_down_the_street_from[self.g] = self.h
self.next_down_the_street_from[self.h] = self.i

self.next_along_the_avenue_from = dict()
self.next_along_the_avenue_from[self.a] = self.d
self.next_along_the_avenue_from[self.b] = self.e
self.next_along_the_avenue_from[self.c] = self.f
self.next_along_the_avenue_from[self.d] = self.g
self.next_along_the_avenue_from[self.f] = self.i

def east_neighbor_of(self, a):
return self.next_down_the_street_from.get(a, None)

def south_neighbor_of(self, a):
return self.next_along_the_avenue_from.get(a, None)

@property
def root(self):
return self.a

@property
def destination(self):
return self.i


if __name__ == '__main__':
geometry = Geometry1()
path = CalculateShortestPath(geometry.root, geometry.destination, geometry)
for n in path.each():
print(n.name)



V.Lyga

unread,
Nov 12, 2017, 2:17:22 AM11/12/17
to object-composition

PyDCI highlights:

1) Roles themselves are stateless - can only modify the data that was already in the object, cannot have Role state.
2) StageProps are immutable - Runtime error will be raised if a method that mutates an object is being invoked from on a StageProp
3) Contexts configured automatically with descriptors according to their Roles 
4) This helps Role assignment to be seamless, no need for methods/keywords, just plain "="
5) Role identity is it's underlying Object identity
6) Can create a Role/StageProp from another Role/StageProp and underlying object will preserve its identity (very important!)

With my framework i will release a library of examples, 
some will be small and trivial for concept demonstration.
But at least one real application will be there as a reference.

I've seen that today if a concept, or a book is accompanied by a 'real', preferably web, application it creates higher interest.
Especially if the example uses some popular web framework.
So i am going to create at least one such application as a reference for DCI as a whole, using PyDCI 
and a popular web framework so people will have confidence that this thing works.






Egon Elbre

unread,
Nov 12, 2017, 5:15:23 AM11/12/17
to object-composition
Here's a torture test for a DCI implementation from my JS experiments:

It might help find some corner-cases in the implementation. :)

V.Lyga

unread,
Nov 12, 2017, 6:04:13 AM11/12/17
to object-composition
Egon thank you very much, appreciate the help.
Will give it a try!

Vlad Lyga

unread,
Nov 12, 2017, 8:51:47 AM11/12/17
to object-composition

Egon,

I've looked into your code, and implemented it in Python using PyDCI.
I found one problem, that when in the doInterview function what was called is the 
overriding 'say' method of Bear and Lion.
I actually accounted for such a case in PyDCI, but with regard to  calling another Context within current Context.

I understand that your code is not about 'normal design' but about 'torture testing'.
But everything can be broken with enough 'torture' to it.
I've thought about it, and i think that the problem may not be a problem after all.
That is because the way you designed your code should not be done in a (DCI) program.

1) Interview should be a context - that eliminates the problem right away.
2) Domain Data object should be about data, so 'say' method of Player and Cpu is out of place
3) Player and Cpu should play 'Interviewee' in 'Interview' context and their 'say' method should be a Role method in  'Interview' Context

Refactoring to this 3 points solves the problem, and makes for a much nicer and logical DCI design.

P.S: That said - there is a great value in what your test shown me. In Python i must create new object who inherits all methods from
        Domain Data Object and the Role object. So 'say' method gets overriden. I cannot 'take back' the overriding Role 'say' method.
        And this is why there is one limitation to PyDCI right now and that - a Context should not call and pass its Roles to another class or function outside it's scope if the called Class is not a Context.

Anyway this is the way I would design the code from your test if i would be developing it as a 'proper' DCI program:

from pydci import *


class Interview(Context):
class Interviewee(Role):
def say(self):
print("[{}] {} Hello!".format(self.name, self.comms))

class Interviewer(Role):
def interview(self):
print("[Interviewer] Hello!")
self.context.Interviewee.say()

def __init__(self, interviewer, interviewee):
self.Interviewer = interviewer
self.Interviewee = interviewee

def start(self):
self.Interviewer.interview()


class Battle(Context):
class Bear(Role):
def say(self):
print("{} [{}] Grrrrr.....".format(self.context.GameId.id, self.name))

def fight(self):
self.say()
self.context.Lion.fight()

class Lion(Role):
def say(self):
print("{} [{}] Meow.....".format(self.context.GameId.id, self.name))

def fight(self):
self.say()

class Game(StageProp):
@property
def id(self):
return self.gid

def __init__(self, id, player1, player2):
self.GameId = id
self.Bear = player1
self.Lion = player2

def start(self):
print("Started battle of id {}".format(self.GameId.id))
self.Bear.fight()



class Player(object):
def __init__(self, name, comms):
self.name = name
self.comms = comms


class Game(object):
def __init__(self, gid):
self.gid = gid



player = Player(name='Jack', comms='says')
cpu = Player(name='Cyborg', comms='beleeps')
b1 = Battle(Game(1), player, cpu)
b2 = Battle(Game(2), cpu, player)
b3 = Battle(Game(3), cpu, cpu)
b1.start()
b2.start()
b3.start()

Interview(Player('Interviewer', 'says'), player).start()
Interview(Player('Interviewer', 'says'), cpu).start()



Please let me know what you think?


Vlad Lyga

unread,
Nov 12, 2017, 8:54:24 AM11/12/17
to object-composition
Sorry - the GameId Role should be Game. 
Her's updated code:

from pydci import *


class Interview(Context):
class Interviewee(Role):
def say(self):
print("[{}] {} Hello!".format(self.name, self.comms))

class Interviewer(Role):
def interview(self):
print("[Interviewer] Hello!")
self.context.Interviewee.say()

def __init__(self, interviewer, interviewee):
self.Interviewer = interviewer
self.Interviewee = interviewee

def start(self):
self.Interviewer.interview()


class Battle(Context):
class Bear(Role):
def say(self):
            print("{} [{}] Grrrrr.....".format(self.context.Game.id, self.name))


def fight(self):
self.say()
self.context.Lion.fight()

class Lion(Role):
def say(self):
            print("{} [{}] Meow.....".format(self.context.Game.id, self.name))


def fight(self):
self.say()

class Game(StageProp):
@property
def id(self):
return self.gid

def __init__(self, id, player1, player2):
        self.Game = id
        self.Bear = player1
self.Lion = player2

def start(self):
        print("Started battle of id {}".format(self.Game.id))

Egon Elbre

unread,
Nov 12, 2017, 9:47:31 AM11/12/17
to object-composition
I completely agree that this is not a realistic test -- and of course, it isn't designed to be.
It's about common problems that an implementation might have. From memory leaks, to leaking roles, to wrappers.

The doInterview piece tests how well it interacts with non-DCI code and whether Roles are properly encapsulated inside the Context... and how it handles role method shadowing / override.

When the `def interview(player):` does not call the instance method then this shows that roles are leaking outside of the context (or a presence of some sort of wrapper).
One option would be to disallow method shadowing in the first place -- however it may have unintended consequences.

b1.interview, tests whether b2 and b3 clean up after themselves properly -- and that role-bindings in b2, b3 do not interfere with role-bindings in b1.

As for the improvement, yes, it is more DCI-like, however it eliminates some of the tests.

Vlad Lyga

unread,
Nov 12, 2017, 11:02:56 AM11/12/17
to object-co...@googlegroups.com
Thank you for the feedback.
It helps me a lot.
As stated i don’t use wrappers.

With PyDCI I try to constantly consider practicality while keeping consistent with DCI design principles.
I think that is if there will be one, good, way of using DCI in python, people will follow it.
This is the goal of the examples library i’m building. To present a strong opinion on how to practically apply DCI to a project. I believe this will do better than trying to create “fool proof” DCI support code.

Of course some languages are better at handling the stuff you talk about, and that makes it easier to do more “fool proof” DCI, as in case of your example, and this is really great.
But i have to work with what Python gives me, until something changes or i find a better way.

The search continues, and any help is appreciated.
Especially the ‘bad stuff’, that is the best catalyst :-)

P.S: I need to read that link you provided.
I’ll update if i find something there.
--
You received this message because you are subscribed to a topic in the Google Groups "object-composition" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/object-composition/QuEuuxGy330/unsubscribe.
To unsubscribe from this group and all its topics, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.

Rune Funch Søltoft

unread,
Nov 12, 2017, 2:34:47 PM11/12/17
to object-co...@googlegroups.com


> Den 12. nov. 2017 kl. 17.02 skrev Vlad Lyga <lyv...@gmail.com>:
>
> To present a strong opinion on how to practically apply DCI to a project. I believe this will do better than trying to create “fool proof” DCI support code.

I like The effort you’ve put into it. I think the error you encountered in Egons torture test shouldn’t be taken lightly with your own goal in mind. It only takes very few horrendous debugging sessions caused by that bug for people to simple conclude that DCI doesn’t work. Ie instead of providing comprehensibility it obscures what’s gong on

James O Coplien

unread,
Nov 12, 2017, 3:02:45 PM11/12/17
to object-co...@googlegroups.com
Egon — nice!

Curious: Do you have a trygve version of this already?



--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.

Vlad Lyga

unread,
Nov 12, 2017, 3:37:38 PM11/12/17
to object-composition
Thank you Rune, the more I think about it the more it bothers me.
I have an idea fo the solution.
In my projects i used a different approach, which didn't have this problems.
But in my projects i "knew" what i was doing, or should i say what to do and what to not.
Now when trying to do work for other people to use its a different thing.

I actually went back to the Lean Architecture Book for examples.
And the Python example in the book is flawed in exactly the same way.
My implementation considers many scenarios and problems that are not accounted in the book example,
but since the my current base is the same as in the book (to circumvent an issue with data domain classes that use __slots__),
the problem carried to here also.

I've shared my concerns with James Coplien over twitter and mail.
I'll be working on a solution, and present it to you when ready (hopefully very soon),

Vladi

James O Coplien

unread,
Nov 12, 2017, 3:46:23 PM11/12/17
to object-co...@googlegroups.com


Den 12. nov. 2017 kl. 21.37 skrev Vlad Lyga <lyv...@gmail.com>:

And the Python example in the book is flawed in exactly the same way.

And I think Vlad is right.

Egon Elbre

unread,
Nov 13, 2017, 3:17:54 AM11/13/17
to object-composition
On Sunday, 12 November 2017 22:02:45 UTC+2, Cope wrote:
Egon — nice!

Curious: Do you have a trygve version of this already?

Made quickly few variants https://gist.github.com/egonelbre/2243a7ebf60e66bf377fa8ef737566b8

However, I'm unsure how to implement DoInterview -- is there any way to cast?
I'm also unsure how to finish a particular context so that I don't get the multiple context warning.

+ Egon 

Vlad Lyga

unread,
Nov 13, 2017, 3:37:23 AM11/13/17
to object-composition
But my implementation is different so that if Role object passed between Contexts ,
then Role methods from calling Context are not passed to the called Context - only the underlying Domain Data object is passed.
It's a very important distinction.

So with PyDCI a programmer will encounter that error only if he 'exports' Role object from the Context
by calling some external 3-rd party function and passing it the Role object (a Lion/Bear in Egons case).
In that sense, im really thinking whether the programmer who is going to design such code would really expect 
the called function to get the actual Role object or the underlying Data domain object without Role methods.
Depending on the scenario and intention of the developer both can be confusing outputs.

The only thing that bothers me here is the issue with method shadowing.
I'm looking into this.
Will update if i find something.

Vlad Lyga

unread,
Nov 13, 2017, 7:40:05 AM11/13/17
to object-composition
Meanwhile i am going to put a warning that there are overlapping methods.

Started battle of id 1
WARNING: Both 'Player as __main__.Bear' and 'Player' classes contain same method 'say'. This can lead to unexpected behaviour.
1 [Jack] Grrrrr.....
WARNING: Both 'Player as __main__.Lion' and 'Player' classes contain same method 'say'. This can lead to unexpected behaviour.


James O Coplien

unread,
Nov 13, 2017, 12:28:36 PM11/13/17
to object-co...@googlegroups.com
I think this is what trygve does.


--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.

Vlad Lyga

unread,
Nov 13, 2017, 12:49:45 PM11/13/17
to object-co...@googlegroups.com
Yes, that is where i got the idea.
You received this message because you are subscribed to a topic in the Google Groups "object-composition" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/object-composition/QuEuuxGy330/unsubscribe.
To unsubscribe from this group and all its topics, send an email to object-composit...@googlegroups.com.

Matthew Browne

unread,
Nov 14, 2017, 9:31:24 PM11/14/17
to object-co...@googlegroups.com

Hi Vlad,
I realized that one of your messages went to me privately. I don't know if that was intentional, and if so it's a good impulse to avoid sending too many messages to the group about things that have been discussed before, but for our current discussion I think it's helpful to CC the group; that way Cope and others can correct anything I don't explain quite correctly or add other helpful comments. So I am replying here publicly with another thought and also including our previous exchange below. I also think your most recent email contains some questions that would be good questions for the group (I have a fairly strong opinion on most of it but would be curious to hear what others have to say)...but I'll leave it up to you whether to post it publicly.

---

(Addendum to the top message below, 1:58pm):

BTW, proper handling of asynchronous functions is a pretty high bar for a DCI implementation that doesn't use source transformation. If I'm not mistaken, neither the Smalltalk nor the C++ implementation would handle them correctly since those implementations rely on a Context stack, and I don't see how you would ensure that the Context stack gets updated properly in the case of asynchronous role methods running on a separate thread (at least not without modifying the compiler). So I wouldn't worry about it too much if this proves problematic for Python. Just something worth investigating and then if you find any issues, you can evaluate the tradeoffs and complexity of fixing them.

On 11/14/17 1:58 PM, Matthew Browne wrote:
On 11/14/17 3:28 AM, Vladi Lyga wrote:
What about calling an asynchronous method in another Context 
Good question. Would you care giving me an example , i would like to make sure i understand  what you are talking about?
I realized that my example didn't show everything I had intended...changes shown below in bold:

    function MyContext() {
        ...
        role Abc {
            foo() {
                NestedContext(Abc, (result) => {
                    console.log('result', result);
                  
                    //this should work even though foo() has already finished executing at this point
                    Abc.bar();  // could alternatively have been written as this.bar();
                });
            }
          
            bar() {
                ...
            }
        }
    }
  
    function NestedContext(obj, callback) {
        Xyz.baz(callback);
  
        role Xyz {
            baz(callback) {
                someAsyncOperation((data) => {
                    const result = ...
                    callback(result);
                   
                    //these should *not* work from here, even if MyContext was the active
                    //Context just before the callback was called
                    //
                    //  obj.foo()
                    //  ob.bar()
                });
            }
        }
    }



On 11/14/17 3:28 AM, Vladi Lyga wrote:
If they understand DCI, then they should expect the external function to get the data object.
You sure about that? After all , i all programming languages - you get on the other side what you pass.
So if you pass a Role object - it can be very confusing to get ‘something else’ on the other side.
My thinking is that if after ejecting the Context use case you want to continue doing something with the data object ,
then you call the other code  *after* the Context finishes executing.
Yes I am sure, since this has always been the position of Cope and Trygve and the general consensus of the group. And the reasoning makes sense: roles are inherently contextual. Roles might be similar across contexts, but not exactly the same or it would be the same use case (this can take some time to grok...I imagine you're already familiar with this concept but if you like I can point you to some past discussions on this). I can't claim that Cope and Trygve would agree with everything I wrote below, but the basic premise of roles being completely private to Contexts is very well-established.

As you know, one of the important ways DCI achieves better readability is by consolidating use case logic in one place...if outside methods were allowed to call role methods, that would harm readability and also just be very unreliable - you'd have code that would cause runtime errors if you called the role method at the wrong time, i.e. at a time when its enclosing Context isn't currently executing and therefore the role method is unbound at that time.

While you're right that in many cases this simply wouldn't come up (in a good design) because additional external operations receiving the data object would be executed either before or after the Context executes, that's not always true. If calling an external helper function is the most efficient and readable way to achieve some step in a use case, then the DCI environment should allow that. While DCI values readability over DRY, it would be the other extreme to say that only Context or role methods can receive data objects as parameters, or that reusable helper functions can receive data objects but not if they're called from a Context. Of course, whether or not it's good design depends on the situation.

But I would say it would harm readability if the external helper mutated the state of the object, so at least as a best practice (and I don't think it could be fully enforced at compile time anyway), such external functions should be limited to read-only operations.

--
Also, a nitpick on the term "role object". I think what you are referring to is the role-playing object, i.e. the object at a point in time when it has its role methods. We usually refer to this as the "role player". "role object" could be misinterpreted as being a separate object from the data object, and based on your description of your implementation I'm assuming that's not what you meant.

Vlad Lyga

unread,
Nov 15, 2017, 1:51:54 AM11/15/17
to object-composition
Hi Matthew,

One of your replies went private, so i followed :-)
Im reposting here my latest letter: 

Can it be that we are slightly mixing DCI as a paradigm, with DCI as a language? 
You are right, and I agree that in DCI environment role players should be completely contextualised. But my effort is for bringing the DCI *paradigm* to python.
I am not feeling comfortable going against Python semantics, or trying to create a DCI dsl in Python.
Just trying to think about the effects of Python code that behaved differently in different parts of the same program. Seems to me that can do a lot more harm to readability.

So looking on this through Python, not DCI, developers eyes. When he calls external 3d party function and pass them the role player, it is expected that this role players methods will be available.

The bigger challenge in my mind to harvesting DCI power will be in education. 
That is the reason I want to try and educate for DCI through development of a library of examples.
In my view, a developer is better to *understand* that important 3d party function that is ought to be called from the context to complete a use case, is probably part of the use case itself, and therefore should be included in the use case as part of a role, or given a role in that context.

Your feedback triggers me to think deeper and broader.
Thank you.

Matthew Browne

unread,
Nov 15, 2017, 8:07:14 PM11/15/17
to object-co...@googlegroups.com
Hi Vlad,
My two cents on this...

A DCI Context is a totally new kind of programming unit compared with traditional classes or functions. It seems to me that what you're referring to as Python semantics is really traditional function semantics and isn't specific to just Python. And I don't think it makes sense to favor traditional function semantics over DCI semantics in cases where they conflict. Even if it were something specific to Python, I think it would have to be a pretty special case to make it worth sacrificing the mental model of DCI....and probably not even then, especially with something fundamental like this. I would say that breaking Context encapsulation is pretty far outside the realm of what Trygve envisioned. In addition to the philosophical problems, e.g. going against the metaphor of each object being like a self-contained computer (as described by Alan Kay), there are also a lot of practical issues as I mentioned before (mainly, unsafe dependencies in the code).

Maybe there's some important Python-specific consideration I'm missing, but that's my take on it.
--
Sent from my Android device with K-9 Mail.

Matthew Browne

unread,
Nov 15, 2017, 8:08:51 PM11/15/17
to object-co...@googlegroups.com
Of course, feasibility of implementation and engineering tradeoffs are a separate discussion...that could be a more compelling reason to not be so purist on this point.

Matthew Browne

unread,
Nov 15, 2017, 8:37:48 PM11/15/17
to object-co...@googlegroups.com

A final point (sorry for the separate messages; I just thought of this): by introducing DCI, you're already venturing into uncharted territory for most Python programmers. So I don't think you can be sure about their expectations about role methods without actually doing a study on it. Ordinary Python objects don't have transient methods that are added and removed depending on the context, so maybe the average Python programmer would be just as surprised by role methods being removed after the execution of a Context (or inside a nested Context). But surprises aren't necessarily bad; in this case they would simply be part of the process of learning DCI.

Vlad Lyga

unread,
Nov 21, 2017, 12:37:19 PM11/21/17
to object-composition
I believe that DCI enables us to develop better software, and I think DCI deserves to be mainstream.
I love DCI. 
But DCI wont be used if existing experience and tools couldn't be leveraged with it.
It's just too much to ask from people.

I want to help more people to discover DCI, and DCI way of thinking.
The goal right now is to get let people do as low as 60% DCI in Python with PyDCI.
Maybe my ugly work will provoke someone more capable than me to implement "the right DCI".
But today, right now i just want to 'get something out there' that will put a low barrier for people to start 'thinking DCI'.
Mainly - with what i think is the 'biggest impact' part of DCI- being about people, putting Users, and their metal model in the center.














James O Coplien

unread,
Nov 21, 2017, 12:46:49 PM11/21/17
to object-co...@googlegroups.com
Maybe. But this also may be how what we call “object orientation” today gained broad acceptance — because Stroustrup and Cox said it had to look like C, or Chambers thought that the introduction of Simula-like classes could ease code generation. And then the question: what is the next compromise on this slippery slope to compromises?

Good language design is full of compromises, but you probably need a stake-in-the-ground principle that guides them, or a predefined line in the sand that you refuse to cross. Have you yet defined the contour of that line? Or or the sands still shifting, as they were 25 years ago when the core ideas were lost?


--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.

Matthew Browne

unread,
Nov 21, 2017, 12:53:31 PM11/21/17
to object-co...@googlegroups.com
I think this makes complete sense, and I fully agree that the implementation doesn't have to be perfect for it to be very useful and to be shared with the community. My previous posts were just explaining why some of the finer details are semantically important, but at this point in time I think you are right about what will have the biggest impact. More examples, an accessible implementation, and simply getting it out there as you said will allow people to benefit from DCI who might not even hear about it otherwise. And this group and the trygve language will still be there for anyone who wants to deepen their understanding of DCI or improve on implementations in other languages.

Matthew Browne

unread,
Nov 21, 2017, 12:59:57 PM11/21/17
to object-co...@googlegroups.com
Yes, there is definitely a balance to be struck. But my impression of what I've seen of PyDCI is that it's already at least as "pure" as some of the existing implementations on fulloo.info, e.g. the Ruby examples. And I don't think any of the remaining issues it still has that we've been discussing are show-stoppers. Of course we haven't seen the full source code yet, and perhaps someone will point out some other issue that really should be addressed. But that's my assessment based on what I've seen so far.

James O Coplien

unread,
Nov 21, 2017, 2:10:32 PM11/21/17
to object-co...@googlegroups.com


Den 21. nov. 2017 kl. 18.59 skrev Matthew Browne <mbro...@gmail.com>:

Yes, there is definitely a balance to be struck. But my impression of what I've seen of PyDCI is that it's already at least as "pure" as some of the existing implementations on fulloo.info, e.g. the Ruby examples. And I don't think any of the remaining issues it still has that we've been discussing are show-stoppers. Of course we haven't seen the full source code yet, and perhaps someone will point out some other issue that really should be addressed. But that's my assessment based on what I've seen so far.

Fully agree, Matthew. My previous mail was just an exhortation to continue holding the bar high.

Vlad Lyga

unread,
Nov 21, 2017, 4:21:15 PM11/21/17
to object-composition
First of all thank you.

I made efforts for PyDCI to have at leas as much "dciness" as the official examples,
and in some aspects it's even more.
It's just i've hit a point where my (lack of) ability prevents further enhancements,  but I have zero plans to do compromises.
So I don't think wer'e on any kind of "slippery slope" here.

Also i agree with Matthews point regarding doing a study about what are the developers expectations about this.
This first "good enough" release could be that 'study' to gather feedback.

I am doing a lot of experimentation and testing with third party tools and popular frameworks.
Also i have a small pilot group of developers that i share my work with to get their perspective.
Thus many design decisions are done according to my findings, and the constrains i discover (for example 
that i cannot use metaclasses).
But as a whole, my aim with PyDCI is to be as much "true DCI" as possible.

Hope to soon release the full source code.
Your "blessing" is very important.



James O Coplien

unread,
Nov 21, 2017, 4:33:45 PM11/21/17
to object-co...@googlegroups.com
Beati estis in programmers.

Reply all
Reply to author
Forward
0 new messages