【问题标题】:Making connections between types and values在类型和值之间建立联系
【发布时间】:2018-06-08 14:20:42
【问题描述】:

我有类型级算术的实现,能够进行一些编译时算术验证,即<,>,=,有两种方式:

有了这些,我可以有一个 getFoo 函数,我可以这样调用:

getFoo[_2,_3]

_2_3 是整数值 2 和 3 的类型级等价物。现在理想情况下,我希望我的 getFoo 函数将整数值作为参数并尝试从值推断 _2 2.

我的计划是将以下 associatedInt 信息添加到 Nat 基类:

trait Nat {
  val associatedInt: Int
  type AssociatedInt = associatedInt.type
}

这样后续的类型会被定义为:

type _1 = Succ[_0] {
  override val associatedInt: Int = 1
}

然后将getFoo的签名改成整数:

def getFoo(i:Int)(implicit ...)

基于此,我们将使用与AssociatedInt 类型关联的类型进行类型级别的算术断言。即,类似:

def getFoo(i: Integer)(implicit ev: Nat{type I = i.type } =:= ExpectedType)

这不起作用。即:

trait Nat {
  val i: Integer
  type I = i.type
}

type ExpectedType = _1

trait _1 extends Nat {
  override val i: Integer = 1
}

def getFoo(i: Integer)
          (implicit ev: Nat{type I = i.type } =:= ExpectedType)= ???

getFoo(1) //this fails to prove the =:= implicit.

回想起来,我不应该期望它。因为如果我们有:

val x : Integer = 1
val y : Integer = 1
type X = x.type
type Y = y.type
def foo(implicit ev: X =:= Y) = 123
foo //would fail to compile.

即具有相同值的不同“对象”的单例类型是不同的。 (我猜原因是目前在 Scala 中,单例类型用于对象,与 literal type 不同)

因此,有了这些背景信息,我想知道是否有任何方法可以实现我想要做的事情,即通过其他方法从关联值推断 a 类型。

【问题讨论】:

    标签: scala types type-deduction type-level-computation type-variables


    【解决方案1】:

    答案似乎只是“不”。这些值在运行时存在。类型检查发生在编译时。这两个时间间隔不相交,运行时总是严格地在编译时间之后出现,因此无法及时传播有关值的信息以获取有关类型的一些附加信息。

    请注意,如果您只需要排序(您不想添加或减去版本号),那么您可以简单地重用子类型关系,如下所示:

    sealed trait Num
    class _9 extends Num
    class _8 extends _9
    class _7 extends _8
    class _6 extends _7
    class _5 extends _6
    class _4 extends _5
    class _3 extends _4
    class _2 extends _3
    class _1 extends _2
    class _0 extends _1
    
    trait Version[+Major <: Num, +Minor <: Num]
    
    println(implicitly[Version[_2, _4] =:= Version[_2, _4]])
    println(implicitly[Version[_2, _3] <:< Version[_2, _4]])
    

    【讨论】:

    • 谢谢。虽然您关于值和类型分离的观点是有效的,但我认为我们仍然有 Aux 模式等技巧,而这正是我正在寻找的东西。
    • @Mathsnoob Aux 只是将一些类型成员解包为类型级函数的显式参数,这些函数将类型映射到其他类型。以任何方式将 values 映射到 types 都无济于事,这将是使用依赖类型进行编程,这需要对整个语言进行完全不同的设计。
    • 我猜到了我想要的。使用包装类型的实例! :P
    【解决方案2】:

    诀窍是意识到值可以具有类型字段,并且类型信息在编译时可用。考虑到这一点,我们可以定义:

    sealed trait NatEnum{
      type Nat_ <:Nat
    }
    

    并为这些类型定义枚举“值”,例如:

    object __0 extends NatEnum{ override type Nat_ = _0 }
    object __1 extends NatEnum{ override type Nat_ = _1 }
    object __2 extends NatEnum{ override type Nat_ = _2 }
    object __3 extends NatEnum{ override type Nat_ = _3 }
    

    并重构getFoo如下:

    def getFoo(maj: NatEnum, min: NatEnum)(implicit
                         maj_check: FooClient.Major =:= maj.Nat_,
                         min_check: FooClient.Minor <:< min.Nat_
                        ) = FooClient.foo
    

    我们可以用它来测试:

    getFoo(__2,__2) //compiles
    getFoo(__1,__0)// doesn't compile
    

    这里是要点的更新版本:simplerigorous

    【讨论】:

    • 嗯...是的,这显然适用于具有恒定路径的单例对象。如果值和值的路径始终是常数,则不必从未来传递任何信息,因此不会违反因果关系。老实说,我仍然不确定这会给你带来什么:如果这些值仍然是常量,那么这些值会做什么类型不做的事情?
    • 很抱歉给您带来了困惑。不,我只是想让这个结构更加用户友好。我通常会尽量避免使用需要提供显式类型信息的函数。在新形式中,用户只需使用枚举即可,无需过多担心底层Nat 的实现。
    猜你喜欢
    • 2011-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-25
    • 2020-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多