20

在 Haskell 中,我相信可以以编译器不允许别名类型和非别名类型之间的引用的方式对类型进行别名。根据这个堆栈溢出问题,可以newtype像这样使用Haskell:

newtype Feet = Feet Double
newtype Cm   = Cm   Double

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

编辑:Ben 在评论中指出,Haskell 中的上述定义是不够的。Feet并且Cm将是新类型,在这些类型上不会定义任何函数。做了更多的研究,我发现以下方法会起作用:

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

这将创建一个从现有类型派生的新Num类型(需要使用 switch: -XGeneralizedNewtypeDeriving)。Show当然,这些新类型从其他类型(例如,等)派生出来会更有价值Eq,但这是正确评估 所需的最低要求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

newtype假设它做了我认为它做的事情,Scala 是否有等价物?

4

4 回答 4

14

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

class CM(val quant : Double) extends AnyVal {
  def +(b : CM) = new CM(quant + b.quant)
  def *(b : Int) = new CM(quant * b)
}
于 2012-11-15T10:23:10.327 回答
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

如果您想将乘法限制为仅 Feet,那么您可以编写一个 typeclass 类型乘法函数

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

于 2012-11-15T01:30:31.517 回答
6

您可以NewType从 scala-newtype 库中使用!

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

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

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

这是您的代码使用 newtype 时的样子。我们将给出两者Feet以及Cm它们自己的不同类型,并让它们Numeric基于 Double 的类型派生类型类(它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类型来表示它,并定义我们自己的操作比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
}

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

于 2018-01-31T19:36:32.670 回答
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在不丢失类型信息的情况下对它们进行任何操作(因此您必须一直向下转换它们)。

于 2019-10-17T15:26:18.070 回答