Cómo leer código JAVA y C++
He escuchado a muchas personas decirme que son buenas en C++ o Java, pero no entienden Smalltalk en absoluto. Según ellos, ¡hay un libro sobre Smalltalk! Lo pensé y pensé que lo que decían podría tener sentido. Si supiera Java, no sería capaz de entender un fragmento de código que he escrito durante muchos años. Antes de entender Smalltalk, debemos aclarar algunos conceptos muy simples y algunos conceptos de sintaxis sutiles y extraños. Si "Lao Wang no entiende Smalltalk", tal vez pueda mejorar su situación. Espero que los lectores puedan comenzar rápidamente. Supongo que el lector comprende la programación orientada a objetos. Si ya conoces Smalltalk, perdóname por jugar.
Oh, los detalles del vocabulario son muy simples
Algunas de las convenciones y modismos que encuentras al leer Smalltalk por primera vez pueden ser muy diferentes a los de otros idiomas. y pueden confundirlo, como comillas dobles alrededor de comentarios, comillas simples alrededor de cadenas, expresiones de sintaxis especiales para caracteres (como $x para "x"). También existe el concepto de símbolos, que son cadenas que tienen solo una instancia en la memoria, por ejemplo, cuando se construye un símbolo (generalmente en tiempo de compilación), primero se busca la misma instancia en la memoria y, si hay una, se busca esa misma instancia en la memoria. se utiliza directamente. El propósito de esto no es ahorrar memoria, sino optimizar la eficiencia de la comparación (consulte los detalles a continuación):
"Esto es un comentario"
"Esto es una cuerda" p> p>
# 'Esto es un símbolo'
# thisIsASymbolToo
Existe una diferencia sutil entre los operadores de asignación y los operadores de comparación:
: =//Asignación
=//Comparación de igual contenido, comparación profunda
= =//Comparación única, comparación superficial
Si me das dos diferentes objetos, están referenciados por diferentes variables "A" y "B", puedo decirte si son el mismo objeto (por a == b) o simplemente objetos diferentes que se ven iguales (por a = b). Para decirlo sin rodeos, == compara dos punteros, = compara todo el contenido del objeto.
Las comas rara vez aparecen en Smalltalk porque no sirven como elementos gramaticales. Es por eso que las matrices son sencillas, como aquellas sin comas adicionales:
#(1 2 3 4 5)
Sin embargo, las comas tienen sentido. Es un operador. Ocasionalmente puedes verlo usado para concatenar dos cadenas, por ejemplo:
Cadena1', 'Cadena2'
Las palabras clave están en todas partes
Smalltalk está lleno de palabras clave. Pero son buenos para la legibilidad, no para la confusión. Para descubrir por qué, comencemos con un fragmento de C++ y Java. Por ejemplo, es posible que esté familiarizado con el siguiente texto:
t->; rotar(a, v); // C++
t.rotate(a, v); / Java< Al objeto /p>
t se le envía un mensaje de rotación con los parámetros a y v. Los lectores que quieran comprender este tipo de código generalmente necesitan encontrar la declaración de la variable y determinar su tipo. Suponemos que la declaración es la siguiente:
Convertir t;
Float a;
Vector v; cualquier tipo en objeto Smalltalk. Así que no hay necesidad de una descripción del tipo, pero aun así necesitamos declarar la variable, como por ejemplo:
|t a v|
Sin mirar la declaración, un buen programador de Smalltalk juzgue el tipo de variable según el nombre. Entonces, digámoslo de otra manera:
|un vector de ángulo de deformación|
Pero permítanme continuar usando el nombre corto original para evitar que el código de muestra sea demasiado largo y afectando la lectura. "Mejoremos" la sintaxis de C++ y Java eliminando factores innecesarios. Por ejemplo, el siguiente código aún está claro:
t.rotate(a,v); //Método de escritura original
trotate(a,v); ?
t Rotate a, v; //¿Quién necesita paréntesis?
Para mejorar aún más la sintaxis, necesitamos saber qué representan los parámetros A y V respectivamente.
Supongamos que todo el ejemplo significa "rotar un ángulo a alrededor del vector v". Se mejora aún más de la siguiente manera:
t Girar a alrededor de v; //¿Quién necesita comas?
¿Sabemos qué es cada ingrediente? No hay problema, porque en nuestro ejemplo mejorado, "t" es la variable, "rotar" es el nombre del método, "por" es el separador, "a" es la variable, "alrededor" es el separador y también lo es el último variable "v". Para eliminar posibles ambigüedades, establecemos la regla de que el delimitador va seguido de dos puntos. Tenemos:
t Presione: a para rotar hacia la izquierda y hacia la derecha: v; // ¿Quién necesita ambigüedad?
Finalmente, enfatizamos que el delimitador es parte del nombre del método por ejemplo, asumiendo que necesitamos una función de la forma "rotate by: around:", eliminando los espacios, obtendremos "rotateby:" around" como nombre final, luego escriba en mayúscula la letra que no es la primera para mejorar la legibilidad, lo que da como resultado "rotateBy: around". Entonces nuestro ejemplo se puede escribir como:
Esta es una pequeña charla.
El nombre del método se divide en varias partes. Afortunadamente, es fácil reunir estas piezas para formar un nombre completo. Cuando estamos en una clase, definimos el nombre del método de la siguiente manera:
Ángulo de autorrotación: vector
|Resultado|
Resultado: = Calcular la respuesta.
Resultados
Cuando se ejecuta, existe una correspondencia uno a uno entre "T" y "self", "A" y "angle", y "V" y " vector". Tenga en cuenta que "" indica que se ha devuelto el resultado; así es como se escribe la palabra clave "return" en Smalltalk. La variable "yo" es sinónimo de "esto". Si no hay una declaración de retorno al final del método, "self" se ejecuta como una declaración implícita; es posible que olvide agregar una declaración de retorno cuando complete un método, pero está bien. Esto también significa que el método devolverá un valor incluso si el remitente del mensaje no lo requiere.
De hecho, la sintaxis idiomática de Smalltalk requiere que "self" no pueda aparecer explícitamente en el encabezado del método (sino que debe estar implícito), por ejemplo:
Relación de rotación: ángulo: vector
|Resultado|
Resultado:= Calcula la respuesta.
Resultados
La belleza de la sintaxis de las palabras clave es que podemos definir diferentes palabras clave para diferentes métodos. Por ejemplo, podemos definir el segundo método de la siguiente manera:
dirección de trotación:dirección del vector:ángulo
No es necesario recordar el orden de los parámetros. La palabra clave nos insta a realizar el pedido. Por supuesto, los programadores tienen la capacidad de abusar de las palabras clave, por ejemplo, si definimos las palabras clave de la siguiente manera:
trotate:angle and:vector
Será difícil para los lectores entender el orden correcto de los argumentos. Este es un mal estilo de programación, estaría bien si solo hubiera un parámetro. Cuando solo hay un parámetro, todavía necesitamos el nombre del método, por ejemplo:
t rotateAroundXBy:angle
t rotateAroundYBy:angle
Queremos la palabra clave; (Distinción de dos puntos fácil de usar) es la descripción del parámetro. Pero ¿qué pasa si este método no tiene parámetros?
T makeIdentity: //¿Tiene sentido los dos puntos al final?
Si las palabras clave representan descripciones de parámetros, entonces no podemos utilizar palabras clave sin parámetros. Entonces el mensaje con cero parámetros debería ser:
T makeIdentity //Esto es una pequeña charla.
Por supuesto, los operadores binarios son los mismos, pero los operadores unarios (makeIdentity es un mensaje unario, pero no un operador unario) son diferentes. Cuando aparecen varias piezas de información juntas, nuestra expresión puede verse así:
un número negativo (b está entre: c y: d)
ifTrue: [a : = c negación]
Como lector, debes saber que a "A" se le envió un mensaje llamado "negativo" (cero parámetros), el cual devolvió verdadero o falso y también se envió a "B" un mensaje llamado " entre: c y: d" que devuelve verdadero o falso.
Los resultados de los dos OR juntos se convierten en el destinatario del mensaje "ifTrue: [a := c negación]". Ésta es la forma auténtica de escribir la estructura de control si-entonces, no una sintaxis especializada. Es solo una sintaxis de palabra clave estándar, con un booleano como receptor, "ifTrue" como palabra clave y "[a := c negated]" (lo llamamos bloque) como parámetro. En Smalltalk nunca encontrará "a := -c" porque no hay un operador unario, pero verá la constante "-5" con "-" como parte de la constante.
Entonces, si ve una expresión como "-3.5 hecho de truncamiento negativo", debe darse cuenta inmediatamente de que no contiene ninguna palabra clave. Entonces a "-3.5" se le debe haber enviado un mensaje "negativo"; el resultado de la ejecución 3.5 se envía junto con el mensaje "truncado" luego el resultado de la ejecución 3 se envía "factorialmente", produciendo el resultado final 6;
Por supuesto, también existen reglas como prioridad de operación (de izquierda a derecha), prioridad de mensaje (el parámetro cero es el más alto, las operaciones binarias son el segundo y la última palabra clave es la última palabra clave). Estos son muy importantes al escribir código, pero no necesita preocuparse por estos detalles al leer el código. Las expresiones son las siguientes de izquierda a derecha:
1+2 * 3 es 9
No hay orden de precedencia, pero rara vez te encuentras con programadores de Smalltalk que escriban expresiones como esta porque de esto confundirá a los lectores que no sean de Smalltalk. Los programadores comunes de Smalltalk usan la siguiente notación alternativa:
(1 + 2) * 3
Aunque los paréntesis son innecesarios.
Un punto y coma y un punto son diferentes.
La mayoría de los programadores que no son de Smalltalk usan un punto y coma para marcar el final de una declaración, pero en Smalltalk usan un punto para expresar este significado. Así que no escribiremos:
Depósito en cuenta: $100;
Adición favorita: Convertir;
Dice:
Depósito en cuenta : 100 dólares.
Adición de colección: conversión.
¡Mmm! ¿Le confunden las noticias sobre el “dólar”? No te sientas raro. Debe haber un método llamado "dólares" que construya un objeto "Dólar" en la clase Integer y lo devuelva. No está en el entorno estándar de Smalltalk, pero podemos ampliarlo. Las clases base (clases integradas) se pueden ampliar a clases definidas por el usuario cuando sea necesario.
Por lo tanto, el punto es un terminador de declaración y es opcional en la última oración (puede usarlo como terminador de declaración si lo desea). Sin embargo, el punto y coma sigue siendo un delimitador especial legal (no un terminador). Se utiliza para especificar que el receptor se puede contraer. Entonces, escríbelo así:
|p|
p := Nuevo Cliente.
Nombre: "Jack".
páginaEdad: 32.
Dirección: "Tierra".
Se puede escribir como:
|p|
p := Nuevo cliente.
p
Nombre: ‘Jack’;
Edad: 32
Dirección: “Tierra”.
O mejor aún:
|p|
p :=Nuevo Cliente
Nombre: 'Jack';
p>
Edad: 32;
Dirección: "Tierra".
Smalltalk no es sensible tipográficamente. Incluso podemos poner todas las declaraciones en la misma línea. Esencialmente, el punto y coma especifica que el mensaje anterior se envió para modificar el receptor y que el siguiente mensaje debe enviarse al mismo receptor (en lugar del resultado de la operación, que se ignora y descarta).
En el último ejemplo, se envía "nuevo" a una clase para obtener una instancia (el resultado de la operación). Luego se envía "Nombre: 'Jack'" a la instancia. El primer punto y coma especifica que los resultados de "Nombre: 'Jack'" se ignoran y "Edad: 32" deben enviarse al destinatario anterior (misma instancia). El resultado del segundo punto y coma que especifica "edad: 32" se ignora y "dirección: 'Tierra'" debe enviarse al destinatario anterior (que sigue siendo la misma instancia).
Finalmente, el resultado de la operación de "dirección: 'Tierra'" se asigna a P. Los métodos que modifican el receptor generalmente devuelven el receptor mismo, por lo que P está vinculado a la instancia de cliente modificada más recientemente.
Podemos simplificar la tarea anterior reemplazando el punto y coma con la frase en inglés "AND ALSO". Es decir, se envía "nuevo" a la clase "Cliente" y la instancia resultante se envía con los mensajes "nombre: 'jack'" y "edad: 32" y "dirección: 'tierra'". Enviar repetidamente mensajes diferentes al mismo destinatario se llama cascada en Smalltalk. Los puntos y comas también pueden aparecer en subexpresiones, como "p:=(Cliente nuevo nombre:'Jack';Edad:32;Dirección:'Tierra')"; tenga en cuenta los paréntesis.
El método Get/Set tiene el mismo nombre que la instancia de la variable.
En Smalltalk, las variables miembro de las instancias de clase de cliente, como el nombre, la edad y la dirección, son privadas. Pero puedes acceder a ellos implementando ciertos métodos. Por ejemplo, en C++ (similar a Java), a menudo escribimos los siguientes métodos de acceso (a menudo llamados métodos get/set):
long getAge(){ return age;}
void setAge(long new age){ age = new age;}
Si aplica este enfoque a una gran cantidad de clases, escribirá cientos de mensajes comenzando con get y set. Si inadvertidamente decide simplificar estos métodos usando nombres simplificados (escritos más adelante), incluso si el compilador de Java puede identificarlos correctamente, causará confusión en el análisis del compilador de C++, porque no podrá distinguir variables y métodos: p>
long age(){ return age;}
void age(long new age){ age = new age;}
¿Puedes distinguir entre la variable "edad " y el mensaje "edad" "?" Conviene hacer una distinción. Cuando use mensajes, debe poner paréntesis como "edad () o edad (22)"; cuando use variables, no es necesario poner paréntesis. La escritura correspondiente en Smalltalk es:
^age
Edad: nueva era
Normalmente usamos la siguiente escritura línea por línea para mejorar la legibilidad:
Edad
Edad
Edad: nueva era
Edad: = nueva era
En Smalltalk, tus Variables se puede distinguir fácilmente de los mensajes sin depender de paréntesis. Si sabes claramente la diferencia entre ellas, podrás ver cuántas variables hay en la siguiente expresión:
edadAGEAGE:AGEAGE+AGEAGE
¡Hmm! La respuesta es tres; la primera y la cuarta edad deben ser variables (la subexpresión que sigue inmediatamente a la palabra clave y todas las expresiones deben comenzar con una variable), y la séptima edad también debe ser una variable (el operador binario La siguiente subexpresión también debe comenzar con una variable). Veamos un ejemplo más obvio de algo como esto:
Tamaño del nombre // El nombre debe ser una variable.
Nombre propio // nombre debe ser una variable.
Colecciones ampliamente utilizadas
Las dos colecciones más utilizadas en Smalltalk son conjuntos ordenados y diccionarios. El concepto de matriz es equivalente a un conjunto ordenado de tamaño constante.
|a b|
a :=Nueva colección pedida
Suplemento: #rojo
Suplemento: #verde;
p>
Tú mismo.
b :=Diccionario nuevo
en:# red put:# rouge;
en:#Green putt:# vert
tú mismo.
Las variables en cada asignación anterior están vinculadas al resultado de la ejecución del último mensaje, por ejemplo, el resultado de "self" es el último conjunto creado; El mensaje "usted mismo" está destinado a devolver el receptor del mensaje (similar a una operación no operativa), pero "add:" y "at:put:" no lo son (devuelven el último argumento).
Entonces, si no existe un "yo", "A" debe ser "#verde" y "B" debe ser "#verde".
Utilizo deliberadamente escritura en cascada para explicar por qué "self" aparece de forma independiente en los métodos de las clases integradas.
La ventaja de las colecciones en Smalltalk es que en ellas se puede almacenar cualquier tipo de objeto. Incluso las claves de un diccionario pueden ser de cualquier tipo; los objetos de la misma colección pueden ser de diferentes tipos. No tenemos que reinventar nuevos tipos de colecciones solo para recopilar un montón de tipos nuevos en una sola instancia.
Puedes acceder a un conjunto ordenado como si fuera una matriz, por ejemplo, "a at: 1" indexa el elemento 1. También se puede acceder a los diccionarios de la misma forma; por ejemplo "en: #red". Pero en muchas aplicaciones, no necesitamos preocuparnos por las claves. De esta manera, es fácil iterar sobre elementos:
A do: [:item |
Hacer algo con un elemento].
b do: [:item |
Usar un elemento para hacer algo].
Incluso si los elementos de la colección son de diferentes tipos, la variable "item" obtendrá cada elemento uno por uno. Si es necesario, podemos saber en tiempo de ejecución qué es un objeto escribiendo "item isKindOf: Boat", que devuelve verdadero o falso. Al mismo tiempo, existen muchos tipos especiales de mensajes de consulta, como "item isCollection" o "item isNumber". Además, hay muchos mensajes de construcción de bucles para crear nuevas colecciones, como:
c:= client select:[:client | client netWorth>500000].
d := Colección de clientes:[:cliente|nombre del cliente].
En el ejemplo anterior, adquirimos un grupo de grandes clientes. En este último, obtenemos una colección de nombres de clientes (la colección original es una colección de clientes).
La abstracción ordenada no requiere la construcción de nuevas clases.
Los lectores suelen ver el siguiente código:
Valor de relleno:x valor:y valor:z
Las palabras clave aquí son todas "valor:". Para un programador que no sea Smalltalk, este estilo de escritura no tiene sentido y resulta confuso. Los programadores han creado (y a menudo lo hacen) nuevas abstracciones aquí.
Permíteme explicarte las funciones que solo son compatibles con Smalltalk. Tomando la clase Cliente que hemos introducido muchas veces como ejemplo, supongamos que tenemos una simple necesidad de recorrer todas las partes de un cliente, por ejemplo, queremos recorrer primero el nombre, luego la edad y finalmente la dirección;
La solución convencional a esta necesidad en C++ y Java es crear una nueva clase de flujo especial o clase enumeradora, quizás llamada ClientIterator, que tiene inicialización, determina si la iteración ha finalizado y, si no es así, Luego itere el siguiente objeto y otros métodos. Usando estos métodos, podemos escribir un bucle que inicializa el iterador, obtiene el siguiente objeto y lo procesa hasta que finaliza la iteración. La ventaja de los iteradores es que puede proporcionar una única variable para rastrear la posición de la iteración en el procesamiento secuencial; no es necesario extender la clase de cliente a una variable "temporal" para iterar;
El siguiente es un código deliberadamente abstracto:
aCliente:=Código para obtener a.
Parte del cliente Do: [:object |
Impresión de objetos: transcripción]
Tenga en cuenta que partsDo: es como un bucle con el objeto como variable de bucle. En la primera pasada, obtenemos el nombre y lo imprimimos en la transcripción (un espacio de trabajo especial en el entorno de programación Smalltalk). Luego, el segundo recorrido obtiene la edad y el tercer recorrido obtiene la dirección. También vale la pena señalar que "partsDo:" es un mensaje de palabra clave, con "aClient" como receptor y "[:object | object print on:transcript]" (un bloque) como parámetro.
Antes de profundizar, te daré la solución Smalltalk. Luego explico cómo funciona y doy más ejemplos idiomáticos.
Lo que debemos hacer es agregar el siguiente método al cliente:
PartNumber:aBlock
BlockValue:SelfName.
Un valor cerrado: la auto-edad.
Valor del bloque: dirección propia.
Para comprender este código, primero debemos darnos cuenta de que estos bloques son funciones anónimas. Para entender mejor de qué estoy hablando, imaginemos que queremos asignar una función a una variable, pero no llamarla. Lo escribiré en un estilo de sintaxis similar a C (sé exactamente cómo hacerlo en sintaxis C, pero no ayuda a explicar las ideas clave; así que no escribiré una sintaxis C estricta):
a = f(x ){ return x+1;}//Sintaxis estilo C
A := [:x | x+1] // Sintaxis de Smalltalk
Aquí la variable "a" se convierte en un objeto de función. f es una función y podemos llamarla mediante "f(10)" para obtener 11. Pero también podemos llamarlo haciendo "a(10)" porque el valor de A es una función. La ejecución de una función a través de la variable "a" no requiere información sobre su nombre original.
Así que en Smalltalk ni siquiera tenemos que preocuparnos por el nombre de la función. Podemos asignarlo fácilmente a cualquier variable y usarlo universalmente. En el ejemplo simple de la llamada a la función anterior, configuramos "un valor: 10" para que devuelva 11. Durante la ejecución, el parámetro
Por lo general, rara vez ejecutamos bloques directamente. En su lugar, lo escribimos como "partsDo:" y ocultamos el incómodo bloque "call" para proporcionar la función abstracta.
Mira más ejemplos. Supongamos que tenemos una clase de avión que mantiene una lista vinculada de pasajeros. Intentamos recorrer todos los niños del visitante (asumiendo que los menores de 12 años se definen como niños). El código para implementar esta función es el siguiente:
Un pasajero en un avión hace: [:persona |
La edad de la persona& lt= 12
ifTrue: [.hacer algo con alguien.]]
Un poco de abstracción ayudará a simplificar el código si necesitamos iterar sobre nodos secundarios en otros contextos. Todo lo que necesitamos hacer es implementar una abstracción llamada "kidsDo:" en la clase de avión (agregué los números de línea en el código como referencia):
1 Kids: A Stone
3. Los pasajeros del autoservicio son: [:persona |
4.
5.ifTrue: [aBlock value: person]]
Ajustamos el código de muestra para expresar la abstracción de la siguiente manera:
6. Un avión kidsDo: [: kid |
7...Haz algo con tus hijos..].
8. "Listo".
Puedes ver la línea 6 ¿Cómo funciona? Cuando aparece el mensaje "Kids:...", se llama al método "kidsDo:" en la línea 1. Luego, la variable "aBlock" en la línea 1 se vincula con "[:kid |"...do algo con los niños...]" (llamado temporalmente kid block). "do:" en la tercera línea de kidsDo: El método "recorrerá a todos los pasajeros. En la línea 5, el mensaje "valor:" se enviará a aBlock solo si la edad del pasajero no es superior a 12 años. Cuando se ejecuta el mensaje "valor:" con "persona" como parámetro , activará una llamada de función al bloque infantil, lo que hará que "niño" se vincule a "persona" y "...hacer algo con el niño..." al final del bloque, y el flujo de ejecución comienza desde "kidsDo: ". Regrese al bucle "Do:" (al final de la línea 5) y continúe procesando a otros niños de esta manera. Una vez finalizado el bucle, el flujo de ejecución regresa de la llamada al método "kidsDo:" en la línea 6, llegando a la línea 8.
En resumen, la línea 6 hace que las líneas 1 a 5 se ejecuten en un bucle, y las líneas 1 a 5 harán que se ejecute el bloque infantil (línea 7). general, block Proporciona la forma más sencilla de abstraer el flujo de control para Smalltalk.
También está inteligentemente diseñado para realizar declaraciones de retorno semánticas, y esta es la única forma de expresar dicha semántica. Permítanme ilustrar esto agregando un método al avión similar a las líneas 6 a 8:
10.findAnySickKid
11."Aquí, self también es un avión. "
12.self kidsDo: [:kid |
13.Kid Isk
14.ifTrue: [^kid]].
p>15. Cero "no hay personas enfermas"
Lee el código y estoy seguro de que no verás nada inusual. Este también es un circuito que pasa por todos los niños en el avión. Si encuentran un niño enfermo, regrese. Además, si se repite más, si no hay niños enfermos, el ciclo finaliza y vuelve a cero (un objeto especial que se detecta fácilmente). Entonces, ¿qué es digno de mención aquí? Bueno, hay tres aspectos destacados: el comienzo del método findAnySickKid en la línea 10, el comienzo de KidsDo en la línea 1 y el final del bloque infantil en las líneas 13 y 14. Al ejecutar "anAirplane findAnySickKid", se llama al método findanysckkid, luego se llama al método kidsDo: y luego se llama al método kid block. La ejecución de "^kid" en el bloque kid no regresa al remitente (método kidsDo), sino al remitente de findAnySickKid. No importa qué tan larga sea la cadena de mensajes desde ^kidsDo: hasta el interior del bloque kid, "kid" siempre se devuelve desde findAnySickKid. Perdón por mi ignorancia, nunca había escuchado el nombre de esta función. Yo personalmente lo llamo retorno por cortocircuito.