在Haskell中,我认为可以以这样的方式对类型进行别名,即编译器不允许别名类型和非混淆类型之间的引用。根据{{3}},人们可以像这样使用Haskell的newtype
:
newtype Feet = Feet Double
newtype Cm = Cm Double
其中Feet
和Cm
的行为类似于Double值,但尝试将Feet
值与Cm
值相乘会导致编译器错误。
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
Scala是否有newtype
等价物,假设这样做符合我的想法吗?
答案 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时代码的外观。我们会为Feet
和Cm
提供他们自己的不同类型,并根据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
,只需像这样定义Feet
和Cm
:
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
的值显式转换为Cm
或Feet
才能获得实例:
val widthInCm: Cm = 30.48.asInstanceOf[Cm]
此外,您不能在不丢失类型信息的情况下对它们执行任何Double
操作(因此您必须一直向下转换它们)。