Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using custom scala types in List

I am looking to create a type family that would represent data sizes (Byte, KB...). for that, the idea is to build a base type to have the real sizes based on:

  type SizeUnit = Int
  type B = SizeUnit
  type KB = SizeUnit
  type MB = SizeUnit
  type GB = SizeUnit
  type TB = SizeUnit
  type PB = SizeUnit
  type EB = SizeUnit
  type ZB = SizeUnit
  type YB = SizeUnit

have an ordered list of them:

val sizes = List(B, KB, MB, GB, TB, PB, EX, ZB, TB)

and have a convertion method that takes a target type, finds the index difference between them and multiplies by 1024 in the power of difference. so:

def convertTo(targetType: SizeUnit): SizeUnit ={
  def power(itr: Int): Int = {
    if (itr == 0) 1
    else 1024*power(itr-1)
  }

  val distance = sizes.indexOf(targetType) - sizes.indexOf(this)
  distance match {
    //same type - same value
    case 0 => targetType
    //positive distance means larget unit - smaller number
    case x>0 => targetType / power(distance)
    //negative distance means smaller unit - larger number and take care of negitivity 
    case x<0 => targetType * power(distance) * (-1)
  }  
}

i have a few problems before i even check the validity of the method (as i am new to Scala):

  • is there a way to create a List (or any other Seq) that holds types rather than values? or rather - types as values?
  • if i understand correctly, types are not kept beyond compilation. does that mean that in runtime, if i pass a GB value to an existing KB, it cannot decipher the types?

thank you, Ehud

like image 725
Ehud Kaldor Avatar asked Oct 18 '12 20:10

Ehud Kaldor


People also ask

What is .type in Scala?

Type declaration is a Scala feature that enables us to declare our own types. In this short tutorial, we'll learn how to do type declaration in Scala using the type keyword. First, we'll learn to use it as a type alias. Then, we'll learn to declare an abstract type member and implement it.

What does <: mean in Scala?

It means an abstract type member is defined (inside some context, e.g. a trait or class), so that concrete implementations of that context must define that type.

How does Scala determine types when they are not specified?

For example, a type constructor does not directly specify a type of values. However, when a type constructor is applied to the correct type arguments, it yields a first-order type, which may be a value type. Non-value types are expressed indirectly in Scala.

Which type in Scala is a subtype of all types?

Scala type hierarchy. Any is the supertype of all types, also called the top type. It defines certain universal methods such as equals , hashCode , and toString . The top-type Any has a subtype Matchable , which is used to mark all types that we can perform pattern matching on.


1 Answers

All those types are simply type aliases, not independent types.

scala> type SizeUnit = Int
defined type alias SizeUnit

scala> type B = SizeUnit
defined type alias B

scala> type KB = SizeUnit
defined type alias KB

scala> (3 : KB) == (3 : B)
res0: Boolean = true

Type aliases are simply different names for the same type. So even if you could write it, your list would be equivalent to having written:

val sizes = List(Int, Int, Int, Int, Int, Int, Int, Int, Int)

And similarly, you could never use these types to write a function that is required to accept a quantity in MB, since all of these types are the same thing.

To separate out B, KB, MB, etc as different "kinds" of integer, you would need them to be subtypes of Int, not type aliases for Int. But Int is a final type, so you can't subtype it anyway.

A much better approach is to just let Int represent a raw number, and instead implement a type that represents an Int together with a unit. There are several approaches you can take for this, but I'd do it something like this:

abstract class SizeUnit

case object B extends SizeUnit
case object KB extends SizeUnit
case object MB extends SizeUnit


case class Storage(num : Int, unit : SizeUnit)

Now 3 megabytes is Storage(3, MB) and 17 bytes is Storage(17, B). You have nice statically enforced separation between arbitrary integers and Storage quantities, and you always have the unit as a data object (no need to be able to statically infer it) whenever you have a Storage quantity. You can put the objects B, KB, MB, etc in a list, and do whatever manipulation with them you want.

Alternatively you could make the unit objects themselves contain some information about their order or ratios between them, rather than storing that information in an external list.

You can even do wacky things with implicit conversions using this scheme. Something like this springs to mind:

object SizeableInt {
    // since I want to give the conversion methods the same name as the
    // units, I need different names to refer to the units in the
    // implementation of those methods. If B, KB, etc were defined in
    // a different qualified namespace, this wouldn't be necessary.
    private val _B = B
    private val _KB = KB
    private val _MB = MB

    case class SizeableInt(x : Int) {
        def B : Storage = Storage(x, _B)
        def KB : Storage = Storage(x, _KB)
        def MB : Storage = Storage(x, _MB)
    }

    implicit def makeSizeableInt(x : Int) : SizeableInt = SizeableInt(x)
}

With that, once you've imported the implicit, you can simply write things like 4 MB or 123456789 B instead of Storage(4, MB) or Storage(123456789, B).

like image 186
Ben Avatar answered Oct 13 '22 09:10

Ben