uqbar-project / wollok-language

Wollok language definition
GNU General Public License v3.0
7 stars 9 forks source link

toString vs kindName vs printString vs className vs shortDescription #140

Open asanzo opened 2 years ago

asanzo commented 2 years ago

Viene de acá.

Todos estos métodos están dando vuelta de alguna forma ú otra en Wollok, y todos toman un objeto y devuelven un string con alguna representación del objeto receptor

toString()

wollok-language

// class Object
/**
   * String representation of Wollok object
   */
  method toString() = self.kindName()

image image

wollok-xtext y wollok-ts-cli

image image

printString()

wollok-language

  // class Object
  /**
   * Provides a visual representation of Wollok Object
   * By default, same as toString but can be overridden
   * like in String
   */
  method printString() = self.toString()

wollok-xtext

image

wollok-ts-cli

image

shortDescription()

wollok-language

  // class Object
  /**
   * Shows a short, internal representation
   */
  method shortDescription() = self.toString()

Pero en Date es nativo:

  // class Date
  /** String representation of a date */
  override method toString() = self.shortDescription()
   /**
   * Shows a short, internal representation of a date
   * (the result varies depending on user's locale)
   *
   * Example:
   *     new Date(day = 2, month = 4, year = 2018).shortDescription()
   *         ==> Answers 2/4/2018
   */
  override method shortDescription() native

wollok-xtext y wollok-ts

image Para todos los objetos es igual que el toString, acá vemos la diferencia de implementación de natives.

kindName()

wollok-language

  // class Object
    /** Object description in english/spanish/... (depending on i18n configuration)
   *
   * Examples:
   *     "2".kindName()  => Answers "a String"
   *    2.kindName()    => Answers "a Integer"
   *
   * @private
   */
  method kindName() native

wollok-xtext y wollok-ts

image

className()

wollok-language

wollok-xtext y wollok-ts-cli

Problemas:

image

Posible solución

Propongo cambiar el lenguaje (wollok-language) de la siguiente forma:

nscarcella commented 2 years ago

+1 a toda la propuesta de @asanzo

Como cosa extra agrego tres ideas:

  1. Hace rato venimos hablando de agregar un package wollok.reflection o wollok.reflect y a mi me parece la opción superadora para lidiar con cosas como kindName. Podríamos armar una listita con todas esas cosas medio reflectivas que tienen los objetos en lang y meterlas como funciones de un/os objeto/s en reflection (O sea, básicamente implementar Mirrors). No tiene porqué hacerse de golpe, puede hacerse de a poco, pero eso permitiría tener (y de ser necesario, delegar en) estas operaciones sin que le aparezcan en el autocompletar al alumno.

  2. No estaría bueno que el método que se usa en todos los otros lenguajes y que el pibe seguro anticipa usar sea el que va a imprimir el mensaje "humanizado" y dejar el método más raro para los usos internos? No sé cual recomendar si toString o show o printString, pero creo que estaría bueno minimizar la sorpresa.

  3. En esa linea de pensamiento, estaría bueno considerar cosas cómo ¿Qué variante (humana/otra) debería usar un objeto cuando se lo concatena? Estoy tentado de pensar que la humana, pero, para los strings eso sería agregar las " y romperías todo. Lo que se me ocurre es que, tal vez no necesitamos una variante "no-humana" y alcanza con invertir el control en dónde haga falta. Por ejemplo, el append (que si no recuerdo mal es native), en lugar de delegar al toString, podría decidir él en base al parámetro que recibió: Si es un string, lo pone sin comillas y maneja cualquier otro caso raro y, el resto, lo delega al toString. En definitiva mi duda es si tenemos más usos para un mensaje human-unfriendly, porque parece medio anti-intuitivo y me pregunto si vale la pena.

npasserini commented 2 years ago

Estoy un poco oxidado con algunas cosas, pero de la excelente descripción de @asanzo a mí me surge esto, trato de relacionarlo con lo que propuso él.

  • [ ] En ese último sentido, podríamos dejar el method toString() = self.kindName() pero podemos hacer un method printString() native que se encargue de hacer que para un WKO diga su nombre, para un objeto no nombrado diga "un Objeto", y para el resto diga "un/a NombreDeClase".

Me hace ruido el toString = kindName, porque parecen referir a cosas distintas:

En algún default, el toString podría estar basado en el kindName, pero no ser igual. Por ejemplo el toString de new Perro() podría ser un/a Perro y su kindName sería Perro.

Siguiendo lo que dice el comentario de Nico sobre "minimizar la sorpresa", me gusta que toString sea la representación primaria de cualquier objeto. (Aunque esto tiene algunas sutilezas, porque posiblemente la "representación primaria" que se llama automáticamente no sea igual al método que vos podés -o deberías- redefinir. Por ejemplo en Pharo esto es distinto.)

  • [ ] Que printString sea lo que se espera en consola para imprimir objetos retornados, que es el redefinible. No sé en qué lugares ameritaría además reemplazar toString por printString. Tunearía las documentaciones de ambos métodos para saber cuál usar y cuál redefinir en cada caso. En particular, me gusta la idea de que "toString" tenga la semántica "convertir a string" y que "printString" tenga la semántica "mostrale a une humane este objeto".

Personalmente no veo muchos casos en los que printString sea distinto de toString, salvo justamente los strings. De todas maneras, lo mantendría al menos por ese caso y debería usarse en todos lados que mostremos un objeto en las herramientas para el programador:

El toString básicamente sirve cuando uno concatena strings dentro de su código de negocio. ¿Es así?

  • [ ] Borrar toda referencia a className. Que en todos los lugares donde se use className se reemplace por kindName. (me gusta más "kindName" para el nombre de un método que me dice tu identificador estático, porque podés no tener una clase, como en el caso de un WKO o un objeto no nombrado).

Comparto, pero posiblemente como un paso intermedio apuntando a la idea de @nscarcella (y antes @javierfernandes) de incorporar Mirrors.

  • [ ] ¿Agregar quizás una validación con warning de no usar kindName? Que diga lo mismo que la de igualar por objeto: "quizás querés usar polimorfismo".

Qué se yo, esto me lleva a discusiones filosóficas sobre los warnings... yo quisiera poder programar sin warnings y entonces si siempre que usás el kindName tira warning... entonces para qué está? El código interno de Wollok si yo lo abro tira warnings? Tal vez si lo pudieras tunear y chequear que no se use dentro de un if entonces sí, a full.

  • [ ] Borrar toda referencia a shortDescription (prefiero) o bien que la documentación de shortDescription agregue algo como "e.g. for small places like object diagrams" (Y que el worksheet de wollok-run-client envíe ese mensaje para mostrar su diagrama).

De lo anterior, yo no entendí por qué sería necesario. El diagrama debería usar lo mismo que la consola. Tal vez es una herencia de cuando los toStrings eran más complejos.

En algunos lugares define toString como shortDescription y en otros shortDescription como toString eso no puede ser saludablew.

No entendí lo de shortDescription en Collection.

  • [ ] La forma de imprimir un Date podría usar formato ISO (YYYY-MM-DD) (cambiar el contrato de su toString) para no tener quilombos de locale.

Puede ser pero sería otra discusión, no sigamos agregando problemas acá.

npasserini commented 2 years ago
  1. Hace rato venimos hablando de agregar un package wollok.reflection o wollok.reflect y a mi me parece la opción superadora para lidiar con cosas como kindName. Podríamos armar una listita con todas esas cosas medio reflectivas que tienen los objetos en lang y meterlas como funciones de un/os objeto/s en reflection (O sea, básicamente implementar Mirrors). No tiene porqué hacerse de golpe, puede hacerse de a poco, pero eso permitiría tener (y de ser necesario, delegar en) estas operaciones sin que le aparezcan en el autocompletar al alumno.

+1 a todo eso.

  1. No estaría bueno que el método que se usa en todos los otros lenguajes y que el pibe seguro anticipa usar sea el que va a imprimir el mensaje "humanizado" y dejar el método más raro para los usos internos? No sé cual recomendar si toString o show o printString, pero creo que estaría bueno minimizar la sorpresa.

+1 también y voto que sea toString, antes propuse cómo combinar toString y printString.

  1. En esa linea de pensamiento, estaría bueno considerar cosas cómo ¿Qué variante (humana/otra) debería usar un objeto cuando se lo concatena? Estoy tentado de pensar que la humana, pero, para los strings eso sería agregar las " y romperías todo. Lo que se me ocurre es que, tal vez no necesitamos una variante "no-humana" y alcanza con invertir el control en dónde haga falta. Por ejemplo, el append (que si no recuerdo mal es native), en lugar de delegar al toString, podría decidir él en base al parámetro que recibió: Si es un string, lo pone sin comillas y maneja cualquier otro caso raro y, el resto, lo delega al toString. En definitiva mi duda es si tenemos más usos para un mensaje human-unfriendly, porque parece medio anti-intuitivo y me pregunto si vale la pena.

Mi idea es que para la concatenación existe el toString. Yo pensaba sólo dos representaciones: printString y toString (además de kindName que es otra cosa). ¿Necesitamos otra representación más? No lo tengo claro.

Igualmente, para mí a esto le falta un detalle técnico para el manejo de errores, ya lo mencionó @asanzo, intento elaborar sobre eso.

¿Qué pasa si el toString tira error?

Bueno, hay lugares en los que eso no es aceptable, en particular cuando estás tratando de mostrar otro error, dentro de un stack trace por ejemplo, no querés otra excepción. Si el toString se lo dejás reescribir a un estudiante que está aprendiendo... es lógico prever errores.

La opción que planteaba @asanzo entiendo pasa por no usar ese toString, que es redefinible sino otro que no se pueda redefinir (bueno, eso no es exactamente posbible en Wollok, pero es uno que no se debe redefinir). Eso agrega una tercera definición, la llamaré baseToString. Con este agregado, básicamente todas las definiciones default del lenguaje pasan a baseToString y method toString() = self.baseToString().

Hasta aquí entiendo sería seguir la lógica de Alf. Esa lógica implica que en un stack trace yo no puedo llamar al toString, al que considero falible, y en cambio llamaría al baseToString, que viene con el lenguaje y se considera infalible. Esta estrategia no me gusta porque impide que yo vea mis representaciones piolas descriptivas de mis objetos en esos lugares... y en todo caso puede pasar en cualquier lado porque el diagrama de objetos tampoco quiero que se rompa, ni la consola... el toString aparece en muchos lugares delicados, si lo vas a excluir de donde no se puede romper tal vez pierde mucha de su utilidad.

Por eso, en cambio, pienso que deberíamos agregar todavía una representación más (sic), a la que llamaré safeToString() y tiene como primera propuesta de implementación esto:

method safeToString() {
   try { return self.toString() }
   catch (...) {
       // .... pensar cómo informar el error en algún lado
       return self.baseToString()
   }
}

De esta manera te evitás la posibilidad del error pero sin perder la posibilidad de tener mensajes "lindos".

Por último, la pregunta en este caso sería cómo queda el printString, que habíamos dicho de definirlo = toString... tal vez es tan fácil como modificarlo a = self.safeToString()?

nscarcella commented 2 years ago

@npasserini No me quedó claro quién llamaría a ese safeToString en lugar del toString habitual. Por otro lado, no estoy seguro de si vale la pena agregar un safeToString que no podemos forzar a que no sobreescriban, sobre todo si es para usarlo en un lugar muy concreto. Yo ahí haría la inversión de control que propuse arriba: Que el toString de los errores sea un native y metemos ese try-catch ahí. Eso hace que el método no sea redefinible (porque no existe).

npasserini commented 2 years ago

Por lo que yo veo el único usuario directo sería el concat, tal vez se me esté escapando alguno. Pero la idea es que todo lo demás use printString que delega en safeTo String, por lo que básicamente todo el código interno del lenguaje y/o IDE termina usándolo.

Esto arma una cadena que es: printString => safeToString => toString => baseToString.

Todo esto es casi desconocido para los usuarios:

On Thu, Jun 30, 2022 at 12:24 AM Nicolás Scarcella @.***> wrote:

@npasserini https://github.com/npasserini No me quedó claro quién llamaría a ese safeToString en lugar del toString habitual. Por otro lado, no estoy seguro de si vale la pena agregar un safeToString que no podemos forzar a que no sobreescriban, sobre todo si es para usarlo en un lugar muy concreto. Yo ahí haría la inversión de control que propuse arriba: Que el toString de los errores sea un native y metemos ese try-catch ahí. Eso hace que el método no sea redefinible (porque no existe).

— Reply to this email directly, view it on GitHub https://github.com/uqbar-project/wollok-language/issues/140#issuecomment-1170707540, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABDLKOKSCAIIBGHEY6LGDWDVRUHPRANCNFSM52GCN2HQ . You are receiving this because you were mentioned.Message ID: @.***>