jueves, 25 de junio de 2009

Objetos que devuelven más de un valor en GeneXus

En GeneXus no tenemos el concepto de función como tenemos en Java o en C#, donde tiene que haber un tipo de datos que es el que devuelve, como en:
private int foo() { return 1; }
Por el contrario, en GeneXus los parámetros de un objeto pueden ser de entrada, salida o entrada/salida, en cualquier cantidad y orden. (*)

Por ejemplo, podría tener una función que dado un producto me devuelve el saldo en cantidad y en importe, de dicho producto en el depósito. (**)

La regla parm sería por ejemplo:
parm(in:ProductoId, out:&SaldoCantidad, out:&SaldoImporte);
Pero entonces, ¿cómo invoco ese programa?. Hay varias opciones, puede ser con call, o incluso con udp...
call(PGetSaldoProducto, ProductoId, &SaldoCantidad, &SaldoImporte)
o
&SaldoImporte = udp(PGetSaldoProducto, ProductoId, &SaldoCantidad)
En una nota hace un tiempo, hablando sobre la legibilidad del código, comentaba sobre la ventaja de usar udp en vez de call... Pero en este caso las dos opciones son malas.

Creo que sería bueno tener una sintaxis como tiene por ejemplo Python para asignar listas de valores... En Python una lista separada por comas es una tupla, que se puede devolver en una función o asignar. Por ejemplo:
# defino la funcion
def foo():
    a = 1
    b = 2
    c = 3
    return a,b,c

# la invoco
d, e, f = foo()   # resultado: d=1, e=2, f=3
En GeneXus, si tuvieramos la posibilidad de usar esta notación, podríamos escribir:
&SaldoImporte, &SaldoCantidad = udp(PGetSaldoProducto, ProductoId)
lo que deja bien claro que la intención de la invocación es cargar el valor de las dos variables de la izquierda...

---------------------------------------
(*) Si bien los parámetros pueden tener cualquier orden, creo que es altamente recomendable dejar los parámetros de salida al final.

(**) Los motivos para tener una función que devuelve más de un valor pueden ser muchos y muy variados. En este caso también se podrían hacer dos funciones, con lógica muy parecida, pero que devolviera una la cantidad y otra el importe. La ventaja de tenerlo así es que se optimiza la performance, porque hay que hacer la recorrida en la base de datos una sola vez.

5 comentarios:

  1. También, como alternativa con respecto a (**) podrías retornar un SDT que tenga ambos valores, donde vuelve a tener sentido la udp. Entonces, tu retorno es el saldo del producto, el cuál está compuesto por cantidad e importe.

    ResponderEliminar
  2. GML: Sí, pero entonces debería tener un montón de SDTs que usaría solo para devolver valores... No se, no me convence demasiado esa opción.

    ResponderEliminar
  3. La verdad es que prefiero usar el call que usar un llamado como en el ejemplo de Python... Me parece mucho más natural.

    ResponderEliminar
  4. Una alternativa, que puede servir en muchos casos es hacerlo así:

    &SaldoImporte = Find(ProductoSaldo, ProductoId = &Id)

    &SaldoCantidad = Find(ProductoCantidad, ProductoId = &Id)

    A partir de GX X, ambos FINDs intentarán resolverse en un único select.

    Ventajas:
    - evitas el Proc y por ende queda mas clara la intención.
    - no pierdes la optimización de recuperar todo en un único select.

    saludos,
    Pablo Mazzilli

    ResponderEliminar
  5. Pablo: Ese caso que planteas es demasiado simple :) Te puedo asegurar que tengo ejemplos que no se resuelven con un solo select...

    Igual está claro que en GeneXus X muchas veces no es necesario hacer un procedimiento si se puede usar una fórmula.

    ResponderEliminar