@tarsa Ok here’s a new platform independent file:
sealed trait AtomVal[A] { def numBytes: Int }
object AtomVal
{
implicit case object IntAtom extends AtomVal[Int] { def numBytes: Int = 4 }
implicit case object DoubleAtom extends AtomVal[Double] { def numBytes: Int = 8 }
}
abstract class Struct2[A, B, R](implicit val ev1: AtomVal[A], val ev2:AtomVal[B])
{
def elemLen: Int = ev1.numBytes + ev2.numBytes
val apply: (A, B) => R
}
class Struct3[A, B, C, R](implicit val ev1: AtomVal[A], val ev2:AtomVal[B], val ev3: AtomVal[C])
{
def elemLen: Int = ev1.numBytes + ev2.numBytes + ev3.numBytes
}
Here’s a scala.js implementation, but one that can have exactly the same interface as a Jvm implementation:
class ValSeq2[A, B, R](length: Int)(implicit ev: Struct2[A, B, R])
{
@ inline def elemLen = ev.elemLen
def atom1Len = ev.ev1.numBytes
def bytesLen = elemLen * length
val buf = new typedarray.ArrayBuffer(bytesLen)
val vw = new typedarray.DataView(buf)
def storeElem(ind: Int, inp1: A, inp2: B): Unit = { storeAtom1(ind, inp1); storeAtom2(ind, inp2) }
def getElem(ind: Int): R = ev.apply(getAtom1(ind), getAtom2(ind))
def sMap[C, D, R2](f: (A, B) => (C, D))(implicit evR2: Struct2[C, D, R2]): ValSeq2[C, D, R2] =
{
val vs2 = new ValSeq2[C, D, R2](length)
(0 until length).foreach(i =>
{
val a = getAtom1(i)
val b = getAtom2(i)
val (c, d) = f(a, b)
vs2.storeAtom1(i, c)
vs2.storeAtom2(i, d)
})
vs2
}
def getTuple(ind: Int): (A, B) = (getAtom1(ind), getAtom2(ind))
def storeAtom1(ind: Int, inp: A): Unit =
{
val byteInd = ind * elemLen
ev.ev1.asInstanceOf[AnyRef] match
{
case AtomVal.IntAtom => vw.setInt32(byteInd, inp.asInstanceOf[Int])
case AtomVal.DoubleAtom => vw.setFloat64(byteInd, inp.asInstanceOf[Double])
}
}
def getAtom1(ind: Int): A =
{
val byteInd = ind * elemLen
ev.ev1.asInstanceOf[AnyRef] match
{
case AtomVal.IntAtom => vw.getInt32(byteInd).asInstanceOf[A]
case AtomVal.DoubleAtom => vw.getFloat64(byteInd).asInstanceOf[A]
}
}
def storeAtom2(ind: Int, inp: B): Unit =
{
val byteInd = ind * elemLen + atom1Len
ev.ev2.asInstanceOf[AnyRef] match
{
case AtomVal.IntAtom => vw.setInt32(byteInd, inp.asInstanceOf[Int])
case AtomVal.DoubleAtom =>vw.setFloat64(byteInd, inp.asInstanceOf[Double])
}
}
def getAtom2(ind: Int): B =
{
val byteInd = ind * elemLen + atom1Len
ev.ev2.asInstanceOf[AnyRef] match
{
case AtomVal.IntAtom => vw.getInt32(byteInd).asInstanceOf[B]
case AtomVal.DoubleAtom => vw.getFloat64(byteInd).asInstanceOf[B]
}
}
}
I’d like to get rid of the And B type parameters on the ValSeq2. There must be away to derive A and B from the implicit “ev: Struct2[_, _, R]” parameter.
Here’s some example classes:
case class ExID(v1 :Int, v2: Double)
object ExID
{
implicit object Ints2Implicit extends Struct2[Int, Double, ExID]
{ override val apply = ExID.apply }
}
case class ExDD(v1 :Double, v2: Double)
object ExDD
{
implicit object Ints2Implicit extends Struct2[Double, Double, ExDD]
{ override val apply = ExDD.apply }
}
And here’s a simple example using the sMap method:
object EJsApp
{
def main(args: Array[String]): Unit =
{
val v = new ValSeq2[Int, Double, ExID](3)
v.storeElem(0, 27, 56.001)
v.storeElem(1, -987, -1987.001)
v.storeElem(2, 2, 2.02)
(0 until 3).foreach(i => println(v.getElem(i)))
val v2 = v.sMap[Double, Double, ExDD]((a, b) => (a + 3.0, b -56))
(0 until 3).foreach(i => println(v2.getElem(i)))
}
}
@LPTK I’ve just studied your response and I think your suggestion of using multiple arrays is a superior solution. Either way the problem of collections of values structs seems eminently solvable. And I would still assert my original contention that we could have 80-90% of the benefits of structs with the current Jvm and Js platforms.
I do think that having value classes without object references would be very useful. Such data is a lot easier to serialise and persist, although your multiple arrays would allow refs anyway.