¿Cuáles son algunos límites estilísticos razonables en la inferencia de tipos?

8

C ++ 0x agrega un soporte de inferencia de tipos bastante completo. Estoy muy tentado a usarlo en todos los lugares posibles para evitar una repetición indebida, pero me pregunto si eliminar la información de tipo explícita en todo el lugar es una buena idea. Considere este ejemplo bastante artificial:

Foo.h:

#include <set>

class Foo {
private:

    static std::set<Foo*> instances;

public:

    Foo();
    ~Foo();

    // What does it return? Who cares! Just forward it!
    static decltype(instances.begin()) begin() {
        return instances.begin();
    }

    static decltype(instances.end()) end() {
        return instances.end();
    }

};

Foo.cpp:

#include <Foo.h>
#include <Bar.h>

// The type need only be specified in one location!
// But I do have to open the header to find out what it actually is.
decltype(Foo::instances) Foo::instances;

Foo() {

    // What is the type of x?
    auto x = Bar::get_something();

    // What does do_something() return?
    auto y = x.do_something(*this);

    // Well, it's convertible to bool somehow...
    if (!y) throw "a constant, old school";

    instances.insert(this);

}

~Foo() {
    instances.erase(this);
}

¿Diría que esto es razonable o es completamente ridículo? Después de todo, especialmente si está acostumbrado a desarrollar en un lenguaje dinámico, realmente no necesita preocuparse demasiado por los tipos de cosas, y puede confiar en que el compilador detectará cualquier abuso atroz del sistema de tipos. Pero para aquellos de ustedes que confían en el soporte del editor para las firmas de métodos, no tienen suerte, por lo que usar este estilo en una interfaz de biblioteca es probablemente una práctica realmente mala.

Me parece que escribir cosas con todos los tipos posibles implícitos en realidad hace que mi código sea mucho más fácil de seguir para mí, porque elimina casi todo el desorden habitual de C ++. Su kilometraje puede, por supuesto, variar, y eso es lo que me interesa saber. ¿Cuáles son las ventajas y desventajas específicas del uso radical de la inferencia de tipos?

Jon Purdy
fuente
En realidad, se hace necesario la atención sobre los tipos de si está desarrollando en un lenguaje dinámico; Si tiene el tipo de algo mal, no lo descubrirá hasta que llegue a esa sección de código en tiempo de ejecución.
Larry Coleman
@ Larry: Por supuesto. Pero no en el sentido de que necesite predecir el tipo exacto y la cadena de herencia de cada objeto en uso.

Respuestas:

5

Siendo principalmente un programador de Python, comparto la mentalidad de que el programador no necesita saber el tipo exacto. En el caso de C ++, especialmente cuando se trata de plantillas con plantillas con plantillas ... Por supuesto, eso no se debe a que desprecie la escritura estática (no, considero que Haskell es una de las mejores cosas desde el pan rebanado, en parte debido a su escritura estática), sino porque no me importa el tipo exacto. Por qué, no se demuestra bien con ejemplos que usan nombres como fooo get_stuff(). Así que escojamos algo más cercano a la realidad:

auto users = get_users();
vector<decltype(users[0])> blocked_users;
/* I'm not a C++ guru, much less C++0x so forgive me if type
   inference and foreach must be combined differently */
for (auto user : users) {
    if (check_name(user.name)) blocked_users.push_back(user)
}

No es una sola anotación de tipo, pero está perfectamente claro lo que hace, ¿verdad? Este código no le importa de qué tipo es users, solo necesita un rango para iterar sobre el que contiene cosas con una nameque se puede alimentar check_name. Nada mas. A nadie le importan los 100 caracteres de los nombres de tipo que de lo contrario tendría que escribir.

Lo mismo se aplica a la mayoría de los códigos con nombres significativos. El ejemplo en la pregunta no es claro, pero tampoco sería claro con nombres de tipo explícitos, porque ni el contexto ni los identificadores dan ninguna indicación de lo que está sucediendo. Utilice identificadores significativos, y el código se puede entender independientemente de las anotaciones de tipo explícito.


fuente
3

La razón por la que hicieron inferencia fue para evitar que las personas escribieran código como:

foo().bar().baz().etc();

Cuando podrías escribir:

Foo f = foo();
Bar b = f.bar();
...

Excepto que Foo sería un tipo de plantilla largo, por lo que se vuelve más agradable escribir:

auto f = foo();
auto b = f.bar();
...

Por lo tanto, como regla general, si usar auto hace que escriba código como se indicó anteriormente en lugar de código similar al del primer ejemplo, entonces úselo. De lo contrario, siga agregando definiciones explícitas.

dan_waterworth
fuente
Tiene sentido. Me encuentro usando automuchos casos en los que estoy seguro de que otros serían explícitos, pero no veo lo que realmente está mal, por ejemplo auto f = new SomethingLong(), porque es obvio lo que devuelve la expresión. Me pregunto dónde dibujar la línea.
Jon Purdy
Eso está bien si SomethingLongno es parte de ninguna estructura de herencia, pero a menos que esté seguro de que nunca lo será, le aconsejaría que no lo haga. Es mucho más fácil trazar la línea con precaución.
dan_waterworth
2

Hay buenas razones para no usar siempre la inferencia de tipos. Haskell tiene inferencia de tipos, pero generalmente declaro tipos de funciones explícitamente de todos modos. Eso es en parte el resultado de mi estilo de desarrollo. Declaro la función primero:

myFunction :: [Int] -> Int
myFunction xs = undefined
A continuación, escribiré el código que usa esa función y haré una compilación para asegurarme de que escribe verificaciones. Una vez que escriba cheques, continuaré con la implementación.

La otra razón para declarar tipos de funciones es que las declaraciones pueden servir como documentación adicional. Que esta documentación se verifique en cada compilación es una ventaja.

Larry Coleman
fuente
2
Si bien el enfoque de tipo primero es muy elegante en Haskell, dudo que sea adecuado para C ++. Sin conceptos , una firma de plantilla C ++ básicamente no dice nada : es la implementación la que define los tipos de requisitos y los argumentos que deben cumplir. Las plantillas simplemente escriben pato en tiempo de compilación - "intenta y mira si funciona". Por lo tanto, diría que podemos estar más implícitos con los tipos, al igual que los lenguajes dinámicos de tipo pato también.
Dario