如何实现行为相同但不能一起使用的类型别名

How to implement type aliases that behave the same but cannot be used together

提问人:t9dupuy 提问时间:3/18/2023 更新时间:3/19/2023 访问量:61

问:

我想实现两个类型别名,它们“引用”相同的基础类型(因此它们的行为相同,具有相同的方法),但不能混合在一起。

代码示例如下:

object example {
    case class foo(value: Double):
        def +(that: foo): foo = foo(value + that.value)

    opaque type myType1 <: foo = foo
    opaque type myType2 <: foo = foo

    extension (d: Double)
      def toMyType1: myType1 = new myType1(d)
      def toMyType2: myType2 = new myType2(d)
}


import example.{myType1, myType2}
import example.{toMyType1, toMyType2}

val a1 = 1.5.toMyType1 //a1: myType1
val a2 = 2.4.toMyType1 //a2: myType1
val b1 = 3.2.toMyType2 //b1: myType2

val a3 = a1 + a2       //a3: foo      but wanted      a3: myType1

val c1 = a1 + b1       //c1: foo      but wanted      COMPILATION ERROR (can't add "myType1" and "myType2")

通缉行为 :

  • 可以将两个值相加,从而产生一个新的(相同的myType1myType1myType2)
  • 的值不能相加(编译错误)myType1myType2

这背后的想法是:你不能把米和秒加在一起,即使用米加米或用秒加秒是一样的。

郑重声明:我认为使用 for 类型别名会使编译器将 and 视为两种不同的类型(禁止在它们之间添加),即使它们实际上是相同的类型。opaquemyType1myType2

Scala 类型 别名

评论

1赞 Gaël J 3/18/2023
我相信你需要让你的不透明类型不延伸.但是,这可能意味着您需要为每种不透明类型多次重新定义操作。foo+
0赞 t9dupuy 3/18/2023
@GaëlJ 是的,我想到了这个解决方案,但它似乎是“次优”的,因为我专门创建了用于描述我所有类型别名的类似行为foo
1赞 Mateusz Kubuszok 3/19/2023
这通常是在 Scala 中使用类型类而不是常见的超类型来实现的。特别是,如果您不想将随机亚型混合在一起。

答:

2赞 Mateusz Kubuszok 3/19/2023 #1

您应该使用类型类。由于您使用的是 Scala 3,因此您可以在 Scala 3 中使用对类型类的改进支持:

object example {

  opaque type myType1 = Double
  opaque type myType2 = Double

  extension (d: Double)
    def toMyType1: myType1 = d
    def toMyType2: myType2 = d

  // type class definition
  trait Semigroup[A]:
    extension (a1: A) def +(a2: A): A

  // type class instance
  private val semigroupDouble = new Semigroup[Double]:
    extension (a1: Double) def +(a2: Double): Double = a1 + a2
  given Semigroup[myType1] = semigroupDouble
  given Semigroup[myType2] = semigroupDouble
}

import example.*

val a1 = 1.5.toMyType1 //a1: myType1
val a2 = 2.4.toMyType1 //a2: myType1
val b1 = 3.2.toMyType2 //b1: myType2

a1 + a2 // myType1

//a1 + b1 // compilation error

评论

0赞 t9dupuy 3/20/2023
该性状可以用于高等种类的类型吗?喜欢?如果是这样,我该如何实例化该值? ?Semigroup[A]trait Semigroup[A[_]]val semigroupHigher = new Semigroup[SomeType[SomeOtherType]]
0赞 Mateusz Kubuszok 3/20/2023
从理论上讲,您可以使用然后在类型上进行一些模式匹配......但一般来说,最好为不同的类型提供单独的类型类。所以我建议改用例如.或者,使用 Cats 方法:分离,在专用方法中组合 2(假设)采用类型参数或提供 .在实践中更容易掌握和使用。A <: AnyKindinline defgiven listSemigroup[A]: Semigroup[List[A]] = ...trait SemigroupK[F[_]]F[A]++[A]given fromSemigroupK[F[_], A](using SemigroupK[F]): Semigroup[F[A]]