tres trayectoria transversal tipos termodinamicas termodinamica son sincronizadores salida que programacion potencia partes mecanica listas lineal las imprimir funciones funcionamiento funcion extensivas estado entrada electronica ejes ejemplos cuales corriente clase cambios caja amplificadores amplificador scala types shapeless

scala - trayectoria - variables termodinamicas pdf



Cómo definir una función cuyo tipo de salida depende del tipo de entrada (2)

Dadas las siguientes clases:

case class AddRequest(x: Int, y: Int) case class AddResponse(sum: Int) case class ToUppercaseRequest(str: String) case class ToUppercaseResponse(upper: String)

¿Cómo defino de una manera segura para algunos tipos alguna función?

def process(req: ???): ???

Tal que lo siguiente debería ser cierto:

val r1: AddResponse = process(AddRequest(2, 3)) val r2: ToUppercaseResponse = process(ToUppercaseRequest("aaa"))

Además, lo siguiente no debe compilar:

val r3 = process("somestring")


Esto es totalmente posible y una cosa totalmente razonable para hacer en Scala. Este tipo de cosas está en todas partes de Shapeless, por ejemplo, y algo similar (pero menos de principios) es la base del patrón magnético que aparece en Spray, etc.

Actualización: tenga en cuenta que la siguiente solución supone que "dadas las siguientes clases" significa que no desea tocar las clases de casos en sí. Si no le importa, vea la segunda parte de la respuesta a continuación.

Desearía una clase de tipo que asigne tipos de entrada a tipos de salida:

case class AddRequest(x: Int, y: Int) case class AddResponse(sum: Int) case class ToUppercaseRequest(str: String) case class ToUppercaseResponse(upper: String) trait Processable[In] { type Out def apply(in: In): Out }

Y luego algunas instancias de clase de tipo:

object Processable { type Aux[I, O] = Processable[I] { type Out = O } implicit val toUppercase: Aux[ToUppercaseRequest, ToUppercaseResponse] = new Processable[ToUppercaseRequest] { type Out = ToUppercaseResponse def apply(in: ToUppercaseRequest): ToUppercaseResponse = ToUppercaseResponse(in.str.toUpperCase) } implicit val add: Aux[AddRequest, AddResponse] = new Processable[AddRequest] { type Out = AddResponse def apply(in: AddRequest): AddResponse = AddResponse(in.x + in.y) } }

Y ahora puedes definir el process usando esta clase de tipo:

def process[I](in: I)(implicit p: Processable[I]): p.Out = p(in)

Que funciona como se desea (tenga en cuenta los tipos estáticos apropiados):

scala> val res: ToUppercaseResponse = process(ToUppercaseRequest("foo")) res: ToUppercaseResponse = ToUppercaseResponse(FOO) scala> val res: AddResponse = process(AddRequest(0, 1)) res: AddResponse = AddResponse(1)

Pero no funciona en tipos arbitrarios:

scala> process("whatever") <console>:14: error: could not find implicit value for parameter p: Processable[String] process("whatever") ^

Ni siquiera tiene que usar un tipo dependiente de la ruta (debería tener solo dos parámetros de tipo en la clase de tipo), pero hace que el process un poco más agradable si, por ejemplo, tiene que proporcionar el parámetro de tipo explícitamente.

Actualización: todo lo anterior supone que no desea cambiar las firmas de su clase de caso (lo que definitivamente no es necesario). Sin embargo, si está dispuesto a cambiarlos, puede hacerlo de forma un poco más concisa:

trait Input[Out] { def computed: Out } case class AddRequest(x: Int, y: Int) extends Input[AddResponse] { def computed: AddResponse = AddResponse(x + y) } case class AddResponse(sum: Int) case class ToUppercaseRequest(str: String) extends Input[ToUppercaseResponse] { def computed: ToUppercaseResponse = ToUppercaseResponse(str.toUpperCase) } case class ToUppercaseResponse(upper: String) def process[O](in: Input[O]): O = in.computed

Y entonces:

scala> process(AddRequest(0, 1)) res9: AddResponse = AddResponse(1) scala> process(ToUppercaseRequest("foo")) res10: ToUppercaseResponse = ToUppercaseResponse(FOO)

El tipo de polimorfismo (paramétrico o ad-hoc) que prefiera depende completamente de usted. Si desea poder describir una asignación entre tipos arbitrarios, use una clase de tipo. Si no le importa, o si no desea activamente que esta operación esté disponible para tipos arbitrarios, utilice subtipos.


Puede definir un rasgo común para solicitudes y un rasgo común para respuestas donde el tipo de solicitud se define para un tipo de respuesta específico:

trait Request[R <: Response] trait Response case class AddRequest(x: Int, y: Int) extends Request[AddResponse] case class AddResponse(sum: Int) extends Response case class ToUppercaseRequest(str: String) extends Request[ToUppercaseResponse] case class ToUppercaseResponse(upper: String) extends Response Response[ToUppercaseRequest]

Entonces, la firma del process sería:

def process[A <: Request[B], B <: Response](req: A): B

Cuando llame al process , tendrá que definir explícitamente los tipos para que el tipo devuelto sea lo que espera que sea, no se puede inferir lo suficientemente específicamente:

val r1: AddResponse = process[AddRequest, AddResponse](AddRequest(2, 3)) val r2: ToUppercaseResponse = process[ToUppercaseRequest, ToUppercaseResponse](ToUppercaseRequest("aaa"))