12

Sometimes I find myself in situations where just by happenstance two things can be treated the same way. But they aren't truly the same, it's just coincedence.

Let me give an example, imagine we are in something like JavaScript so that types don't get in the way of my example. Suppose we are writing some event handler, you receive an event, find an instance of some class corresponding to that event and then you're going to call a method of that instance.

Maybe you write this as say a switch statement on the code of the event, or something, and specify which method to execute. Let's suppose there's 10 events to handle, and 6 of them have mutually distinct methods to call, and the remaining 4 all have a method with the same name to call.

But the 4 corresponding classes aren't related in anyway, certainly there's nothing that ensures these methods have the same name.

In this case, should you put these 4 cases in a single switch case?

For me I think, no, as then the code 'suggests' something which isn't so, namely that these 4 cases are related.

6
  • Maybe try a UML of your code to see if it's really accidental
    – Emobe
    Jun 13, 2020 at 11:47
  • 1
    If they're all events, why aren't you just having a single dispatch or run function on each of them and doing away with the switch statement altogether?
    – corsiKa
    Jun 13, 2020 at 19:16
  • @corsiKa Do you mean to have a dispatch or run method on the event? To me that doesn't always make sense, the thing that creates the event shouldn't need to know how to handle the event, if you mean to add the run method on later, then still the events have to be inspected and switched on to determined what run method to add, which just moves the switch somewhere else. But in any case this was just supposed to be an example of the sort of situation I meant, nothing precise. Jun 13, 2020 at 22:58
  • No there's no switch necessary. Consider Java's Runnable class.
    – corsiKa
    Jun 14, 2020 at 1:12
  • 2
    Ah, I see what you mean - I interpretted you saying "you receive an event" to mean "you receive an event object" not "you receive an event string" - that being said, in Java I do use annotations and reflection to make direct calls in such a way, and I am sure you could do so in Javascript, although you would clearly need proper security on such things. For sure, a mapping object would be superior to a switch, and more maintainable, but I suppose such things do digress from the original question (slightly)
    – corsiKa
    Jun 14, 2020 at 14:57

7 Answers 7

20

Your example is a bit too complicated to make your point. You suggest dynamic dispatch, which actually works pretty much like that... if types get in the way and compile-time is a thing, name-mangling helps resolve identification and entity uniqueness.

What you describe as accidental polymorphism is only possible at the symbolic level. In the conceptual level, you can never have "accidental" polymorphism, because it is about the philosophy. Symbolically, John and John and John is not a sentence that makes much sense. But conceptually, 3 Johns can exist in one room at the same time, because John is a symbol for a concept. To make matters worse, john is occasionally also a colloquialism. Furthermore, run, as a symbol, does not describe anything in the absence of context. Conceptually, you can run() a method, or a Human instance can john.run().

Accidental polymorphism at the symbolic level is common, but accidental polymorphism at the conceptual level is, as Christophe aptly puts it, not accidental. A method and a human are two different types and run is never around without taking the entity into account.

To give an answer to the original question, I need to reformulate it, so that it matches your example. You ask whether it is a good idea to exploit the lack of a type system for static analysis to "retrofit" your code into fewer categories. It is not...

For example fast elf means what it means in English, but in German, it means almost eleven. And then, still, is it eleven* kilograms or eleven o' clock? Context is also important. As you see, symbols coincide, but concepts definitely don't. Code is about using symbols to express concepts. Treating coincident symbols as coincident concepts is not a good idea and is, more often than not, going to confuse anyone else reading your code.

*(Then again, elven and eleven are just one letter away....)

2
  • 1
    Good, I like this answer it contains just the right amount of analogy as to convince me. Your rephrasing of the question is the/a correct one. Jun 12, 2020 at 18:56
  • As a side note, in some paradigms, what you refer to as symbolic and conceptual levels of polymorphism are inherently bound to one another (think duck typing, in which methods or selectors, if you will, become effectively context-free)
    – crizzis
    Jun 13, 2020 at 13:35
11

The philosophical question of the day: is it really accidental ?

You are telling us as example, that a switch processes events, and that you realise how some of these processing methods have the same name and from there, go for polymorphism.

Some people would wonder why all your event handlers don’t have the same name for their methods, and why you don’t use some adapter when the actions are a little different.

A similar idea behind is the command pattern: where in fact all changes made on some receivers are all encapsulated behind a command.

So may be it’s not polymorphism by accident, but polymorphism discovered by accident ? ;-)

To dig further

I can recommend you Herbert Simon’s book The science of the artificial. Simon’s an amazing guy who started as nobel prize of economy to end his career as a pioneer in AI. In this book he analysed systems, and he found out that biological systems, human organisations, political systems, and IT systems share a lot more in common that one could imagine at first sight. Who knows: maybe once you’ve read it, you’ll reconsider for ever the word "accidental" in relation with similarity or polymorphism?

4
  • It's a good point, and certainly I agree some times it isn't accidental and has given me pause and resulted in me refactoring something. That being said, I think this command pattern for example could seem to me in some circumstances a little artificial. Suppose I just have some singleton instances of certain classes, these classes have many methods, and I just want to use one. Does it really make sense to wrap these classes in some other classes just to achieve polymorphism in this place? I'm certainly open to the idea, but I'd like to hear an argument for it. Thanks for the links. Jun 12, 2020 at 17:38
  • 1
    @KircheisVent Far from me the idea of promoting polymorphism at all costs! On contrary, I’m a strong believer of composition over inheritance. Polymorphism should be used only if there is a strong reason and a real is-a relationship (e.g. drag_event_processor is-an event_processor). But when structurally polymorphism emerges, it’s a sign that shall not be ignored. Maybe it’s coincidence and forget about it, but often it’s a gut feeling that make you realize a hidden connection that will enlighten your vision on your design.
    – Christophe
    Jun 12, 2020 at 18:31
  • 1
    @KircheisVent Additional hint: speaking of artificial, and pardon me if I’m provocative, but if it appears that you have lots of singletons in your design, there might be something fishy going on, because singletons should really be the exception and not the rule. Not knowing anything about your design, I cannot advise you on that nor strongly recommend not to use them (your design, your choice), but maybe give a critical thought to it.
    – Christophe
    Jun 12, 2020 at 18:37
  • 1
    No provocation. It's a good point. I take no real criticism from it because these situations usually arise in established JavaScript projects I am thrust into. I do find I don't get myself into these scenarios in my other work. Sometimes the design of these JavaScript projects is so alien to me as to leave me feeling quite unsure of myself. Jun 12, 2020 at 18:54
6

Ever heard of duck typing?

Forget what these things “are”. Focus on what they can do for you.

If an algorithm needs to call a, b, & c and x, y, & z each have appropriate a, b, & c’s then feel free to use any of them.

The typing system was never meant to force you to build elaborate is-a taxonomies to make this work. It sometimes gets taught that way but it’s not required.

I very much let use, not hierarchy, drive my designs. So call it accidental if you like. I think of it as sensible.

2

They should be separate. A unit of code should have one and only one reason to change (the single responsibility principle). But if your switch statement is being against two unrelated objects A and B, then in the future the switch statement might need to be changed to handle a new use case for one of those objects but not the other. A change to make the switch statement continue to work for object A could end up breaking the switch statement for object B.

Programmers like to eliminate or avoid "duplicate" code because it is easy to see, and easy to eliminate. But if that code is handling different use cases it isn't really duplicate. By coupling multiple independent parts of the code base to a single function you end up making the whole code base more fragile.

1
  • "Sometimes I find myself in situations where just by happenstance two things can be treated the same way. But they aren't truly the same, it's just coincedence." Also, by coincidence, one ends up needing to change and the thing using it will break quite unexpectedly. It is strange that it is not the accepted/most obvious answer.
    – user275564
    Jun 13, 2020 at 14:22
1

imagine we are in something like JavaScript so that types don't get in the way

*shudder*

OK my javascript reservations aside there is a movement to allow "anything that fits a interface to be that interface" even if the type doesn't inherit that interface in typed languages.

You'll still get a compile error if you try something that doesn't match the interface, but you don't have the need to explicitly declare interfaces when making the class.

The reasoning is that you can avoid tight-ish coupling between classes via simple universally used interfaces, such as IEnumerable and refactoring classes to add more and more interfaces where you want to use limited subsections of their methods.

However, to my mind this is just syntactic sugar. saving you some typing by dynamically adding the inheritance. Its an argument about whether less typing >= || === less clear code.

In "something like javascript" you are obviously risking runtime errors.. well all the time.. you don't really have a way of enforcing the existence of methods on objects at all, so go crazy and have fun.

In a typed language where you are maybe casting to a dynamic type, or using reflection, you are simply shooting yourself in the foot by not adding and using an interface. Why risk the error when for the want of a bit of typing you can avoid it?

3
  • I like types, I'm a Scala developer mostly, but I also work with JavaScript and Typescript. Typescript is of the movement you allude to and it's a much better system for the reasons you give. And the type system in Scala is powerful enough as to allow you to safely do things. Unfortunately I sometimes have to work in pure JS projects and 'work with what I have', as it were. Jun 12, 2020 at 17:26
  • 1
    I would go with a "pretend its typed" approach then. Whats caught me out before is when you have to interface js libs with typed code and the js guys have just gone crazy. Its impossible to work out a type system that fits and you end up having the dynamic class infiltrate your lovely well formed code
    – Ewan
    Jun 12, 2020 at 17:37
  • @KircheisVent If you like how TS improves on JS, you might also like how Elm goes even further. I found that an interesting read recently, but - fill disclosure - I've not used Elm yet.
    – J.G.
    Jun 13, 2020 at 12:54
0

I think it depends on the example. I love DRY code, but @ScottJacobsen's answer has already made Dave Thomas's point that it's knowledge, not the code itself, that's supposed to go unduplicated.

imagine we are in something like JavaScript so that types don't get in the way of my example

I take this to mean, "instead of taking a type-A object in my example and doing a switch over A's subclasses, let's assume several types can be passed to the function", and I agree that's a helpful way to simplify how we think about the issue. But languages that let us do that still vary in how it plays out.

Suppose we want to (add two numbers or concatenate two strings). We can handle cases separately then refactor to taste, maybe getting fewer branches. In JS, a+b would work for all cases, concatenating if one variable's a string and the other's a number. Python could use the same syntax except when we mix, and R would need an if/else even if we don't aim to support the mixed case. If we wanted code that failed on a mixed case, that would actually take more work in JS. So, what works for us is an accident concerning how our aims match against a language's design decisions.

In a more complicated example, the objects might be instances of custom classes. Since our code might become wrong as a class evolves, we've used our knowledge of what works to write the code, and there's no risk of it duplicating knowledge even in the case with a maximal number of branches. By contrast, accidental polymorphism obscures knowledge we're lucky worked out the way it did, so it's going further than DRY advises.

0

Yes, as long you are coding with this style consistently and you are not relying on what they are. But in reality no, let's see why.

Let me make a short example, you get a Updatable interface

public interface Updatable
{
    public void Update();
}

Now, there are many things that could get be updated, a sound system, a physics system, another algorithm. So there is nothing wrong to have the same interface even though those are very different. However things starts to change if you have to make assumptions internally, in example if you have also real life systems, like controlling real pipes in a chemical plant, you may want to avoid to open close valve continuosly when you are around equilibrium points, you will just wear valves more than needed. In that case maybe you want a Update every 2/3 minutes and not every half second. Functionally the interface is the same, but you have to make distinction because of underlying implementation, regular updates (which is an anti-pattern anyway) and real-valve updates. At this point you'd better live with different interfaces

public interface TickingSystem
{
    public void Update();
}

public interface PipeSystem
{
    public void Update();
}

Resist the temptation to do this:

public interface TickingSystem: Updatable
{
}

public interface PipeSystem:Updatable
{
}

Which is conceptually wrong. Having the same methods, should not be an excuse to have the same interface, as long as it could be potentially used in different ways. A interface is a contract, so "this thing should be used in a certain way".

Eventually you may want to adapt a compatible interface to another compatible interface only for very few specific cases. In that case you take something that should be used like a Pipe, but is so technologically advanced that you get benefits using it as a TickingSystem, but you make that very clear in code, you are adapting something to be used as something else, and you are conscious that you should not do that.

public class FastPipeSystem: TickingSystem
{
     public PipeSystem pipeSystem; // makes clear your are just using a interface

     // accept just the specific types of pipes that can have very fast switching
     public FastPipeSystem(MicroPipe yourFastPipe)
     {
          pipeSystem = yourFastPipe;
     }

     public FastPipeSystem(LazyValvePipeEvenIfFastInput yourFastPipe)
     {
          pipeSystem = yourFastPipe;
     }

     public override Update() //a ticker
     {
          pipeSystem.Update(); // that tick a pipe .. (that can be ticked!)
     }
}

If you make a concrete example on your problem experts on stackoverflow will be happy to see if in your code you have one case or the other, but otherwise this is the most we can say.

Sometimes some interfaces are complex enough to allow to use different behaviours depending on some parameters

public interface ConditionallyUpdatableSystem
{
    public int MillisecondsToWait();

    public void Update();
}

In example such interface allows to select different behaviors depending on result of MillisecondsToWait (in example 0 => continuos ticking, 120000 for pipes etc.) But it is generally bad Idea, because it is really creating a fork where you can avoid the conditional jump just by using 2 specialized different types. Usually complex interfaces are for complex behaviors (and then someone lazily decide to use them for simpler behaviors). In example all interfaces of standard data containers are pretty rich, but someone is free to make simpler container and ignoring the richness of the interface, but at that point is ok, becuase the rich interface was the only viable alternative to resolve a problem. But making more complex interfaces for simple problems instead is generally a bad idea.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.