Generics in Scala

0

Good morning

I was trying to apply a function on a figure to return a new figure but with the position in x and y changed for this I define the figure as:

* To use pattern matching:

    case class Circulo(x:Int, y: Int, radio: Double) extends Figura
    case class Rectangulo(x:Int, y: Int, base: Int, altura: Int) 
    extends Figura
    trait Figura[T] {
      val x: Int 
      val y: Int 
    }
  • The operation to move is defined in an engine class that for now has no more functionality than this:

    object Motor {
    
      def trasladar(x: Int, y: Int) = (figura: Figura)  => figura 
      match {
        case Circulo(xPos, yPos, radio) => Circulo(xPos+x, yPos+y, 
        radio)
        case Rectangulo(xPos, yPos, base, altura) => 
        Rectangulo(xPos+x, yPos+y, base, altura)
      }
    }
    
  • It seems to work the theme is that when I run the test, for example:

    "Un Motor" should "trasladar un Circulo" in {
    
        val trasladar = Motor.trasladar(2,-1)
        val circuloBase = Circulo(1,1,43)
        val circulo = trasladar(circuloBase)
    
        assert(circulo.x === 3)
        assert(circulo.y === 0)
        assert(circuloBase !== circulo)
        assert(circulo.radio === 43) 
       }
    
  • I can not ask about the radius of the circle, the same thing happens to me with other figures as a rectangle, when I ask their base, etc. They told me that I had to use generics but I still do not understand how to use them and I get typo errors everywhere, so how could I use generics to fix this? Any change in the design of the problem for better is welcome Greetings

asked by nicolastpi01 10.12.2017 в 02:40
source

1 answer

1

You have to explain a few things before arriving at a possible solution, although I have already told you that it is not as simple as using Generics , although it has something to do with types .

Let's see first why it fails, then a solution following the "Object Oriented Programming" and, finally, a solution with "Types Classes" using implicit .

Why it fails

Before a scala code, the question that needs to be asked is whether the compiler has all the necessary information to infer the type of all the objects. Sometimes, when an ambiguity of types occurs, the compiler uses the rule of using the most specific type (when there is contravariance ); in others it uses the most general type (when there is covariance ).

In your case, the function Motor.trasladar has a signature Figura => Figura since it has to serve equal to Circulos that for Rectangulos , and Figura is the general type that covers both.

When you apply Motor.trasladar to circuloBase you then get a Figura , not a Circulo , and hence fail. In order to obtain your radio attribute you will have to convert it first to Circulo :

val trasladar = Motor.trasladar(2,-1)
val circuloBase = Circulo(1,1,43)
val circulo = trasladar(circuloBase).asInstanceOf[Circulo]

It is not a good solution since the "conversion" of types is carried out in execution, with the consequent risk of errors. We have to look for a better solution.

Object Orientation

The most common way to solve the problem would be to refactor the classes to contain the method:

trait Figura {
  val x: Int
  val y: Int
  def trasladar(x: Int, y: Int): Figura
}

case class Circulo(x: Int, y: Int, radio: Double) extends Figura {
  def trasladar(x: Int, y: Int) = this.copy(x = this.x + x, y = this.y + y)
}

case class Rectangulo(x: Int, y: Int, base: Int, altura: Int) extends Figura {
  def trasladar(x: Int, y: Int) = this.copy(x = this.x + x, y = this.y + y)
}

To use it:

val circuloBase = Circulo(1, 1, 43)
val circulo = circuloBase.trasladar(2, -1)

I guess the drawback is that you want to create more operations on these objects, but without modifying the model. Let's say you have sources you do not want to touch. Then this solution is not feasible. Let's see something else ...

Generic types

Let's see if we can use generic types:

def trasladar[T <: Figura](x: Int, y: Int) = (figura: T)  =>
  figura match {
    case Circulo(xPos, yPos, radio) => Circulo(xPos+x, yPos+y, radio)
    case Rectangulo(xPos, yPos, base, altura) => Rectangulo(xPos+x, yPos+y, base, altura)
  }

In this parameterization of types we indicate that the type T must be a subtype of Figura , indicated by T <: Figura . The transformation trasladar converts an object of type T into another type T .

We can think that it will go well, because when applied to an object Circulo , T will be equal to Circulo and then return a Circulo , and the same when applied to Rectangulo .

The problem is that the compiler does not know to what the function will be applied, so it can not relate T with some of the subtypes of Figura .

It is possible that in new implementations of scala (eg: with dotty ) have compound types that could solve this problem. But today it is clear that the best way is using typeclasses with implicit, and increasingly will be present in scala bookstores.

Type classes (typeclass)

The typeclass has no relation to the "Object Classes" that you see in object-oriented programming. They come from the Theory of Categories that they have known so well implement in haskell and that already extends to other functional languages such as scala.

Let's say we want to define a series of operations and then implement them on different types of data. The set of these operations would be our "type class" ( typeclass ):

trait Transform[T <: Figura] {
  def trasladar(x: Int, y: Int): T => T
}

We only have one operation, but we could define all that we need, or even add more typeclasses with other sets of operations.

We define the transformations for each of the types we have (what is called "create instances of the typeclass" ):

object TransformCirculos extends Transform[Circulo] {
  def trasladar(x: Int, y: Int) =
    circulo => circulo.copy(x = x + circulo.x, y = y + circulo.y)
}

object TransformRectangulos extends Transform[Rectangulo] {
  def trasladar(x: Int, y: Int) =
    rect => rect.copy(x = x + rect.x, y = y + rect.y)
}

With the function to move in this way:

def trasladar[T <: Figura](x: Int, y: Int)(figura: T)(tr: Transform[T]) =
  tr.trasladar(x, y)(figura)

The inconvenience is that every time we do a trasladar we will have to explicitly pass the corresponding Transform object:

val trasladar = Motor.trasladar(2,-1)(_: Circulo)(Motor.TransformCirculos)

We can avoid this if we make the Transform objects implicit, as well as the argument of the function:

implicit object TransformCirculos extends Transform[Circulo] {
  def trasladar(x: Int, y: Int) =
    circulo => circulo.copy(x = x + circulo.x, y = y + circulo.y)
}

implicit object TransformRectangulos extends Transform[Rectangulo] {
  def trasladar(x: Int, y: Int) =
    rect => rect.copy(x = x + rect.x, y = y + rect.y)
}

def trasladar[T <: Figura](x: Int, y: Int)(figura: T)(implicit tr: Transform[T]) =
  tr.trasladar(x, y)(figura)

In this way, we would not have to specify the transformation object as long as the trasladar function finds it within its environment:

val trasladar = Motor.trasladar(2,-1)(_: Circulo)

Still, we could not do this:

val trasladar = Motor.trasladar(2,-1)

since the compiler does not have the necessary information about the types of data to which it will be applied. On the other hand, the alternative of specifying the type of data is possible:

val trasladar = Motor.trasladar[Circulo](2,-1) _

Observe the underline _ at the end indicating to the compiler that we want to operate with the function and do not give an error by taking it as a method.

End

Finally, I put the complete code to make it clearer:

trait Figura {
  val x: Int
  val y: Int
}

case class Circulo(x: Int, y: Int, radio: Double) extends Figura
case class Rectangulo(x: Int, y: Int, base: Int, altura: Int) extends Figura

object FiguraMain extends App {

  object Motor {

    trait Transform[T <: Figura] {
      def trasladar(x: Int, y: Int): T => T
    }

    implicit object TransformCirculos extends Transform[Circulo] {
      def trasladar(x: Int, y: Int) =
        circulo => circulo.copy(x = x + circulo.x, y = y + circulo.y)
    }

    implicit object TransformRectangulos extends Transform[Rectangulo] {
      def trasladar(x: Int, y: Int) =
        rect => rect.copy(x = x + rect.x, y = y + rect.y)
    }

    def trasladar[T <: Figura](x: Int, y: Int)(figura: T)(implicit tr: Transform[T]) =
      tr.trasladar(x, y)(figura)

  }

  def main = {

    val trasladar = Motor.trasladar[Circulo](2, -1) _
    val circuloBase = Circulo(1, 1, 43)
    val circulo = trasladar(circuloBase)

    assert(circulo.x == 3)
    assert(circulo.y == 0)
    assert(circuloBase != circulo)
    assert(circulo.radio == 43)
  }
}
    
answered by 12.12.2017 / 19:26
source