我目前正在尝试在scala中定义时钟数据流语言的模型。
流程实际上代表某种类型T的无限序列值,由某个时钟C调节(时钟表示流量实际可用的时刻)。
通过根据从另一个(布尔)流F'导出的时钟C本身对其进行采样,可以从流F导出采样流SF:SF包含当布尔流F'为真时采样的F的值。 / p>
“基本时钟”是从名为“T”的始终为真的流派生的时钟。
在下面的示例中,F和F'位于基准时钟上(并且 - 用于表示流在某个时刻没有值)
T : 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ... (always 1)
F : 0 0 0 1 1 1 0 1 0 1 0 0 0 1 1 1 1 ...
F' : 0 1 0 0 0 1 0 1 1 1 0 0 0 1 0 0 1 ...
F sampled on F': - 0 - - - 1 - 1 0 1 - - - 1 - - 1 ...
所以,(F'上采样的F)在F'为真时取F的值,在F'为假时不定义。
目标是使这种抽样关系在流的类型中显而易见并执行静态检查。例如,如果它们位于同一时钟,则仅允许从另一个流中采样流。 (这是用于建模数字电路的DSL)。
所讨论的系统是一个依赖类型的系统,因为时钟是流类型的一部分,它本身是从流值中派生出来的。
因此,我尝试使用路径依赖类型在scala中对此进行建模,并从无形状中获取灵感。时钟的建模类型如下:
trait Clock {
// the subclock of this clock
type SubClock <: Clock
}
trait BaseClock extends Clock {
type SubClock = Nothing
}
这定义了时钟类型和特定时钟,基本时钟没有副时钟。
然后,我试图模拟流程:
trait Flow {
// data type of the flow (only boolean for now)
type DataType = Boolean
// clock type of the flow
type ClockType <: Clock
// clock type derived from the Flow
class AsClock extends Clock {
// Subclock is inherited from the flow type clocktype.
type SubClock = ClockType
}
}
我在流特性中定义了一个内部类,以便能够使用路径相关类型将Flow提升到时钟。如果f是流,f.AsClock是一种可用于定义采样流的Clock类型。
然后我提供了在基准时钟上构建流的方法:
// used to restrict data types on which flows can be created
trait DataTypeOk[T] {
type DataType = T
}
// a flow on base clock
trait BFlow[T] extends Flow { type DataType = T; type ClockType = BaseClock }
// Boolean is Ok for DataType
implicit object BooleanOk extends DataTypeOk[Boolean]
// generates a flow on the base clock over type T
def bFlow[T](implicit ev:DataTypeOk[T]) = new BFlow[T] { }
到目前为止一切顺利。然后我提供了一个wat来构建一个采样流程:
// a flow on a sampled clock
trait SFlow[T, C <: Clock] extends Flow { type DataType = T; type ClockType = C }
// generates a sampled flow by sampling f1 on the clock derived from f2 (f1 and f2 must be on the same clock, and we want to check this at type level.
def sFlow[F1 <: Flow, F2 <: Flow](f1: F1, f2: F2)(implicit ev: SameClock[F1, F2]) = new SFlow[f1.DataType, f2.AsClock] {}
这是使用f2.AsClock将流值提升到类型的位置。
这背后的想法是能够写出这样的东西:
val a1 = bFlow[Boolean]
val a2 = bFlow[Boolean]
val b = bFlow[Boolean]
val c1: SFlow[Boolean, b.AsClock] = sFlow(a1, b) // compiles
val c2: SFlow[Boolean, b.AsClock] = sFlow(a2, b)
val d: SFlow[Boolean, c1.AsClock] = sFlow(a1, c1) // does not compile
并让编译器拒绝最后一种情况,因为a1和c1的ClockType不相等(a1在基本时钟上,c1在时钟b上,所以这些流不在同一时钟上)。
所以我在构建器方法中引入了一个(隐式ev:SameClock [F1,F2])参数,其中
SameClock是一个特性,用于在编译时见证两个流具有相同的ClockType,并且使用从第二个流派生的时钟对第一个流进行采样是正确的。
//type which witnesses that two flow types F1 and F2 have the same clock types.
trait SameClock[F1 <: Flow, F2 <: Flow] {
}
implicit def flowsSameClocks[F1 <: Flow, F2 <: Flow] = ???
这是我完全无能为力的地方。我已经无形地查看了Nat和HList源代码,并且理解见证这些事实的对象应该以结构正向归纳的方式构建:为为要静态检查的类型设置此类型构造函数的对象提供隐式构建器。如果可能的话,隐式解析机制会生成一个见证该属性的对象。
但是我真的不明白编译器如何使用正向归纳为任何类型实例构建正确的对象,因为我们通常使用递归来证明使用简单的术语解释术语并证明更简单的情况。
对类型级编程有很好理解的人的一些指导会很有帮助!
答案 0 :(得分:1)
我认为你使一个基本问题过于复杂(或者说你已经为这个问题简化了太多问题)。
您不应该需要使用implicits来使路径依赖类型工作。事实上,目前Scala没有办法向类型系统证明类似a.T <: b.T
的基于隐式的东西。唯一的方法是让Scala理解a
和b
实际上是相同的值。
这是一个简单的设计,可以满足您的需求:
trait Clock { sub =>
// This is a path-dependent type; every Clock value will have its own Flow type
class Flow[T] extends Clock {
def sampledOn(f: sub.Flow[Boolean]): f.Flow[T] =
new f.Flow[T] { /* ... */ }
}
}
object BaseClock extends Clock
object A1 extends BaseClock.Flow[Int]
object A2 extends BaseClock.Flow[Boolean]
object B extends BaseClock.Flow[Boolean]
val c1: B.Flow[Int] = A1 sampledOn B
val c2: B.Flow[Boolean] = A2 sampledOn B
val d1 = c1 sampledOn c2
//val d2: c2.Flow[Int] = A1 sampledOn c2 // does not compile
最后一行无法编译,错误:
Error:(133, 38) type mismatch;
found : B.Flow[Boolean]
required: BaseClock.Flow[Boolean]
val d2: c2.Flow[Int] = A1 sampledOn c2 // does not compile
^
(请注意,是否使用val
或object
声明值是无关紧要的。)