3

The question is in the title. I want to have my thinking verified by experienced people. You can add more or disregard my opinion, but give me a reason.

Here is an example requirement: Suppose you are required to implement a fighting game. Initially, the game only includes fighters, who can attack each other. Each fighter can punch, kick or block incoming attacks. Fighters can have various fighting styles: Karate, Judo, Kung Fu... That's it for the simple universe of the game. In an OO like Java, it can be implemented similar to this way:

abstract class Fighter {
    int hp, attack;

    void punch(Fighter otherFighter);
    void kick(Fighter otherFighter);
    void block(Figther otherFighter); 
};

class KarateFighter extends Fighter { //...implementation...};
class JudoFighter extends Fighter { //...implementation... };
class KungFuFighter extends Fighter { //...implementation ... };

This is fine if the game stays like this forever. But, somehow the game designers decide to change the theme of the game: instead of a simple fighting game, the game evolves to become a RPG, in which characters can not only fight but perform other activities, i.e. the character can be a priest, an accountant, a scientist etc... At this point, to make it more generic, we have to change the structure of our original design: Fighter is not used to refer to a person anymore; it refers to a profession. The specialized classes of Fighter (KaraterFighter, JudoFighter, KungFuFighter) . Now we have to create a generic class named Person. However, to adapt this change, I have to change the method signatures of the original operations:

class Person {
    int hp, attack;
    List<Profession> skillSet;
};

abstract class Profession {};

class Fighter extends Profession {         
    void punch(Person otherFighter);
    void kick(Person otherFighter);
    void block(Person otherFighter); 
};

class KarateFighter extends Fighter { //...implementation...};
class JudoFighter extends Fighter { //...implementation... };
class KungFuFighter extends Fighter { //...implementation ... };

class Accountant extends Profession {
     void calculateTax(Person p) { //...implementation...};
     void calculateTax(Company c) { //...implementation...};
};
//... more professions...

Here are the problems:

  1. To adapt to the method changes, I have to fix the places where the changed methods are called (refactoring).

  2. Every time a new requirement is introduced, the current structural design has to be broken to adapt the changes. This leads to the first problem.

  3. Rigid structure makes it hard for code reuse. A function can only accept the predefined types, but it cannot accept future unknown types. A written function is bound to its current universe and has no way to accommodate to the new types, without modifications or rewrite from scratch. I see Java has a lot of deprecated methods.

OO is an extreme case because it has inheritance to add up the complexity, but in general for statically typed language, types are very strict.

In contrast, a dynamic language can handle the above case as follow:

;;fighter1 punch fighter2
(defun perform-punch (fighter1 fighter2) ...implementation... )

;;fighter1 kick fighter2
(defun perform-kick (fighter1 fighter2) ...implementation... )

;;fighter1 blocks attacks from fighter2
(defun perform-block (fighter1 fighter2) ...implementation... )

fighter1 and fighter2 can be anything as long as it has the required data for calculation; or methods (duck typing). You don't have to change from the type Fighter to Person. In the case of Lisp, because Lisp only has a single data structure: list, it's even easier to adapt to changes. However, other dynamic languages can have similar behaviors as well.

I work primarily with static languages (mainly C and Java, but working with Java was a long time ago). I started learning Lisp and some other dynamic languages this year. I can see how it helps improving my productivity.

Amumu
  • 824
  • 2
  • 8
  • 14
  • 9
    I feel like there's a good question in here somewhere, but I can't tell exactly what it is. – KChaloux Dec 11 '12 at 15:01
  • 1
    Well, I put it at the very first sentence :) – Amumu Dec 11 '12 at 15:13
  • Initially, I want to keep the question short. I just want to ask about the advantages of having no type in dynamic languages for dynamic requirements. But then, it will hard to discuss without a concrete example for everyone as a baseline for the discussion. – Amumu Dec 11 '12 at 15:32
  • This doesn't seem like a question so much as a backhanded attack on OOP rigidity. If you reworked your "OOP" solution using behavioral interfaces it would paint a different picture. – Ed Hastings Dec 11 '12 at 18:55
  • @EdHastings You mean, instead of using abstract class, we use `implement interface XXX {}`? It can work, but still limited. You can have reusable methods, but you still have types. How do you tackle with conceptual changes from requirements, such as from Figther as a person to Fighter as a profession? How can the existing methods can stay the same with heavy structural change during development? – Amumu Dec 11 '12 at 19:13
  • @EdHastings I mean, during development time, is there a way to not rewriting/refactoring code over and over again when types change? i.e. change the name of class/interface Fighter to FightingStyle, and you will have to refactor multiple files that use it. Further, is there a way to make function adaptable to the future types (new concepts) added to existing system? I would appreciate if you demonstrate it in a popular OO language. – Amumu Dec 11 '12 at 19:16
  • @EdHastings I don't mean to attack anything. I understand the important of static language: it is used for proving correctness of the program before it's running. Aggressive type system is usually applied for critical system. However, there are domains where requirements keep changing. Such type system can hinder productivity. Well, this is all my thinking anyway, so I need unbiased opinions to verify. – Amumu Dec 11 '12 at 19:19
  • The "soft" in software pertains to its changeability. If requirements change sufficiently, a software change will be necessary. That is true of both statically and dynamically typed languages. They vary in degree of tolerance to change and ramifications of making changes, but change is a given for any living software. Even in dynamically typed languages if you make a major change to your requirements, while the specifics differ you still have to manage that change across your app(s). – Ed Hastings Dec 11 '12 at 21:22
  • As to behavioral interfaces, I mean you could look at your proposal from a different perspective and say, do I care if a person is a fighter, or do I just care if they can do certain things? Do I need to know if this person is a KungFuKarateTaeKwonDoJudoFighter, or do I just care what they do when I call Attack(target), or Block(attacker) or Damaged(damage)? Do I care if some kind of person instance directly resolves these behaviors, or do I just need an instance of something that complies with IAttacker, IBlocker, IDamageable, IMoveable, whatever; such that other logic can handle resolution? – Ed Hastings Dec 11 '12 at 21:30
  • You could also think along the lines of a Fighter punching the World instead of another Fighter. The World can calculate if there's another Fighter standing in the way of the punch. – James Dec 12 '12 at 16:16
  • 8
    You seem to be under the impression that refactoring Dynamically typed languages is easier than Statically typed ones, because you won't have the compiler throwing up errors at you if a type needs to change. In my experience, it's the opposite. A dynamic language will begin interpreting sooner after the refactor, but there were be a lot of logical errors that will be very hard to find without significant testing, that could otherwise have been picked up by the compiler. As for avoiding rewriting large amounts of code, sometimes you can't. Implementation has to change with the logic. – KChaloux Dec 14 '12 at 14:05
  • 3
    Refactoring is not a "problem". It's part of the software development process. – Tulains Córdova Dec 17 '12 at 15:54
  • @James A Fighter punching the World ? Is that a Chuck Norris joke ? – Tulains Córdova Dec 17 '12 at 18:45
  • @KChaloux is correct. You're assuming that a dynamic language is a silver bullet and that all code written in a dynamic language is future-proofed and doesn't need to be refactored. This couldn't be further from the truth. – Jim G. Dec 31 '12 at 16:40

5 Answers5

2

I am not entirely sure what your question is, because it the question includes a number of vague terms: "dynamic language", "this" and "dynamic requirement".

"Dynamic language" can refer to any programming language that does something that other languages may do at compile time. This can include aspects of data values (null or not null), aspects of the type system (e.g. creating new types at runtime), aspects of method and function dispatch and many other aspects. Your question seems to be primarily concerned with dynamic method dispatch (although you talk about static typing later on). My attitude to this would be that different languages provide one with different tools of how to deal with requirements and also with changing requirements. In that sense, the answer to your question is probably "yes". Where languages like Java or Scala provide interfaces and traits to make it easier to adapt the design of your programs to changing requirements, a language like Clojure provides dynamic dispatch, Groovy provides metaprogramming and still other languages provide no or very little tools at all. I think which of these tools are better suited for the task depends on the details of the task, the details of your definition of "better" (development time and effort, availability of programmer, performance of code, likelihood of certain classes of bugs during runtime etc.) and on personal preference and cannot be answered in general.

jpp1
  • 209
  • 1
  • 6
  • 2
    "Dynamic language" is more of a marketing term. Now statically typed languages like Scala and C# has a _dynamic_ keyword, they are now dynamic languages! – Vorg van Geir Apr 03 '13 at 14:26
  • @VorgvanGeir I'd say C# is a static language with some support for dynamic language features. – Andy Jan 28 '14 at 18:00
1

I think the answer boils down to: yes a "dynamic" language helps your coding productivity as you can make sweeping changes quickly and easily. However, it hurts your release productivity as you have create a mess of code that has no consistent structure to it and lots of only-runtime-detectable errors that will need fixing later, rather than sooner like you'd get in a "static" language.

There's simply no substitute for doing things correctly, and that means thought, design, and careful consideration of the impact of your changes.

gbjbaanb
  • 48,354
  • 6
  • 102
  • 172
1

A dynamic language may complain less when you refactor, but it could also be hiding type errors from you that you'd catch at compile time on a statically typed language. In general, you'll have to refactor your code when your requirements change regardless of whether you're on a dynamic or statically typed language. There's no silver bullet there.

C and Java aren't good benchmarks for static languages, because they have very limited type systems and features. You can't get anything done in Java without extreme verbosity. Want to pass a tuple? Well, you can't. Records? You can't. Want an anonymous function? You'll have to write 4 lines of boilerplate for a payload that could be as short as 2 + 2. God help you if you want to curry or compose functions.

You have to resort to a bunch of OO hacks to work around those missing features, and even then you can still shoot yourself in the foot with inheritance and null because so many people don't understand how dangerous they are. So if that's your only point of reference, just about any dynamic language will allow you to write less code, but not necessarily by virtue of their dynamic typing. Languages like Standard ML or F# also provide those features without burdening you with type annotations because the compiler can infer the types.

A good statically-typed language is more expressive than a dynamically typed language with equivalent features. Dynamic typing deprives you of the ability to express constraints on your data using the type system. That's because dynamic typing is a special case of static typing - it's more correct to think of these languages as being unityped. If that seems strange to you, imagine coding in Java using only Object variables and casts. The problem is that mainstream statically typed languages are crippled. But if you're coding in Lisp, I don't think you're too concerned with what's mainstream.

Doval
  • 15,347
  • 3
  • 43
  • 58
0

A dynamic language can be great when faced with dynamic requirements. Personally, I prefer Python to C when I'm faced with a lot of changes. There is far less ceremony compared with C++, C#, Java. But ...

Regardless of the language, the architecture of the system / project has a massive impact upon how well we can adapt to changes, such as those that you mention. For me the bigger question is "How quickly can we refactor to accommodate the changes." For your example, base classes would need changes and I would prefer to have multiple inheritance.

In our projects, the external analysis and design documents are massively revisited when big changes come about. We prefer to issue revised documents and from that make a big list of change requests. And yes, the test suites need a lot of extra work too.

CyberFonic
  • 161
  • 3
0

I'm not sure, I'm getting your point here, but different kinds of languages don't really solve the underlying problem which is, basically, that a once written code base needs to be expanded. While this is something that refactoring, obviously, can solve, maybe the general concept/design of the code was unfortunate in the first place.

For instance, how about a Fighter (or a Person if you later change that) does "just something". Using, for example Java generics, you can even limit what they can do.

class Fighter {
    void doSomething(Command<? extends FighterCommand> cmd) {
        cmd.execute();
    }
}

One can now easily, at any point during development, expand the abilities of a fighter by just adding another command.

class KickCommand extends FighterCommand {
    @Override
    public void execute() {
        kickSomeone();
    }
}

Using the Command pattern here would absolve you from changing interfaces all over the place just because the fighter needs to punch someone now, too.

And getting closer to what you were asking, extending the Fighter concept to a Person concept should be possible by making the Fighter extend (the now new) Person. If a Person can doSomething(), too, it's only one @Override in the Fighter away (or deletion of the Fighter method :)).

jhr
  • 473
  • 2
  • 7