Within Scala, is it possible to alias a type but disallow cross-use of aliased/non-aliased types like Haskell?
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)
}
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
You could use NewType
from the scala-newtype library!
Shameless plug: I am the author of scala-newtype
https://github.com/estatico/scala-newtype
This combines the ideas from Scalaz and Shapeless as well as introduces ideas straight from Haskell (like GeneralizedNewTypeDeriving).
Here's what the your code may look like using newtype. We'll give both Feet
and Cm
their own distinct types and have them derive the Numeric
type class based on the one for Double (which deriving
does automatically).
We can then use the extension methods provided by Numeric.Implicits
-
object Example {
type Feet = Feet.Type
object Feet extends NewType.Default[Double] {
implicit val num: Numeric[Type] = deriving
}
type Cm = Cm.Type
object Cm extends NewType.Default[Double] {
implicit val num: Numeric[Type] = deriving
}
val widthInFeet = Feet(1.0)
val widthInCm = Cm(30.48)
import Numeric.Implicits._
// Does not compile:
// val nonsense = widthInFeet + widthInCm
// Compiles!
val doubleWidthInFeet: Feet = widthInFeet + widthInFeet
}
However, you use *
in the example, and we wouldn't want Feet * Feet = Feet
as it should really be Feet * Feet = Feet²
, so let's add a FeetSq
type to represent that and define our own operations to be more type safe than Numeric
-
object Example {
type Feet = Feet.Type
object Feet extends NewType.Default[Double] {
implicit final class Ops(val self: Feet) extends AnyVal {
def +(other: Feet) = Feet(self.repr + other.repr)
def -(other: Feet) = Feet(self.repr - other.repr)
def *(other: Feet) = FeetSq(self.repr * other.repr)
}
}
type FeetSq = FeetSq.Type
object FeetSq extends NewType.Default[Double]
type Cm = Cm.Type
object Cm extends NewType.Default[Double]
val widthInFeet = Feet(1.0)
val widthInCm = Cm(30.48)
// Does not compile:
// val nonsense = widthInFeet * widthInCm
// Compiles!
val squareFeet: FeetSq = widthInFeet * widthInFeet
}
Here we're using an implicit final class Ops
to define methods on our newtype. This class is eliminated at compile time, so at runtime we just end up calling extension methods from the Ops
object.