I took the algorithm and stuck it into a toy example for training purposes. Without DCI. And I had no problems with identity. I had the ability to add state to nodes too.
It reminded me of a "view model", namely a model of a model, adapted for a specific MVC view, or in this case just a specific algorithm.
Sadly it made me again question the real value of DCI.
I'm
not surprised. Toy examples cannot be expected to show the benefits of
DCI, two of them being clean mental models and readable code. Even the "write
code first, think later" approach works if the example is
sufficiently simple. I got some help from DCI when I wrote my Shapes
animation example I first used POJO, the result was chaotic code.
Then DCI, which was straight forward. May be a new POJO implementation
would be OK; I haven't tried it.
The
benefits of DCI may easier to see if you ask somebody other than the
original coder to read the code and explain how it works. (The reader
being familiar with DCI, of course.)
Trygve Reenskaug mailto: try...@ifi.uio.no
Morgedalsvn. 5A http://folk.uio.no/trygver/
N-0378 Oslo Tel: (+47) 22 49 57 27
Norway
Hi Rune,
That's not part of the algorithm, is it? Or what do you mean?
I agree, it's hard to communicate, especially on forums, and that
there will be cases where DCI (or similar) helps and others where it
doesn't. I'm not saying DCI won't help - I'm saying I feel there are
other mechanisms / designs / paradigms that help equally well.
On reflection, I think that Jim's requirements were a little far
fetched. While he was trying to show the problems that can happen, I
don't believe that they really do happen that often. I have been
using view models and other such constructs for years, and had never
even heard of object schizophrenia before encountering this forum.
The train stations are simple domain objects related to the bigger
picture, namely timetable software. In the context of figuring out the
shortest (or rather quickest) path, the train stations and the paths
between them are converted to nodes and edges and Dijkstra's algorithm
is applied.
@Rune: There is no communication between any objects. Perhaps that is
the point. I am manipulating data here using, an algorithm. My
domain model is fully anemic. As such, it is hardly object oriented,
and perhaps that is why I don't need DCI in this case, in order to
make my code readable, reviewable and simple. It is sufficient as it
is. Even if it were in a huge system hiding behind 100s of thousands
of lines of code.
Cheers,
Ant
On Dec 12, 8:38 pm, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
> Communication is hard. What I tried to say, no doubt unsuccessfully, was
> that it is easy to write readable code if the problem is sufficiently
> simple. I don't understand why you introduced trains and stations for
> the Dijkstra algorithm. Both you and I can write much simpler code that
> implements the Dijkstra algorithm in a perfectly readable way. We can,
> for example, simply copy the code given in Wikipedia. Jim's code was
> intended to illustrate some interesting aspects of DCI, not to show
> superior code. He even changed the problem to illustrate what he wanted
> to illustrate. Even somewhat more complex problems can be readable, at
> least to the programmer himself. The acid test is will be when a reader
> wants to reason about the code or when a maintainer picks up the code
> some years later.
>
> I confidently expect that many real sized programs can be made more
> tractable with DCI. I have no proof since I haven't, as yet, written any
> real sized program using DCI. You have your expectations, I have mine. A
> likely outcome is that yes, I am right for some problems. And yes, you
> are right for other problems. DCI is simply a new tool in the
> programmer's toolbox.
>
> On 2011.12.12 19:06, ant.kutsch...@gmail.com wrote:
>
>
>
>
>
>
>
>
>
> > It's a toy example, because Dijkstra's algorithm is one too ;-)
>
> > You seem to be saying the only way to have clear models and readable
> > code is with DCI. I disagree. For my solution I have a domain class
> > called train station.
>
> > To get shortest paths I build a secondary model with "nodes" which
> > provides me with a specific view of a train stations and their
> > neighbours. All the logic is in a service which contains the
> > algorithm.
>
> > I've separated the state and behaviour, and I have readable and
> > reviewable code. And my mental model does not differ from the one
> > which the business analyst provided me with because we discussed it at
> > length and the analyst agrees that to determine shortest paths between
> > train stations you need to think in terms of nodes and edges and have
> > a service which provides shortest path solutions.
>
> > ----- Reply message -----
> > From: "Trygve Reenskaug" <tryg...@ifi.uio.no>
> > To: <dci-ev...@googlegroups.com>
> > Subject: Implemented the Dijkstra algorithm: lessons learned
> > Date: Mon, Dec 12, 2011 15:33
>
> > On 2011.12.12 07:48, ant.kutsch...@gmail.com wrote:
> >> I took the algorithm and stuck it into a toy example for training
> >> purposes. Without DCI. And I had no problems with identity. I had
> >> the ability to add state to nodes too.
>
> >> It reminded me of a "view model", namely a model of a model, adapted
> >> for a specific MVC view, or in this case just a specific algorithm.
>
> >> Sadly it made me again question the real value of DCI.
>
> > I'm not surprised. Toy examples cannot be expected to show the
> > benefits of DCI, two of them being clean mental models and readable
> > code. Even the "/write code first, think later/" approach works if the
> > example is sufficiently simple. I got some help from DCI when I wrote
> > my /Shapes /animation example I first used POJO, the result was
> > chaotic code. Then DCI, which was straight forward. May be a new POJO
> > implementation would be OK; I haven't tried it.
>
> > The benefits of DCI may easier to see if you ask somebody other than
> > the original coder to read the code and explain how it works. (The
> > reader being familiar with DCI, of course.)
>
> --
>
> Trygve Reenskaug mailto: tryg...@ifi.uio.no
Any solution with dual identity for the same entity can cause trouble in some situation. The smarter the solution, the harder it can be to find an identity-related bug. "Canonical" DCI does not have this flaw.-----About lessons learned: the Python implementation works really well, but I'll be looking into a DCI implementation my colleague made in Python that he claims is better. There's still some iffiness that pops up with respect to object identity sometimes, --.
My big issue: state and role scope.
I have an issue with "canonical" DCI stating that roles are pure behavior. In the Dijkstra implementation of Cope he struggles with the same issue. In the Dijkstra context you want to annotate objects with state like "visited", "best previous node" and "best weight" that are totally irrelevant outside of the Dijkstra context. However, in canonical DCI I'd have to add these instance variables to my Data objects just in case they get used by the Dijkstra context, or (and this is what Cope did) plonk that data in the context. I really don't like that, it feels like a workaround. I want to attach state when and where I want to, durn it!
Point is, Python is powerful enough that I can attach state when and where I want, but it's not nearly as elegant as the way roles just disappear when they go out of scope. There's also a namespace issue, because if I start attaching state in the context of a Role/Context it might clash with the name another Role or Context used.
So my boat-rocky question is this: shouldn't we have a notion of "PersistentRole", "StateRole", "RoleState", "DynamicData" or whatever you want to call a bunch of dynamically attached state (with appropriate accessors)? This state might have global scope, context scope or role scope.
YDCI Heresy Y/N?
Anyway, have a look at the code. Apart from the above questions and issues it works and it's pretty darn clean compared to a ClassOO implementation! It calculates any grid, not just Manhattan form, muhaha :-)
The Manhattan grid
complicates the algorithm, but introduces two roles that essentially do
exactly the same: SouthNeighbor and EastNeighbor. A name conflict
surfaces if the two role methods have the same name and are injected
into the same class. In my latest implementation, the selector is:recomputeTentativeDistance,
but the role methods are distinct. The bodies of these two methods may
or may not be the same. In my case, they are different in a version
where all role methods writes a trace.
I want to stress that I
am not belittling other DCI implementations. On the contrary, they are
very valuable for making the DCI paradigm available for programming
here and now. They may be less than ideal, but never forget Voltaire:
"the best is the enemy of the good".
Cheers
--Trygve
@Rune: There is no communication between any objects. Perhaps that is
the point.
Arguments like "I have worked with xxx for years, and have never had problem with dual identities" is not an argument against a better solution. I want the better solution if I can have it.
Jim found a way to give state to the roles in his first Dijstra/Ruby program. It was an experiment, and he has removed this feature in his later implementations.
"Persistent role" is meaningless since a role only exists during an interaction.So my boat-rocky question is this: shouldn't we have a notion of "PersistentRole", "StateRole", "RoleState", "DynamicData" or whatever you want to call a bunch of dynamically attached state (with appropriate accessors)? This state might have global scope, context scope or role scope.
The Manhattan grid complicates the algorithm, but introduces two roles that essentially do exactly the same: SouthNeighbor and EastNeighbor. A name conflict surfaces if the two role methods have the same name and are injected into the same class. In my latest implementation, the selector is:recomputeTentativeDistance, but the role methods are distinct. The bodies of these two methods may or may not be the same. In my case, they are different in a version where all role methods writes a trace.
I want to stress that I am not belittling other DCI implementations. On the contrary, they are very valuable for making the DCI paradigm available for programming here and now. They may be less than ideal, but never forget Voltaire: "the best is the enemy of the good".
Trygve,Your answer did give me what I was looking for, the fact that you consider "role state" to be non-DCI. I can understand the reasons for it and it definitely makes things simpler, but I keep wanting to push temporary state into an object.
Jim found a way to give state to the roles in his first Dijstra/Ruby program. It was an experiment, and he has removed this feature in his later implementations.That's interesting. Jim, can you comment on why you came to this conclusion?
Trygve,
Your answer did give me what I was looking for, the fact that you consider "role state" to be non-DCI. I can understand the reasons for it and it definitely makes things simpler, but I keep wanting to push temporary state into an object.
---
"Persistent role" is meaningless since a role only exists during an interaction.
So my boat-rocky question is this: shouldn't we have a notion of "PersistentRole", "StateRole", "RoleState", "DynamicData" or whatever you want to call a bunch of dynamically attached state (with appropriate accessors)? This state might have global scope, context scope or role scope.
I meant not so much putting state in a role but something that is attachable like a role, but is purely for state. In the end it's still better encapsulation if state is as close to the behavior as it can get, isn't it? I'd like to have Context-scope state that I can attach to an object, and that will be removed as the program moves out of scope of that Context.
Simple. Put the interaction-related state in the context.
It's simple, but is it the only option? Like Cope describes in his Dijkstra example, there is a notion of annotating nodes on a map. My mental model is then that I can attach temporary state to a node (a RolePlayer), and not the (to me) unnatural solution of keeping everything I want to say about an object somewhere separate from that object.
I want to be able to say node.visited instead of context.listofvisitednodes.contains(node), and node.weight instead of context.weightDictionary[node]. I might not be seeing something, but I think it's a lot more understandable and cleaner if I can attach temporary state than to keep a whole bunch of lists and dictionaries in the context.
It's simple, but is it the only option? Like Cope describes in his Dijkstra example, there is a notion of annotating nodes on a map. My mental model is then that I can attach temporary state to a node (a RolePlayer), and not the (to me) unnatural solution of keeping everything I want to say about an object somewhere separate from that object."I want to be able to say node.visited instead of context.listofvisitednodes.contains(node)"
+1
James, you always tell us to think about objects and not classes.
In the context of calculating lowest cost paths through a map, we need
to track whether a node has been visited. Surely it is more object
oriented to put that attribute on the object to which it is relevant,
rather than to artificially keep that attribute outside of the object
in the context?
The proof comes when you implement Dijkstra's algorithm using
wrappers, because one naturally places that attribute in the node.
You did it, Serge did it, and I did it.
I too want a better solution if I can have it. But DCI doesn't cut
the mustard, at this time.
My requirements in language selection are:
1) The language of choice needs to be mature and supported by rich
libraries and products, governed by well thought out specifications
ratified by the industry (no a single company like Microsoft, but a
set of companies with differing interests),
2) The language of choice needs to be deployable in large enterprises
governed my silly politics,
3) The language of choice needs to be one which will help me, as a
freelancer, make a good living, long term,
4) The language of choice needs to support DCI,
5) The language of choice needs to be supported by powerful IDEs which
allow programmers productivity to reach maximum levels.
So languages like Ruby are out of the question, when compared to
platforms like Java, .NET and SAP. Note I used the word platform.
SAP (ABAP) probably won't ever support DCI - last time I looked it
wasn't even OO.
Java the language is too static, and its requirement to provide a
class name when declaring variables causes naming problems, as we
discovered in the summer.
Groovy on the other hand is a language that runs on the Java platform
(since it is compiled into Java bytecode and is hence fully compatible
with all Java APIs/libraries/products/specifications).
So let's look at some Groovy code in a context:
class FlyingCarContext {
/* a role in this context */
class PlaneRole {
def fly(){
println "I'm flying!"
}
}
/* a role player */
def myFlyingCar
/* constructor */
FlyingCarContext(aFlyingCar){
myFlyingCar = aFlyingCar
}
def execute(){
myFlyingCar.fly()
}
}
To execute that context, I could do something like this (in Java or
Groovy):
Car aCar = new Car();
new FlyingCarContext(car).execute();
Using Groovy has entirely removed the naming problems which Java has
and it is very DCI compliant. And like I said a few days ago, you can
build a framework which handles context stacking as well as role
removal at the end of the context. It comes in high on the list of
DCI compatible languages.
So the million dollar question is: "Is this a better solution (than
one which uses wrapers)?"
I say no, it is not better, although perhaps no worse either. But it
still fails to meet my requirements, because programmer productivity
has been reduced due to the dynamics which have been introduced.
No current IDE is able to let the programmer see what methods are
available on the variable "myFlyingCar" in the execute method:
myFlyingCar.fly()
Rune claims that while no IDE can do this today, it is theoretically
possible. I am not convinced. I challenge those who are, to show me
I am wrong in my doubts.
The thing is, when I implement Dijkstra's algorithm using wrappers or
a view model, in a way that I have done for years, which incidentally
does not suffer from object-schizophrenic problems, I find it better
than an implementation which uses DCI. Productivity and the compilers
ability to check for type problems at compile time are more important
to me than mental models, but you'll have to wait for my next post to
find out why.
PS. What has this got to do with DCI evolution? Everything, because
in order for DCI to evolve and be absorbed by the masses, language
designers who support DCI will also have to fulfil the other
requirements.
Rune claims that while no IDE can do this today, it is theoretically
possible. I am not convinced. I challenge those who are, to show me
I am wrong in my doubts.
> James, you always tell us to think about objects and not classes.
No, not always. There are no objects in the source code. The source code is the programmer's domain.
Yet the running program — for which the programmer is also responsible — comprises objects.
The real core of DCI is to take something that is a gestalt at run time (an object) and to break it down into its compile-time gestalten (data and behavior). Yes: the goal is to think about objects. That is a difference that class-oriented programmers notice. The fact that we need to think anew about objects doesn't dispel the need to consider the source code. Part of that is now in classes, and part of that is in roles. Neither of them exist at run time.
> The thing is, when I implement Dijkstra's algorithm using wrappers or
> a view model, in a way that I have done for years, which incidentally
> does not suffer from object-schizophrenic problems, I find it better
> than an implementation which uses DCI.
I think that is because you are limited by your language, and how it affects the way you think about design.
> The proof comes when you implement Dijkstra's algorithm using
> wrappers, because one naturally places that attribute in the node.
> You did it,
Show me where I ever said that example was DCI.
visited' and 'tentativeDistance'
properties. I found that 'visited' is a derived node
property, the set of all 'unvisited' nodes being more
basic. But that means that I zoom back from individual nodes to the
graph as a whole. I see the graph as a whole and the (tentative) paths
that have been calculated so far. The set of 'visited'
nodes is the set of nodes participating in a path. A 'visited'
node property is true if the node is a member of the 'visited'
set, That's why I call it a derived property. The inverse definition is
also possible; the set of 'visited' nodes can be computed
by selecting all nodes with the 'visited' property == true.
I find this alternative less illuminating because the path is more
basic than its individual nodes. Map, the
geometry as a whole with its nodes and edges. Unvisited,
the set of unvisited geometry nodes. DistanceDict, a
dictionary that binds a node to its tentative distance from the start.
The first has a global scope, the other two are scoped to the Context.Can you please send me a screenshot of autocomplete offering me the
"fly" method on the line marked below, using the following code:
class FlyingCarContext {
/* a role in this context */
class PlaneRole {
def fly(){
println "I'm flying!"
}
}
/* a role player */
def myFlyingCar
/* constructor */
FlyingCarContext(aFlyingCar){
myFlyingCar = aFlyingCar
}
def execute(){
myFlyingCar. //AUTOCOMPLETE HERE PLEASE
}
}
And I think you are wrong ;-)
I like to think in terms of nodes and edges while working inside
Dijkstra's algorithm. That is something DCI lets me do just as much
as wrappers, mapped transfer objects or a view model. Each have
advantages and disadvantages. Of all the options, one has to choose
the "best", normally the one which one prefers the most. I've weighed
up the pros and cons of each, and I am happy with my current choice.
Actually, I find that having a class to specify what a node is, helps
me think about nodes better than having a train station playing the
dynamic role. If I have a train station, I don't know what roles it
really has on any specific line of the code, without studying the code
first. And, by having a class to represent the node, I have no issues
adding state to the object (i.e. in the place which my mental model
thinks it belongs) in order to help me track whether I have visited
the node or not.
I didn't say you said it was. I said that you added state to a node,
and I implied you did it because it felt natural to do so. The point
being, three people (well 4 if we include Stephan) felt it was worth
putting state in the node. To me, that signifies it is the natural
place for that state.
Mvh
Rune
> I like to think in terms of nodes and edges while working inside
> Dijkstra's algorithm
I can easily see how this might help implement an abstract algorithm.
That you only have to think in the terms of the algorithm. Is that why
you prefer to just use nodes and edges?
> To me, that signifies it is the natural
> place for that state.
Not everything that feels very real and natural is correct though ask
Galileo or any nuclear physicist or any procedural programmer writing
his first program in Java
Hi Trygve,
I don't think that is necessarily true. What I have done in my
project was to create a component (in POJO) for calculating shortest
paths. That component defines an interface, composed of operations
which can be called (e.g. calculateFastestRoute), and an object model
(data) which is passed to the operation. To call an operation, I map
my domain object (e.g. train station) into the object model, namely
nodes, geometry, etc. I pass that object model to the operation and I
get a result back, in terms of a list of nodes showing the shortest
path. In my specific implementation I even have additional
information like the waiting time at each node. I can then map the
results back.
There is additional work in this mapping, but by sneakily making nodes
have a reference to train stations, the mapping is quite simple in the
end. Mapping from train station to node is also as simple as passing
the train station to the node constructor.
The result is that I do not have chopped noodles of state and
behaviour which is distributed all over the place. Rather, I have a
component of software which has a very clear behaviour defined by its
operations (a public interface into the component). That component
has a clear responsibility. All the relevant concerns have been
separated. Reviewing the algorithm is very easy, because the reviewer
only needs to know about nodes, which are clearly defined in a class,
so that the reader knows exactly which data the node has. Nodes
themselves contain no behaviour, true to a services paradigm, rather
than an OO paradigm.
Cheers,
Ant
The example was designed in large part as a test for the current level of comprehension and mitigation of the deeper issues underlying computational models. In particular, the code was designed to explore identity and equality, algorithmic thinking versus data thinking, language restrictions versus computational models, and a number of other axes of design. I focused on the areas where the rhetoric of this group (and of object-composition) have given me concern.
We have a long way to go.
Rune, you have just contradicted yourself, and confirmed my original
statement. Dynamic languages cannot support autocomplete as well as
languages which require the programmer to declare the class of each
variable. That statement is true and irrefutable.
Try implementing Dijkstra's algorithm in Javascript in a manner
similar to the way James did it in Ruby, and you will see that
autocomplete is rendered almost completely useless.
In my opinion (and I am not alone here), productivity is reduced. And
that is one reason why DCI is not a better solution than wrappers, in
my opinion. In terms of evolution, DCI needs to address this issue,
in my opinion.
I prefer to think in terms of nodes and edges, because train stations
and train trips are not as accurate. And... in terms of using the
algorithm elsewhere (taxi trips, tourists walking around town, etc),
doing so makes the algorithm useable in other software.
+1
On Dec 15, 8:31 pm, rune funch <funchsolt...@gmail.com> wrote:Rune, you have just contradicted yourself, and confirmed my original
> No cuz webstorm is a JS IDE. But don't be silly when it can be done
> for a language as dynamic as JS then it can be done for any dynamic
> language. You'll never get code completion for everything in a dynamic
> language but you don't have that in java either. Whether or not you
> have it depends on your tooling and the way you code. Just try to add
> an object to an arraylist an fetch it again the line below. Now use
> autocomplete for any interesting method. You can't you first need to
> tell the compiler which type the object has runtime (aka cast) because
> that information got thrown away. Similar with dynamic languages you
> can come in a situation where the compiler needs help. In WebStorm
> attaching methods runtime is _not_ one of them though
statement. Dynamic languages cannot support autocomplete as well as
languages which require the programmer to declare the class of each
variable. That statement is true and irrefutable.
Try implementing Dijkstra's algorithm in Javascript in a manner
similar to the way James did it in Ruby, and you will see that
autocomplete is rendered almost completely useless.
In my opinion (and I am not alone here), productivity is reduced.
And
that is one reason why DCI is not a better solution than wrappers,
in
my opinion. In terms of evolution, DCI needs to address this issue,
in my opinion.
Trygve Reenskaug mailto: try...@ifi.uio.no
Morgedalsvn. 5A http://folk.uio.no/trygver/
N-0378 Oslo Tel: (+47) 22 49 57 27
Norway
Re: the importance of autocomplete. What percentage of the total program development time do you estimate is spent actually typing at the keyboard?
On Dec 15, 8:48 pm, rune funch <funchsolt...@gmail.com> wrote:
Your almost suggesting we should avoid abstraction because it removes the ability to think of the system as a whole.
Your almost suggesting we should avoid abstraction because it removes the ability to think of the system as a whole.