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):
thank you, Ehud
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.
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.
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.
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.
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)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With