Seaside - Intro a Smalltalk
En esta clase vamos a ver un enfoque bastante radical o diferente a los que veníamos viendo para la construcción de aplicaciones web. Para eso vamos a cambiar muchas cosas:
- el lenguaje va a ser Smalltalk
- el IDE ya no será Eclipse, sino que vamos a trabajar con Pharo, que también es Open Source. La integración entre Pharo y Smalltalk es muy fuerte, de hecho el lenguaje se refunda con el IDE en el año 1981 (cuando salió el Smalltalk '80)
- en lugar de Tomcat usaremos un Web Server para Smalltalk llamado Comanche (que curiosamente también es de Apache, viene junto con el IDE)
- el framework será Seaside, y resuelve cosas en forma diferente a lo que venimos acostumbrados (incluso comparado a Wicket)
Empecemos entonces por hacernos amigos de la tecnología de a poquito
El IDE
Existen muchos IDEs para trabajar con Smalltalk
- Dolphin, de Object-Arts, que funcionaba sólo para Windows y quedó discontinuado
- Squeak, el proyecto que retomaron los creadores del Smalltalk-80 y que fue sponsoreado por Disney durante cinco años, multiplataforma, y que dio origen a
- Visual Age de IBM
- Visual Smalltalk de Cincom
- GNU Smalltalk
- Smalltalk/X
Nosotros nos quedamos con Pharo, porque es el IDE que más actividad académica y comercial tiene: es un software open-source que está en continuo movimiento.
¿Qué tiene un IDE de Smalltalk?
El entorno de trabajo de Smalltalk es diferente de lo que estamos acostumbrados:
- Tenemos algunas herramientas importantes para conocer:
- el System Browser o Class Browser: donde vemos las clases agrupadas en categorías
- las ventanas Workspace: nos permite jugar un poco con los objetos, instanciarlos y enviarle mensajes para ver cómo se comportan. Esos objetos viven mientras el Workspace permanezca abierto.
- el Test Runner: Smalltalk viene con SUnit, que es la implementación smalltalkera de JUnit para automatizar tests
- el Monticello Browser: el versionador que viene con Pharo
- el Transcript: si lo necesitamos, es nuestro system console, pero esperemos que no haga falta
- una ventana llamada Seaside Control Panel que permite controlar el webserver embebido (el Comanche)
- No hay diferencia entre "momento de desarrollo" y "momento de ejecución", los objetos no se construyen como si fuera una demo, siempre están disponibles. Esto representa una ventaja: se puede construir un juego de datos y enviar mensajes a objetos sin tener que pasar del estado "programando" al estado "probando".
- Todos los objetos (incluyendo las ventanas del System Browser donde desarrollamos el comportamiento de nuestras clases) viven en un ambiente, esto ya lo sabemos. El ambiente de Smalltalk es un archivo que llamamos imagen. Por eso es importante Grabar la imagen cada tanto para no perder lo que vinimos haciendo si hay un corte de luz... al volver vemos que en el mismo lugar donde dejamos las ventanas aparecen.
Smalltalk
Smalltalk es un lenguaje muy sencillo de aprender. Sólo existen estas construcciones:
- todo objeto se maneja con referencias que llamamos variables, cuando quiero enviar un mensaje a un objeto lo hago siguiendo la sintaxis
objeto mensajeUnario
objeto mensajeBinario parametro
objeto mensajeDePalabraClave: parametro1
objeto mensajeDeUnParametro: parametro1 conOtroParametro: parametro2 (también es de palabra clave)
Es decir que cuando tengo que enviar un mensaje con muchos parámetros los voy separando con un prefijo que construye el nombre del método
mensajeDeUnParametro:conOtroParametro:
es el nombre del método (o selector para los smalltalkeros)
Abrimos un workspace y probamos algunas cosas:
3 + 5
con el botón derecho hacemos Print It y eso nos devuelve por suerte... 8. Enviamos un mensaje binario al objeto 3.
Print It implica que lo que devuelve un mensaje no tiene efecto colateral, lo que nos importa es ver qué devuelve.
Do It por el contrario no muestra lo que devuelve un mensaje, porque lo importante es afectar al objeto al que le enviamos el mensaje.
Seguimos jugando con números:
9 sqrt
Esto envía al objeto 9 el mensaje sqrt (square root, raíz cuadrada). Para ver cómo resuelve un mensaje, podemos entrar en la clase SmallInteger, o bien hacer desde el mismo workspace:
9 browse
Esto nos deja parados en el System Browser en la clase del objeto receptor.
Enviamos un mensaje de palabra clave:
9 radix: 2
Y eso pasa el número 9 a un String que representa el número en base 2.
Si vemos cómo se implementa un método, entenderemos que Smalltalk es bastante simple en su sintaxis:
- el circunflejo (^) se usa cuando queremos devolver un objeto en un método
- := (dos puntos + igual) es la asignación destructiva, eso pisa la referencia de un objeto a otro
- el this de java se llama self en Smalltalk, el super de Java en Smalltalk también se llama super
- un método en un objeto se escribe:
sumaleDosA: unNumero
" en el comentario ponemos lo que hace un método "
| variableLocal |
variableLocal := 2 + unNumero.
^variableLocal
Y eso es todo.
Decisiones que tomaron quienes desarrollaron el Smalltalk:
- En Smalltalk todo es un objeto
- no hay tipos primitivos
- hay un true y un false en el ambiente, son objetos booleanos (no existen los Booleans y los booleans)
- lo mismo los números (6, 9.0, 1 / 3, 8@3 que es un punto)
- y los strings que se demarcan con comillas simples: 'hola mundo', 'Pepe'
- también las clases
- los bloques, demarcados con corchetes, permiten definir porciones de código a ejecutar, sin parámetros: [ 6 + 8 ], [ 'hola' reverse ], o con parámetros: [ :numero | numero factorial ], [ :sumando1 :sumando2 | sumando1 + sumando2 ]. Entonces el comportamiento de un objeto se modela con objetos. Esto le da capacidades dinámicas al lenguaje como veremos en breve.
- no hay tipos primitivos
- Smalltalk no tiene chequeo estático de tipos, las variables referencian a objetos, entonces
- cuando envío un mensaje a un objeto en ese mismo momento se enlaza el operando con el operador
- esto por un lado da cierta flexibilidad, por otro lado no es posible encontrar errores de tipo en tiempo de compilación
- No hay selección simple/múltiple (if) ni iteración (while, for) como sintaxis del lenguaje, eso se logra siempre enviando mensajes a objetos
- en el caso del if, se envía un mensaje a un booleano
Implementando un if polimórfico en Smalltalk
Jugamos con los booleanos
En Smalltalk tenemos la clase abstracta Boolean, de la cual heredan las subclases True y False.
true es el literal asociado al único objeto de la clase True, al igual que false que se asocia al único objeto en la imagen para la clase False.
¿Qué podemos hacer con un boolean?
Podemos enviar un mensaje unario, para obtener el contrario:
false not
Lo cual devuelve true. También podemos evaluar dos booleanos, con and/or:
false & true
true | false
Lo cual devuelve false y true respectivamente.
Si queremos ver la implementación de estos métodos: vamos al System Browser, botón derecho Find class (f): Boolean
Objetos bloque
Por otra parte, existen los objetos bloque, que permiten definir comportamiento:
programitaFactorial5 := [ 5 factorial ]
Esta vez hago botón derecho > DoIt, porque no me interesa mucho qué devuelve [ 5 factorial ], lo que me interesa es que la referencia a programitaFactorial5 apunte hacia ese objeto bloque.
¿Cómo puedo evaluar el bloque?
Enviando un mensaje, claro:
programitaFactorial5 value
Esto produce un resultado que quiero ver, entonces uso Print It: recibo un 120. Todo bien.
¿No se podrá dejar el 5 como parámetro? Sí, construyendo otro objeto bloque:
programitaFactorial := [ :numero | numero factorial ]
Pero ahora no puedo enviar el mensaje value, intentemos por las dudas:
programitaFactorial value.
Nos da error: 'This block accepts 1 argument, but was called with 0 arguments.'
Pero sí puedo hacer:
programitaFactorial value: 6.
Que da, como todos sabemos 720.
Uniendo los puntos
Ahora volvamos a Boolean, hay un mensaje de palabra clave ifTrue: ifFalse: que entienden tanto true como false, y que permite resolver un if.
Supongan que tenemos una condición booleana que no sabemos a priori si se puede cumplir o no. ¿Podríamos tratar a False y a True como objetos polimórficos, mandándole dos códigos:
- el primero que debería ejecutarse si la condición se cumple
- el segundo que debería ejecutarse si la condición no se cumple?
Entonces,
(3 > 7) ifTrue: [ 'hola' ] ifFalse: [ 'chau' ]
(8 < 17) ifTrue: [ 'hola' ] ifFalse: [ 'chau' ]
Si los objetos true y false son polimórficos, entonces esto es bastante fácil de lograr:
- si soy true, me importa el bloque por verdadero
- si soy false, me importa el bloque por falso
El método ifTrue:ifFalse: en True evalúa el primer bloque (alternativa por true) y descarta el segundo.
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
^trueAlternativeBlock value
El método ifTrue:ifFalse: en False evalúa el segundo bloque (alternativa por false) y descarta el primer bloque.
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
^falseAlternativeBlock value
Todo tiene que ver con todo
Tener objetos bloque es una abstracción muy interesante:
- el for se puede reemplazar por enviar un mensaje a una colección, como vemos en este ejemplo que filtra los nombres de más de 3 letras:
nombres := OrderedCollection with: 'Dalila' with: 'Paula' with: 'Joe'.
nombres select: [ :nombre | nombre size > 3 ]
- relacionado con el pattern Command
- si el comportamiento es un objeto, puedo modificar dinámicamente el comportamiento de un objeto reemplazando un bloque (command) por otro
- puedo crear un command para ejecutarlo en otro momento
- relacionado con UI: al crear un link o un botón (en general, cualquier widget que sirva para eventos de usuario) por lo general sabemos qué comportamiento tiene asociado (callback). Los objetos bloque nos ayudan a definir ese comportamiento.