我有一些科学项目。那里有各种长度的矢量/方形矩阵。显然(例如)长度为2的向量不能添加到长度为3的向量中(依此类推)。有几个.NET库,它们处理向量/矩阵。所有这些都有通用的载体/矩阵或者有一些非常特殊的载体/矩阵,它们不能满足需要。
大多数(如果不是全部)这些库可以从列表或数组创建向量。不幸的是,如果我错误地给出了一个错误长度的输入数组,那么我将得到一个错误长度的向量,然后一切都会在运行时爆炸!
我想知道是否有可能在编译时检查数组长度,以便获得编译错误,如果,我想说,我尝试将5元素数组传递给长度为2“构造函数”的向量。毕竟,printfn
几乎就是这样!
我想到了F#类型的提供商,但我不确定如何在这里应用它们。
非常感谢!
感谢OP提出了一个有趣的问题。我的答案频率下降并不是因为不愿意提供帮助,而是因为有一些问题引起了我的兴趣。
我们在F#中没有依赖类型,而F#不支持带有数字类型参数的泛型(如C ++)。
但是,我们可以为Dim1
,Dim2
等不同维度创建不同的类型,并将它们作为类型参数提供。
这将允许我们为apply
设置一个类型签名,它将一个向量应用于这样的矩阵:
let apply (m : Matrix<'R, 'C>) (v : Vector<'C>) : Vector<'R> = …
除非矩阵的列与向量的长度匹配,否则代码将无法编译。此外;结果向量的长度是列的行数。
一种方法是定义一个接口IDimension
和一些表示不同维度的具体实现。
type IDimension =
interface
abstract Size : int
end
type Dim1 () = class interface IDimension with member x.Size = 1 end end
type Dim2 () = class interface IDimension with member x.Size = 2 end end
然后可以像这样实现矢量和矩阵
type Vector<'Dim when 'Dim :> IDimension
and 'Dim : (new : unit -> 'Dim)
> () =
class
let dim = new 'Dim()
let vs = Array.zeroCreate<float> dim.Size
member x.Dim = dim
member x.Values = vs
end
type Matrix<'RowDim, 'ColumnDim when 'RowDim :> IDimension
and 'RowDim : (new : unit -> 'RowDim)
and 'ColumnDim :> IDimension
and 'ColumnDim : (new : unit -> 'ColumnDim)
> () =
class
let rowDim = new 'RowDim()
let columnDim = new 'ColumnDim()
let vs = Array.zeroCreate<float> (rowDim.Size*columnDim.Size)
member x.RowDim = rowDim
member x.ColumnDim = columnDim
member x.Values = vs
end
最后,这允许我们编写如下代码:
let m76 = Matrix<Dim7, Dim6> ()
let v6 = Vector<Dim6> ()
let v7 = apply m76 v6 // Vector<Dim7>
// Doesn't compile because v7 has the wrong dimension
let vv = apply m76 v7
如果你需要各种各样的维度(因为你有一个代数递增/递减向量/矩阵的维度),你可以使用一些教会数字的智能变体来支持它。
如果这个可用或不可用完全取决于读者我认为。
PS。
如果它们适用于比浮子更多的类型,也许也可以使用单位措施。
你要找的东西的总称是dependent types,但F#不支持它们。
我已经看到an experiment在使用类型提供程序来模仿依赖类型的一种特定风格(约束基本类型的域),但我不希望它可以使用当前形式的类型提供程序来实现你想要的。他们似乎过于异想天开。
打印格式字符串似乎正在这样做(实际上打印机是依赖类型的“Hello World”应用程序),但实际上它们是有效的,因为它们得到编译器的特殊处理,并且其机制不可扩展。
你注定要在运行时确保正确的长度。
我最好的选择是使用结构来编码实际向量并确保API级别的正确性,将它们映射到与这些矩阵代数库交互的点上的数组,然后将结果映射回具有充足断言的结构完成后。
来自@Justanothermetaprogrammer的评论有资格作为答案。以下是它在实际例子中的工作原理。示例中的矩阵实现基于MathNet.Numerics.LinearAlgebra
:
open MathNet.Numerics.LinearAlgebra
type RealMatrix2x2 =
| RealMatrix2x2 of Matrix<double>
static member private createInternal (a : #seq<#seq<double>>) =
matrix a |> RealMatrix2x2
static member create
(
(a11, a12),
(a21, a22)
) =
RealMatrix2x2.createInternal
[|
[| a11; a12|]
[| a21; a22|]
|]
let m2 =
(
(1., 2.),
(3., 4.)
)
|> RealMatrix2x2.create
元组签名和“重新映射”到#seq<#seq<double>>
可以很容易地使用例如Excel或任何其他方便的工具进行代码生成,以获得必要的维度。实际上,整个类以及任何其他必要的运算符覆盖(例如RealMatrix2x2
乘以RealMatrix2x2
,......)可以是为所有必要维度生成的代码。