[英]Type constraints on dimensionality of vectors in F# and Haskell (Dependent Types)
我是F#和Haskell的新手,并且正在实施一个项目,以确定我希望花更多时间来使用哪种语言。
在很多情况下,我希望给定的数值类型基于给顶层函数提供的参数(即在运行时)具有给定的尺寸。 例如,在此F#代码段中,我有
type DataStreamItem = LinearAlgebra.Vector<float32>
type Ball =
{R : float32;
X : DataStreamItem}
我希望所有DataStreamItem
类型的实例都具有D
维。
我的问题是出于算法开发和调试的利益,因为这种形状不匹配的错误可能很难确定,但是在算法运行时应该不是问题:
在F#或Haskell中 ,是否有办法约束DataStreamItem
和/或Ball
具有D
维? 还是在每次计算中都需要使用模式匹配?
如果是后者,是否有任何良好,轻量级的范式可以在发生此类约束违规时立即予以捕捉(在性能至关重要的情况下可以将其删除)?
编辑:
为了阐明D
被约束的意义:
D
的定义是,如果将main(DataStream)
函数的算法表示为计算图,则所有中间计算都将依赖于D
的维来执行main(DataStream)
。 我能想到的最简单的例子是一个点的产品M
与DataStreamItem
:的尺寸DataStream
会确定的尺寸参数的创建M
另一个编辑:
一周后,我发现以下博客恰好概述了我在Haskell的依赖类型中寻找的内容:
https://blog.jle.im/entry/practical-dependent-types-in-haskell-1.html
另一个:此Reddit包含有关Haskell中依赖类型的一些讨论,并包含R. Eisenberg相当有趣的论文建议的链接。
Haskell不是F#类型的系统都不足以(直接)表达“ N个递归类型T的嵌套实例,其中N在2到6之间 ”或“ 一串正好为6个长的字符 ” 的语句。 至少没有确切的术语。
我的意思是,可以肯定,您始终可以表示这样的6位长字符串类型,如type String6 = String6 of char*char*char*char*char*char
或该类型的某种变体(从技术上讲,这对于您的特定示例应该足够了)向量,除非您没有告诉我们整个示例),但是您不能说类似type String6 = s:string{s.Length=6}
,更重要的是,您不能定义concat: String<n> -> String<m> -> String<n+m>
形式的函数concat: String<n> -> String<m> -> String<n+m>
,其中n
和m
表示字符串长度。
但是你不是第一个问这个问题的人 。 这个研究方向确实存在,并且被称为“ 从属类型 ”,我可以将其要旨最概括地表达为“ 对类型进行更高阶,更强大的运算 ”(与像联合和交集相反,正如我们在ML语言)-请注意,在上面的示例中,我如何用数字而不是其他类型对String
类型进行参数化,然后对该数字进行算术运算。
在这一方向上最杰出的语言原型(据我所知)是Agda , Idris , F *和Coq (实际上不是完整的AFAIK)。 签出它们,但要提防:这是明天的边缘,我不建议基于这些语言启动一个大型项目。
(编辑:显然,您可以在Haskell中做一些技巧来模拟依赖类型,但这不是很方便,您必须启用UndecidableInstances
)
或者 ,您可以选择在运行时进行检查的较弱解决方案。 一般要点是:将矢量类型包装在普通包装中,不允许直接构造,而是提供构造函数,并使这些构造函数确保所需的属性(即,长度)。 就像是:
type Stream4 = private Stream4 of DataStreamItem
with
static member create (item: DataStreamItem) =
if item.Length = 4 then Some (Stream4 item)
else None
// Alternatively:
if item.Length <> 4 then failwith "Expected a 4-long vector."
item
这是Scott Wlaschin提出的方法的更完整说明: 约束字符串 。
因此,如果我正确理解,您实际上并没有执行任何类型级别的算术运算,而只是拥有一个“长度标记”,该“长度标记”在函数调用链中共享。
长期以来,在Haskell可以做到这一点。 我认为非常优雅的一种方法是使用所需长度的标准固定长度类型为数组添加注释:
newtype FixVect v s = FixVect { getFixVect :: VU.Vector s }
为了确保正确的长度,您仅提供从固定长度类型构造的(多态) 智能构造函数,这是绝对安全的,尽管未提及实际的维数!
class VectorSpace v => FiniteDimensional v where
asFixVect :: v -> FixVect v (Scalar v)
instance FiniteDimensional Float where
asFixVect s = FixVect $ VU.singleton s
instance (FiniteDimensional a, FiniteDimensional b, Scalar a ~ Scalar b) => FiniteDimensional (a,b) where
asFixVect (a,b) = case (asFixVect a, asFixVect b) of
(FixVect av, FixVect bv) -> FixVect $ av<>bv
由未装箱的元组进行的构造实际上效率很低,但这并不意味着您可以使用这种范例编写高效的程序–如果维度始终保持不变,则只需要包装一次并解开包装,就可以通过安全而又重要的操作来完成所有关键操作运行时未经检查的拉链,折叠和LA组合。
无论如何,这种方法并未真正广泛使用。 也许单个常量维度实际上对于大多数相关操作来说太过局限,并且如果您经常需要拆开元组,那么它效率太低。 目前流行的另一种方法是用类型级数字实际标记向量。 随着GHC-7.4中数据类型的引入,这些数字已经以可用形式可用。 到目前为止,它们仍然很笨拙,不适合进行适当的算术运算,但是即将发布的8.0将大大改进Haskell中这种依赖类型编程的许多方面。
提供有效的长度索引数组的库是linear的 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.