¿Existe una ventaja real para los lenguajes dinámicos? [cerrado]

29

Primero quiero decir que Java es el único lenguaje que he usado, así que disculpe mi ignorancia sobre este tema.

Los idiomas escritos dinámicamente le permiten poner cualquier valor en cualquier variable. Entonces, por ejemplo, podría escribir la siguiente función (psuedocode):

void makeItBark(dog){
    dog.bark();
}

Y puedes pasar dentro de él cualquier valor. Mientras el valor tenga un bark()método, el código se ejecutará. De lo contrario, se genera una excepción de tiempo de ejecución o algo similar. (Corrígeme si me equivoco sobre esto).

Aparentemente, esto te da flexibilidad.

Sin embargo, leí un poco sobre lenguajes dinámicos, y lo que la gente dice es que cuando diseñas o escribes código en un lenguaje dinámico, piensas en los tipos y los tienes en cuenta, tanto como lo harías en un lenguaje estáticamente escrito.

Entonces, por ejemplo, al escribir la makeItBark()función, intenta que solo acepte 'cosas que pueden ladrar', y aún así necesita asegurarse de que solo le pase este tipo de cosas. La única diferencia es que ahora el compilador no le dirá cuándo cometió un error.

Claro, hay una ventaja de este enfoque que es que en los lenguajes estáticos, para lograr 'esta función acepta cualquier cosa que pueda ladrar', necesitaría implementar una Barkerinterfaz explícita . Aún así, esto parece una pequeña ventaja.

¿Me estoy perdiendo de algo? ¿Qué estoy ganando realmente usando un lenguaje de tipo dinámico?

Aviv Cohn
fuente
66
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof")). Ese argumento ni siquiera es una clase , es una tupla anónima llamada. La escritura de pato ("si suena como un ...") le permite hacer interfaces ad hoc con esencialmente cero restricciones y sin sobrecarga sintáctica. Puedes hacer esto en un lenguaje como Java, pero terminas con mucha reflexión desordenada. Si una función en Java requiere una ArrayList y desea darle otro tipo de colección, es SOL. En python que ni siquiera puede aparecer.
Phoshi
2
Este tipo de pregunta se ha hecho antes: aquí , aquí y aquí . Específicamente, el primer ejemplo parece responder a su pregunta. ¿Quizás puedas reformular el tuyo para que sea distinto?
logc
3
Tenga en cuenta que, por ejemplo, en C ++, puede tener una función de plantilla que funcione con cualquier tipo T que tenga un bark()método, con el compilador quejándose cuando pasa algo incorrecto pero sin tener que declarar realmente una interfaz que contiene ladrido ().
Wilbert
2
@Phoshi El argumento en Python todavía tiene que ser de un tipo particular; por ejemplo, no puede ser un número. Si tiene su propia implementación ad-hoc de objetos, que recupera sus miembros a través de alguna getMemberfunción personalizada , makeItBarkexplota porque llamó en dog.barklugar de dog.getMember("bark"). Lo que hace que el código funcione es que todos están implícitamente de acuerdo en usar el tipo de objeto nativo de Python.
Doval
2
@Phoshi Just because I wrote makeItBark with my own types in mind doesn't mean you can't use yours, wheras in a static language it probably /does/ mean that.Como señalé en mi respuesta, este no es el caso en general . Ese es el caso de Java y C #, pero esos lenguajes tienen sistemas de módulos y tipos paralizados, por lo que no son representativos de lo que puede hacer la escritura estática. Puedo escribir un genérico perfectamente makeItBarken varios lenguajes de tipo estático, incluso los no funcionales como C ++ o D.
Doval

Respuestas:

35

Los idiomas de tipo dinámico son de tipo único

Comparando sistemas de tipos , no hay ventaja en la escritura dinámica. La escritura dinámica es un caso especial de escritura estática : es un lenguaje de escritura estática donde cada variable tiene el mismo tipo. Puede lograr lo mismo en Java (menos concisión) haciendo que cada variable sea de tipo Objecty que los valores de "objeto" sean de tipo Map<String, Object>:

void makeItBark(Object dog) {
    Map<String, Object> dogMap = (Map<String, Object>) dog;
    Runnable bark = (Runnable) dogMap.get("bark");
    bark.run();
}

Por lo tanto, incluso sin reflexión, puede lograr el mismo efecto en casi cualquier lenguaje de tipo estático, dejando de lado la comodidad sintáctica. No obtienes ningún poder expresivo adicional; por el contrario, tiene menos poder expresivo porque en un lenguaje de tipo dinámico, se le niega la capacidad de restringir variables a ciertos tipos.

Hacer un ladrido de pato en un lenguaje estáticamente escrito

Además, un buen lenguaje de tipo estático le permitirá escribir código que funcione con cualquier tipo que tenga una barkoperación. En Haskell, esta es una clase de tipo:

class Barkable a where
    bark :: a -> unit

Esto expresa la restricción de que para que algún tipo ase considere Barkable, debe existir una barkfunción que tome un valor de ese tipo y no devuelva nada.

Luego puede escribir funciones genéricas en términos de la Barkablerestricción:

makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)

Esto dice que makeItBarkfuncionará para cualquier tipo que satisfaga Barkablelos requisitos. Esto puede parecer similar a un interfaceen Java o C #, pero tiene una gran ventaja: los tipos no tienen que especificar por adelantado qué clases de tipos satisfacen. Puedo decir que el tipo Duckes Barkableen cualquier momento, incluso si Duckes un tipo de terceros que no escribí. De hecho, no importa que el escritor de Duckno haya escrito una barkfunción; puedo proporcionarla después del hecho cuando digo el idioma que Ducksatisface Barkable:

instance Barkable Duck where
    bark d = quack (punch (d))

makeItBark (aDuck)

Esto dice que Ducks puede ladrar, y su función de ladrido se implementa golpeando al pato antes de hacerlo graznar. Con eso fuera del camino, podemos llamar makeItBarka los patos.

Standard MLy OCamlson aún más flexibles en el sentido de que puede satisfacer la misma clase de tipo en más de una forma. En estos idiomas puedo decir que los enteros se pueden ordenar usando el orden convencional y luego dar la vuelta y decir que también se pueden ordenar por divisibilidad (por ejemplo, 10 > 5porque 10 es divisible por 5). En Haskell solo puede crear una instancia de una clase de tipo una vez. (Esto le permite a Haskell saber automáticamente que está bien llamar barka un pato; en SML u OCaml debe ser explícito sobre qué bark función desea, porque puede haber más de una).

Concisión

Por supuesto, hay diferencias sintácticas. El código de Python que presentó es mucho más conciso que el equivalente de Java que escribí. En la práctica, esa concisión es una gran parte del atractivo de los lenguajes de tipo dinámico. Pero la inferencia de tipos le permite escribir código que sea igual de conciso en lenguajes tipados estáticamente, al liberarlo de tener que escribir explícitamente los tipos de cada variable. Un lenguaje de tipo estático también puede proporcionar soporte nativo para el tipeo dinámico, eliminando la verbosidad de todas las manipulaciones de fundición y mapas (por ejemplo, C # 's dynamic).

Programas correctos pero mal escritos

Para ser justos, la escritura estática necesariamente excluye algunos programas que son técnicamente correctos aunque el verificador de tipos no pueda verificarlo. Por ejemplo:

if this_variable_is_always_true:
    return "some string"
else:
    return 6

La mayoría de los idiomas de tipo estático rechazarían esta ifafirmación, aunque la rama else nunca ocurrirá. En la práctica, parece que nadie hace uso de este tipo de código; cualquier cosa demasiado inteligente para el verificador de tipos probablemente hará que los futuros mantenedores de su código lo maldigan a usted y a sus familiares. Por ejemplo, alguien tradujo con éxito 4 proyectos de Python de código abierto a Haskell, lo que significa que no estaban haciendo nada que un buen lenguaje de tipo estático no pudiera compilar. Además, el compilador encontró un par de errores relacionados con el tipo que las pruebas unitarias no detectaban.

El argumento más fuerte que he visto para la tipificación dinámica son las macros de Lisp, ya que le permiten extender arbitrariamente la sintaxis del lenguaje. Sin embargo, Typed Racket es un dialecto de Lisp de tipo estático que tiene macros, por lo que parece que el tipeo estático y las macros no son mutuamente excluyentes, aunque tal vez sea más difícil de implementar simultáneamente.

Manzanas y naranjas

Finalmente, no olvide que hay diferencias más grandes en los idiomas que solo su sistema de tipos. Antes de Java 8, hacer cualquier tipo de programación funcional en Java era prácticamente imposible; una lambda simple requeriría 4 líneas de código de clase anónimo repetitivo. Java tampoco tiene soporte para literales de colección (por ejemplo [1, 2, 3]). También puede haber diferencias en la calidad y disponibilidad de herramientas (IDE, depuradores), bibliotecas y soporte comunitario. Cuando alguien afirma ser más productivo en Python o Ruby que Java, esa disparidad de características debe tenerse en cuenta. Hay una diferencia entre comparar idiomas con todas las baterías incluidas , núcleos de idiomas y sistemas de tipos .

Doval
fuente
2
Olvidó atribuir su fuente para el primer párrafo - existentialtype.wordpress.com/2011/03/19/…
2
@Matt Re: 1, I haven't assumed it's not important; I addressed it under Conciseness. Re: 2, although I never explicitly said it, by "good" I mean "has thorough type inference" and "has a module system that allows you to match code to type signatures after the fact", not up-front like Java/C#'s interfaces. Re 3, the burden of proof is on you to explain to me how given two languages with equivalent syntax and features, one dynamically-typed and the other with full type inference, you wouldn't be able to write code of equal length in both.
Doval
3
@MattFenwick I've already justified it - given two languages with the same features, one dynamically-typed and the other statically-typed, the main difference between them will be the presence of type annotations, and type inference takes that away. Any other differences in syntax are superficial, and any differences in features turns the comparison into apples vs oranges. It's on you to show how this logic is wrong.
Doval
1
You should have a look at Boo. It's statically typed with type inference, and has macros that allow for the language's syntax to be extended.
Mason Wheeler
1
@Doval: cierto. Por cierto, la notación lambda no se usa exclusivamente en la programación funcional: que yo sepa, Smalltalk tiene bloques anónimos, y Smalltalk está tan orientado a objetos como puede ser. Entonces, a menudo la solución es pasar un bloque de código anónimo con algunos parámetros, sin importar si se trata de una función anónima o un objeto anónimo con exactamente un método anónimo. Creo que estas dos construcciones expresan esencialmente la misma idea desde dos perspectivas diferentes (la funcional y la orientada a objetos).
Giorgio
11

This is a difficult, and quite subjective issue. (And your question may get closed as opinion-based, but that doesn't mean it's a bad question - on the contrary, even thinking about such meta-language questions is a good sign - it's just not well-suited to the Q&A format of this forum.)

Here's my view of it: The point of high-level languages is to restrict what a programmer can do with the computer. This is surprising to many people, since they believe the purpose is to give users more power and achieve more. But since everything you write in Prolog, C++ or List is eventually executed as machine code, it is actually impossible to give the programmer more power than assembly language already provides.

The point of a high-level language is to help the programmer to understand the code they themselves have created better, and to make them more efficient at doing the same thing. A subroutine name is easier to remember than a hexadecimal address. An automatic argument counter is easier to use than a call sequence here you have to get the number of arguments exactly right on your own, with no help. A type system goes further and restricts the kind of arguments you can provide in a given place.

Here is where people's perception differs. Some people (I'm among them) think that as long as your password checking routine is going to expect exactly two arguments anyway, and always a string followed by a numeric id, it's useful to declare this in the code and be automatically reminded if you later forget to follow that rule. Outsourcing such small-scale book-keeping to the compiler helps free your mind for higher-level concerns and makes you better at designing and architecting your system. Therefore, type systems are a net win: they let the computer do what it's good at, and humans do what they're good at.

Others see to quite differently. They dislike being told by a compiler what to do. They dislike the extra up-front effort to decide on the type declaration and to type it. They prefer an exploratory programming style where you write actual business code without having a plan that would tell you exactly which types and arguments to use where. And for the style of programming they use, that may be quite true.

I'm oversimplifying dreadfully here, of course. Type checking is not strictly tied to explicit type declarations; there is also type inference. Programming with routines that actually do take arguments of varying types does allow quite different and very powerful things that would otherwise be impossible, it's just that a lot of people aren't attentive and consistent enough to use such leeway successfully.

In the end, the fact that such different languages are both very popular and show no signs of dying off shows you that people go about programming very differently. I think that programming language features are largely about human factors - what supports the human decision-making process better - and as long as people work very differently, the market will provide very different solutions simultaneously.

Kilian Foth
fuente
3
Thanks for the answer. You said that some people ' dislike being told by a compiler what to do. [..] They prefer an exploratory programming style where you write actual business code without having a plan that would tell you exactly which types and arguments to use where.' This is the thing that I don't understand: programming isn't like musical improvisation. In music if you hit a wrong note, it may sound cool. In programming, if you pass something into a function that isn't supposed to be there, you'll most likely get nasty bugs. (continuing in next comment).
Aviv Cohn
3
I agree, but many people don't agree. And people are quite possessive about their mental preconceptions, particularly since they're often unaware of them. That's why debates about programming style usually degenerate into arguments or fights, and it's rarely useful to start them with random strangers on the internet.
Kilian Foth
1
This is why - judging by what I read - people using dynamic languages take types into account just as much people using static languages. Because when you write a function, it's supposed to take arguments of a specific kind. Doesn't matter if the compiler enforces this or not. So it comes down to static typing helping you with this, and dynamic typing doesn't. In both cases, a function has to take a specific kind of input. So I don't see what the advantage of dynamic typing is. Even if you prefer an 'exploratory programming style', you still can't pass whatever you want into a function.
Aviv Cohn
1
People often talk about very different types of projects (especially regarding size). The business logic for a web site will be very simple compared to say a full ERP system. There is less risk that you get things wrong and the advantage of being able to very simply reuse some code is more relevant. Say I have some code that generates a Pdf (or some HTML) from a data structure. Now I have a different data source (first was JSON from some REST API, now it's Excel importer). In a language like Ruby it can be super easy to 'simulate' the first structure, 'make it bark' and reuse the Pdf code.
thorsten müller
@Prog: The real advantage of dynamic languages is when it comes to describing things which is really hard with a static type system. A function in python, for example, could be a function reference, a lambda, a function object, or god knows what and it'll all work the same. You can build an object that wraps another object and automatically dispatches methods with zero syntactic overhead, and every function essentially magically has parametrized types. Dynamic languages are amazing for quickly getting stuff done.
Phoshi
5

Code written using dynamic languages is not coupled to a static type system. Therefore, this lack of coupling is an advantage compared to poor/inadequate static type systems (although it may be a wash or a disadvantage compared to a great static type system).

Furthermore, for a dynamic language, a static type system doesn't have to be designed, implemented, tested, and maintained. This could make the implementation simpler compared to a language with a static type system.


fuente
2
Don't people tend to eventually re-implement a basic static type system with their unit tests (when targeting a good test coverage)?
Den
Also what do you mean by "coupling" here? How would it manifest in an e.g. micro-services architecture?
Den
@Den 1) good question, however, I feel that it's outside the scope of the OP and of my answer. 2) I mean coupling in this sense; briefly, different type systems impose different (incompatible) constraints on code written in that language. Sorry, I can't answer the last question -- I don't understand what's special about micro-services in this regard.
2
@Den: Very good point: I often observe that unit tests I write in Python cover errors that would be caught by a compiler in a statically typed language.
Giorgio
@MattFenwick: You wrote that it is an advantage that "... for a dynamic language, a static type system doesn't have to be designed, implemented, tested, and maintained." and Den observed that you often do have to design and test your types directly in your code. So the effort is not removed but moved from language design to the application code.
Giorgio