Polymorphism in procedural language

Asked

Viewed 1,075 times

30

One of the characteristics of object orientation is polymorphism, the ability of a method to perform different behaviors according to the context that can be defined by a hierarchy (inheritance/interface) or overload(overloading).

It is suggested that in some situations the exchange of chains of if/switch by polymorphism which seems to be a more elegant solution.

Question

The concept of polymorphism (superscript, functions with the same name perform different behaviors) can be applied in the procedural paradigm in the language C?

What would be idea basic to implement it?

  • 2

    I guess it varies from language to language, doesn’t it? Anyway, one way possible to emulate it would keep together with each data its type, and when calling the function decide which implementation is most appropriate based on it. This would hardly support the syntax, however... But again, I think it varies from language to language.

  • 1

    Complementing (I am uncertain whether I should post an answer, because this question seems to me quite wide) if the language is strongly typed it is more difficult to write "generic" code that applies to more than one type. Otherwise, there are more possibilities, even if the language is not object-oriented (e.g.: in C you can use void* like "pointer to anything").

  • @mgibsonbr, thank you for the observations of the question, had not noticed that it is so wide. I’ll rephrase, your first comment of the idea an answer that seems to me appropriate I think you have captured the thrust of the question. If you have any more suggestions/editing how to specify/restrict the question call ai.

  • 1

    I agree with everything @mgibsonbr said. It is always possible to create a way to get the desired result, how easy, elegant and the result makes up for it is the question.

  • 1

    there are cases where using switch is more elegant than Dispatch by methods. Methods make it easy to extend the program with new classes but it is difficult to extend the program with new methods. Switchs work in the opposite direction: it is difficult to extend the program with new cases to the switch but it is easy to create new functions with a switch inside. In the functional programming community (where it is common to use algebraic types and "switch") this difference is known as problem of expression (Expression problem).

1 answer

27


A switch can always be replaced by a table, after all its internal implementation is usually done with a table. A sequence of ifs is just not so because it is difficult for the compiler to determine uniformity. So this gives a good indication of the answer.

Static polymorphism

If we’re talking static polymorphism, solved at compile time, it’s hard to do much. Either do manual or use a code manipulation tool. You might be able to use the preprocessor to help, but you probably would have to have a compiler take care of this before the current official compiler. It does not seem to be the focus of the question, but I ask for the sake of completeness.

Parametric polymorphism

Parametric polymorphism can be easily obtained with void *. The problem is, he’s not a security guy. Security could be obtained statically with an analyzer, so it falls into the previous question I mentioned. In fact C11 even has something in this sense with _Generic.

In dynamic languages, in a certain way, all types are already void * which is a pointer to any type. It is actually common in dynamic languages that objects have their type added to value. So all the data is parametric or at least polymorphic.

Polymorphism ad hoc

With the polymorphism ad hoc (dynamic overload), which is not very common in most static languages mainstream, it is possible to create a table with function names and their respective addresses. When I say name, it can be a string or it can be an integer value representing the name, perhaps even through an enumeration. If it is string will probably be done with a dictionary structure, but with a sequential integer one can use a array even.

It would probably have a function that would control access according to certain criteria.

In dynamic languages this table can be quite natural. In fact the objects in these languages are usually implemented as dictionaries or similar structures. There in the object itself is already stated what is each function that must be called. That is, in this type of language everything uses dynamic polymorphism. Which seems obvious since language is dynamic.

This mechanism could also be used in the next type of polymorphism, although perhaps with lower efficiency.

Instead of table it is also possible to assemble a structure.

Polymorphism of subtype

This I would call classical polymorphism, which everyone recognizes right away as a mechanism of polymorphism. In the most common implementation it chooses which function to use according to a pointer contained somewhere, then the concrete object has information of what type it is available at runtime and this type will determine which function will be called. I think that’s what the question is about.

There’s a question in the OS that talks about polymorphism in C which is one of the few languages mainstream that does not yet have subtype polymorphism in the language. There are some solutions:

typedef struct {
   data member_x;
} base;

typedef struct {
   struct base;
   data member_y;
} derived;

//esta função pertence à base e pode ser usada com estruturas derived
void function_on_base(struct base * a);

//esta função só pode receber derived de forma segura   
void function_on_derived(struct derived * b);

It is possible to internalize the function in the structure according to another example contained in the OS question:

struct inode_operations {
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
    ...
};

//chamada
struct inode_operations   *i_op;
i_op -> create(...);

Another way:

typedef struct {
   int (*SomeFunction)(TheClass* this, int i);
   void (*OtherFunction)(TheClass* this, char* c);
} VTable;

typedef struct {
   VTable* pVTable;
   int member;
} TheClass;

//chamada
int CallSomeFunction(TheClass* this, int i) {
  (this->SomeFunction)(this, i);
}

Example taken from a response in the OS.

It may seem gambiarra but this is more or less what happens internally in C++, for example. Note that pointers to SomeFunction and OtherFunction are adjusted according to the concrete data, or depending on the type of the object it will point to different functions. Of course, the code to build that VTable should be done by hand whenever a new object is created, there are no syntax facilities to help.

The call through pointer ((this->SomeFunction)) is only necessary if you choose polymorphism, if a function does not need this resource the call can be normal. For those who do not know this syntax what you do there is to take the content of this->SomeFunction which is a pointer, as can be seen in the definition of the structure, and is given a call on this pointer. In a normal call the pointer is fixed and is present directly in the code not needing a variable to indicate what to call. Obviously when you have to access a variable to pick up the pointer that will be used for the call is a little slower. This is called indirect.

A parameter this exists and is seen, but in a language with OOP syntax it is hidden and is implicit in the code.

Dynamic languages

In a dynamic language one way is to use a dictionary. Let’s suppose a fictitious language that has a dictionary and reference syntax for functions, then it could do:

func Base()
    var objeto = { => }
    objeto["dado"] = 1
    objeto["teste"] = &Base_teste
    return objeto
func Base_teste()
    return "base"
func Derivada()
    var objeto = { => }
    objeto["dado"] = 1
    objeto["dado2"] = "dado"
    objeto["teste"] = &Berivada_teste
    return objeto
func Derivada_teste()
    return "derivada"
//chamada
var base = Base()
var derivada = Derivada()
//imprime base porque a variável base possui um membro "teste" que contem &Base_teste
print base.teste()
//imprime derivada porque a variável base possui um membro "teste" que contem &Derivada_teste
print derivada.teste()

I put in the Github for future reference.

So even if somewhere waits for a guy Base if you pass an object of the type Derivada will call the method teste concrete-type.

Of course it varies from language to language, but PHP, Python, Ruby, Javascript, Lua, Harbour, anyway, any dynamic language, some even partially exposes this, which implement some form of OOP basically do this without the programmer seeing it this way.

Completion

Computers are procedural, so concretely there is no polymorphism, it is created through code patterns, such design patterns that can be embedded in a language mechanism. Computers only know how to transfer data from one place to another, change their bits in a very limited set of operations, make data comparisons and change the continuity of code execution. More and more we see abstractions and move away from the actual functioning of computers.

C++ was initially not a complete compiler and only turned the input code into a C code.

There is the doubt, using C which is a procedural language we can make polymorphic codes and implement the other characteristics of OOP, so the code written in this way is object oriented even if written in procedural language? I imagine so, but I have no information that formally defines this.

Addendum

Some people think that OOP cures cancer. It’s just a tool. It’s an abstraction designed to make it easier to solve certain problems. Nor is it a universal paradigm that the only pure language of this paradigm has failed. Multi-paradigm languages have worked well where most of the time is procedurally and in some circumstances the object orientation is used.

Even those who defend the correct use of the paradigm know and use it this way. Only purists think that any procedural vestige is bad. But even these don’t usually use Smalltalk.

So in the background using OOP in a procedural language is what we do, some languages just made it a little easier.

Look at what happened to C. Almost nobody saw OOP as something very important and it’s extremely rare to see someone implementing something that approached the paradigm. When C++, people continued to code in C and basically the same way. Many who preferred C++ made the choice for other reasons and not because it could be used with easy object orientation.

PHP was nothing object-oriented but it was always relatively easy to use the paradigm with what already existed in the language. Nobody did that. And OOP was already in fashion for a long time. Suddenly PHP had syntax that simplified OOP. And what happened? Everyone thought this was the right way to program, which is much better that way, that the advantages are too great to ignore.

Now, if OOP was so good and had so many benefits, why didn’t anyone use the paradigm even if they had to do it manually in procedural language? If OOP is only advantageous if it has a syntax that makes it easier then I conclude that the advantage is small. One explanation for not using before is that it does not usually compensate for its use in most cases.

Another explanation is that people don’t understand the workings of the mechanisms that make OOP work, they don’t know which procedural languages can behave like object-oriented so-called languages. That is why the question is important.

  • I started to write the answer when it was in another format, as it is more complete, I will leave so.

  • 1

    "the only pure language of this paradigm failed, "which?

  • 2

    @Patrick http://en.wikipedia.org/wiki/Smalltalk

  • 7

    Computers actually only run electrons through semiconductors, so what you describe as actual computer functioning is already an abstraction (and electrons and semiconductors, at some level, are also abstractions). According to the nature of the study, a different level of abstraction is adopted - if we always speak in "real" we do not leave the place. To use a paradigm is to write functional code that abstracts the work that the compiler will have transforming that into something else - if we write this other thing and not the code that abstracts it, we are definitely not using that paradigm.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.