Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Within Scala, is it possible to alias a type but disallow cross-use of aliased/non-aliased types like Haskell?

Tags:

In Haskell, I believe that it is possible to alias a type in such a way that the compiler does not allow references between the aliased type and the unaliased type. According to this stack overflow question, one can use Haskell's newtype like so:

newtype Feet = Feet Double
newtype Cm   = Cm   Double

where Feet and Cm will behave like Double values, but attempting to multiply a Feet value and a Cm value will result in a compiler error.

EDIT: Ben pointed out in the comments that this above definition in Haskell is insufficient. Feet and Cm will be new types, on which there will be no functions defined. Doing a bit more research, I found that the following will work:

newtype Feet = Feet Double deriving (Num)
newtype Cm   = Cm   Double deriving (Num)

This creates a new type that derives from the existing Num type (requires using switch: -XGeneralizedNewtypeDeriving). Of course, these new types will be even more valuable deriving from other types such as Show, Eq, etc. but this is the minimum required to correctly evaluate Cm 7 * Cm 9.

Both Haskell and Scala have type, which simply aliases an existing type and allows nonsensical code such as this example in Scala:

type Feet = Double
type Cm = Double

val widthInFeet: Feet = 1.0
val widthInCm: Cm = 30.48

val nonsense = widthInFeet * widthInCm

def getWidthInFeet: Feet = widthInCm

Does Scala have a newtype equivalent, assuming that this does what I think it does?

like image 296
rybosome Avatar asked Nov 15 '12 00:11

rybosome


2 Answers

Another option would be to use value classes. These create a wrapper around an underlying type which is converted into direct access to the raw type at compile time, with methods on the class being converted into static calls on an associated companion object. For example:

class CM(val quant : Double) extends AnyVal {
  def +(b : CM) = new CM(quant + b.quant)
  def *(b : Int) = new CM(quant * b)
}
like image 153
Submonoid Avatar answered Jan 16 '23 10:01

Submonoid


Yeah you using something known as Unboxed Tagged Types in scala.

This is how Tagged is defined:

type Tagged[U] = { type Tag = U }
type @@[T, U] = T with Tagged[U]

This allows you to do something like this

sealed trait Feet

def Feet[A](a: A): A @@ Feet = Tag[A, Feet](a)
Feet: [A](a: A)scalaz.@@[A,Feet]

scala> val mass = Feet(20.0)
mass: scalaz.@@[Double,Feet] = 20.0

scala> 2 * mass
res2: Double = 40.0

to also add CM

sealed trait CM

def CM[A](a: A): A @@ CM = Tag[A, CM](a)
CM: [A](a: A)scalaz.@@[A,CM]

scala> val mass = CM(20.0)
mass: scalaz.@@[Double,CM] = 20.0

If you want to restrict multiplication to only Feet then you could write a typeclass type multiplication function

trait Multiply[T] { self =>
   def multiply(a: T, b: T): T
}
implicit val footInstance = new Multiply[Feet] {
   def multiply(a: Feet, b: Feet): Feet = Feet(a * b)
}
implicit val cmInstance = new Multiply[CM] {
  def multiply(a: CM, b: CM): CM = CM(a * b)
}

def multiply[T: Multiply](a: T, b: T): T = {
  val multi = implicitly[Multiply[T]]
  multi.multiply(a,b)
} 

you can then do

multiply(Feet(5), Feet(10)) // would return Feet(50)

this is the best Scala can do

To learn more about the boxed type check out http://eed3si9n.com/learning-scalaz-day3

like image 35
Reuben Doetsch Avatar answered Jan 16 '23 10:01

Reuben Doetsch