在Scala中,是否可以为类型设置别名但禁止交叉使用别名/非别名类型(如Haskell)?

时间:2012-11-15 00:30:20

标签: scala haskell types

在Haskell中,我认为可以以这样的方式对类型进行别名,即编译器不允许别名类型和非混淆类型之间的引用。根据{{​​3}},人们可以像这样使用Haskell的newtype

newtype Feet = Feet Double
newtype Cm   = Cm   Double

其中FeetCm的行为类似于Double值,但尝试将Feet值与Cm值相乘会导致编译器错误。

编辑:Ben在评论中指出,Haskell中的上述定义是不够的。 FeetCm将是新类型,不会定义任何函数。做了一些研究,我发现以下内容可行:

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

这将创建一个派生自现有Num类型的新类型(需要使用switch:-XGeneralizedNewtypeDeriving)。当然,这些新类型从ShowEq等其他类型衍生出来更有价值,但这是正确评估Cm 7 * Cm 9所需的最低要求。

Haskell和Scala都有type,它只是对现有类型进行别名,并允许在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

Scala是否有newtype等价物,假设这样做符合我的想法吗?

4 个答案:

答案 0 :(得分:13)

另一个选择是使用值类。这些创建了一个基础类型的包装器,它在编译时转换为对原始类型的直接访问,类上的方法被转换为关联的伴随对象上的静态调用。例如:

class CM(val quant : Double) extends AnyVal {
  def +(b : CM) = new CM(quant + b.quant)
  def *(b : Int) = new CM(quant * b)
}

答案 1 :(得分:9)

是的,你在scala中使用了一种称为Unboxed Tagged Types的东西。

这就是Tagged的定义方式:

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

这可以让你做这样的事情

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

还要添加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

如果你想将乘法限制为只有英尺,那么你可以写一个类型类型的乘法函数

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)
} 
然后你可以做

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

这是Scala可以做的最好的

要了解有关盒装类型的更多信息,请查看 http://eed3si9n.com/learning-scalaz-day3

答案 2 :(得分:5)

您可以使用scala-newtype库中的NewType

无耻插件:我是scala-newtype的作者

https://github.com/estatico/scala-newtype

这结合了Scalaz和Shapeless的想法以及直接从Haskell引入的想法(如GeneralizedNewTypeDeriving)。

以下是使用newtype时代码的外观。我们会为FeetCm提供他们自己的不同类型,并根据Double(Numeric自动执行的类型)派生deriving类型类

然后我们可以使用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
}

但是,您在示例中使用*,我们不希望Feet * Feet = Feet因为它应该是Feet * Feet = Feet²,所以让我们添加FeetSq 1}}类型来表示它并定义我们自己的操作比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
}

我们在这里使用implicit final class Ops来定义新类型的方法。这个类在编译时被删除,所以在运行时我们最终只是从Ops对象调用扩展方法。

答案 3 :(得分:0)

对于

val widthInCm: Cm = 30.48

def getWidthInFeet: Feet = widthInCm

,只需像这样定义FeetCm

type Feet <: Double
type Cm <: Double
只要您没有将它们向上转换回Double

就可以完成任务。自己看看:

def getWidthInFeet: Feet = widthInCm

Error:(1, 28) type mismatch;
 found   : widthInCm.type (with underlying type Cm)
 required: Feet
    def getWidthInFeet: Feet = widthInCm

一个副作用是,您需要将Double的值显式转换为CmFeet才能获得实例:

val widthInCm: Cm = 30.48.asInstanceOf[Cm]

此外,您不能在不丢失类型信息的情况下对它们执行任何Double操作(因此您必须一直向下转换它们)。