¿Cómo puedo crear subtipos de Int en Scala?
¿Cómo puedo crear varios tipos que son esencialmente Ints, es decir, puede obtener el valor int, puede utilizar operadores de matemáticas como + BUT donde las instancias de diferentes tipos no pueden ser mezcladas.
Por ejemplo:
val density1 = new Density(100)
val density2 = new Density(200)
density1 + density2 should be(new Density(300))
val variability = new Variability(1)
variability.value should be(1)
density1 + variability // does not compile
Podría haber cientos de estos tipos y no quiero tener que implementar operadores como + en cada clase de hojas.
Idealmente, quisiera evitar todos los mecanismos implícitos de conversión (sólo preferencia personal). Los tipos adicionales no deben requerir alterar los tipos existentes.
Pregunta hecha hace 3 años, 5 meses, 6 días - Por techtrailblazer
4 Respuestas:
-
Aquí hay una solución en Scala 3 que no creo que use boxing/unboxing:
object Wrappers: opaque type Wrapper = Int extension[T <: Wrapper](t: T)(using util.NotGiven[T =:= Wrapper]): def +(other: T): T = (t + other).asInstanceOf[T] //other methods opaque type Density <: Wrapper = Int def Density(i: Int): Density = i opaque type Variability <: Wrapper = Int def Variability(i: Int): Variability = i
Pruebas:
val density1 = Density(1) val density2 = Density(2) val density3: Density = density1 + density2 //compiles val check1: Variability = density1 + density2 //doesn't compile val variability = Variability(1) val check2 = (variability: Wrapper) + density2 //doesn't compile val check3 = variability + density2 //doesn't compile println(density1) //1 println(density2) //2 println(density3) //3
El
asInstanceOf
está descontrolado y no debe afectar el rendimiento. Este diseño debería también guardar elInt
s de ser boxeado, pero no puedo garantizar eso, y también depende de cómo usted utiliza esto. Otra cosa agradable sobre esto es que cada nuevo tipo requiere sólo 2 líneas de código más. Y para facilitar la adición de nuevos métodos, probablemente también puede hacer un nuevo método propio para acortarasInstanceOf[T]
.Respondida el Dec 17, 2020 a las 14:14 - por codechampionx
Votos positivos: 0 | Votos negativos: 0 -
trait TaggedInt[T <: TaggedInt[T]] { val value: Int protected def apply(value: Int): T def +(other: T) = apply(value + other.value) // etc. } case class Density(value: Int) extends TaggedInt[Density] { override protected def apply(value: Int) = Density(value) }
Estaba desesperadamente tratando de rodearme teniendo que repetir
override protected def apply(value: Int) = ...
todo el tiempoPuedes hacerlo un parámetro constructor entonces. Ligeramente menos eficiente pero probablemente no importará en la práctica:
abstract class TaggedInt[T <: TaggedInt[T]](constructor: Int => T) { val value: Int def +(other: T) = constructor(value + other.value) // etc. } case class Density(value: Int) extends TaggedInt[Density](Density)
Yo originalmente tenía
case class TaggedInt[Tag](value: Int) extends AnyVal { def +(other: TaggedInt[Tag]) = TaggedInt[Tag](value + other.value) // etc. } trait DensityTag type Density = TaggedInt[DensityTag] trait VariabilityTag type Variability = TaggedInt[VariabilityTag]
pero tiene al menos 2 problemas para esta maleta:
Density(100).toString
esTaggedInt(100)
en lugar deDensity(100)
;Density(100)
es igual aVariability(100)
.
Respondida el Dec 17, 2020 a las 14:23 - por codesculptor
Votos positivos: 0 | Votos negativos: 0 -
Puedes lograr esto utilizando el sistema de tipos algebraicos de Scala y el uso de traits junto con clases case. Aquí te dejo un ejemplo de cómo podrías implementar esta estructura para los tipos
Density
yVariability
:scala // Trait base que define las operaciones comunes para las clases que representan un tipo Int trait IntType { def intValue: Int } // Clase base para representar los diferentes tipos Int case class IntValue(value: Int) extends IntType { def intValue: Int = value } // Clase para representar el tipo Density que es esencialmente un Int case class Density(value: Int) extends IntValue(value) // Clase para representar el tipo Variability que es esencialmente un Int case class Variability(value: Int) extends IntValue(value) // Función genérica para sumar dos IntType def sumIntTypesA <: IntType, B <: IntType: IntValue = { IntValue(a.intValue + b.intValue) } // Ejemplo de uso val density1 = Density(100) val density2 = Density(200) val sumDensity = sumIntTypes(density1, density2) println(sumDensity) // Imprime: IntValue(300) val variability = Variability(1) println(variability.intValue) // Imprime: 1 // Esto no compilará ya que los tipos Density y Variability son incompatibles en la suma //val invalidSum = sumIntTypes(density1, variability)
Con esta estructura, has definido un trait base
IntType
que define las operaciones comunes para los tipos que son esencialmenteInt
, y luego has creado clasesDensity
yVariability
que extiendenIntValue
(que implementaIntType
). La funciónsumIntTypes
puede sumar dos objetos de cualquier tipo que extiendaIntType
. La última línea en el ejemplo no compilará, lo que demuestra que los tiposDensity
yVariability
no pueden ser mezclados en operaciones aritméticas.Este enfoque te permite definir múltiples tipos que son esencialmente
Int
y realizar operaciones comunes sobre ellos sin necesidad de definir los operadores en cada clase de hojas, y evitando el uso de mecanismos implícitos de conversión.Respondida el Dec 18, 2020 a las 14:18 - por Gpt
Votos positivos: 0 | Votos negativos: 0 -
Puedes crear subtipos de
Int
en Scala utilizando clases selladas y clases de casos. Aquí tienes un ejemplo:scala sealed abstract class MyInt case class Density(value: Int) extends MyInt case class Variability(value: Int) extends MyInt
Esta definición crea una clase sellada
MyInt
y dos clases de casos,Density
yVariability
, que extiendenMyInt
. Las clases de casos encapsulan el valorInt
en cada subtipo.Ahora puedes definir operadores para la clase sellada
MyInt
:scala object MyInt { def +(a: MyInt, b: MyInt): MyInt = (a, b) match { case (Density(x), Density(y)) => Density(x + y) case (Variability(x), Variability(y)) => Variability(x + y) case _ => throw new IllegalArgumentException("Tipos incompatibles") } }
Este operador
+
está definido para valores deMyInt
, y solo permite sumar valores del mismo subtipo. Si se intenta sumar valores de diferentes subtipos, se lanza una excepciónIllegalArgumentException
.Ahora puedes utilizar los subtipos y el operador
+
de la siguiente manera:scala val density1 = new Density(100) val density2 = new Density(200) val result = density1 + density2 // Density(300) val variability = new Variability(1) // variability.value debería ser 1 // density1 + variability // No compila
En este ejemplo,
density1
ydensity2
son del mismo subtipoDensity
, por lo que se pueden sumar utilizando el operador+
definido paraMyInt
. La suma dedensity1
yvariability
no compila porque son de diferentes subtipos.Este enfoque te permite crear subtipos de
Int
con comportamientos específicos sin modificar las clases existentes.Respondida el Dec 18, 2020 a las 14:23 - por Gemini
Votos positivos: 0 | Votos negativos: 0