F#/编译时验证数组长度的最简单方法

F#/编译时验证数组长度的最简单方法,f#,type-providers,F#,Type Providers,我有一些科学项目。这里有各种长度的向量/方阵。显然(例如)长度为2的向量不能与长度为3的向量相加(以此类推)。有几个处理向量/矩阵的网络库。它们要么有通用向量/矩阵,要么有一些非常特殊的向量/矩阵,不符合需要 这些库中的大多数(如果不是全部的话)都可以从列表或数组中创建向量。不幸的是,若我错误地给出了一个长度错误的输入数组,那个么我将得到一个长度错误的向量,然后在运行时一切都将崩溃 我想知道是否有可能在编译时检查数组长度,以便在我尝试将5个元素的数组传递给长度为2的向量“构造函数”时得到编译错误

我有一些科学项目。这里有各种长度的向量/方阵。显然(例如)长度为2的向量不能与长度为3的向量相加(以此类推)。有几个处理向量/矩阵的网络库。它们要么有通用向量/矩阵,要么有一些非常特殊的向量/矩阵,不符合需要

这些库中的大多数(如果不是全部的话)都可以从列表或数组中创建向量。不幸的是,若我错误地给出了一个长度错误的输入数组,那个么我将得到一个长度错误的向量,然后在运行时一切都将崩溃

我想知道是否有可能在编译时检查数组长度,以便在我尝试将5个元素的数组传递给长度为2的向量“构造函数”时得到编译错误。毕竟,
printfn
几乎可以做到这一点

我想到了F#类型提供者,但我不确定如何在这里应用它们


非常感谢

你要找的东西的通称是,但F#不支持它们

我见过使用类型提供程序来模拟依赖类型的一种特殊风格(约束基元类型的域),但我不认为使用当前形式的类型提供程序可以实现您想要的。他们似乎太异想天开了

打印格式字符串似乎就是这样做的(事实上,打印机是依赖类型的“Hello World”应用程序),但实际上它们可以工作,因为编译器对它们进行了特殊处理,并且其机制是不可扩展的

你注定要在运行时确保正确的长度

我的最佳选择是使用结构对实际向量进行编码,并确保API级别的正确性,在与矩阵代数库交互时将它们映射到数组,然后在完成后将结果映射回结构,并提供大量断言

感谢OP提出了一个有趣的问题。我的回答频率下降不是因为不愿意帮忙,而是因为有几个问题引起了我的兴趣

F#中没有依赖类型,F#不支持带有数字类型参数的泛型(如C++)

但是,我们可以为不同的维度创建不同的类型,如
Dim1
Dim2
等等,并将它们作为类型参数提供

这将允许我们为
apply
提供一个类型签名,该签名将向量应用于如下矩阵:

let apply (m : Matrix<'R, 'C>) (v : Vector<'C>) : Vector<'R> = …
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
向量和矩阵可以这样实现

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
类型向量i维数
和'Dim:(新:单元->'Dim)
> () =
班
设dim=新的“dim()
让vs=Array.zero创建dim.Size
成员x.Dim=Dim
成员x.值=vs
结束
类型矩阵“RowDim”)
和'ColumnDim:>i尺寸
和“ColumnDim:(新:unit->”ColumnDim)
> () =
班
让rowDim=new'rowDim()
让columnDim=new'columnDim()
设vs=Array.zeroCreate(rowDim.Size*columnDim.Size)
成员x.RowDim=RowDim
成员x.ColumnDim=ColumnDim
成员x.值=vs
结束
最后,这允许我们编写如下代码:

let apply (m : Matrix<'R, 'C>) (v : Vector<'C>) : Vector<'R> = …
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
设m76=矩阵()
设v6=向量()
设v7=应用m76 v6//Vector
//未编译,因为v7的维度错误
让vv=应用m76 v7
如果你需要一个大范围的维度(因为你有一个增加/减少向量/矩阵维度的代数),你可以使用一些聪明的教会数字变体来支持这一点

我认为这是否可用完全取决于读者

附言


如果度量单位应用于比浮点更多的类型,则可能也可以用于此目的。

来自@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
打开MathNet.Numerics.linearlgebra
类型RealMatrix2x2=
|矩阵的实矩阵x2x2
静态成员私有createInternal(a:#seq)=
矩阵a |>实矩阵X2x2
静态成员创建
(
(a11,a12),
(a21、a22)
) = 
RealMatrix2x2.createInternal
[| 
[a11;a12]
[a21;a22]
|]
设m2=
(
(1., 2.),
(3., 4.)
)
|>RealMatrix2x2.create

元组签名和到
#seq
的“重新映射”可以很容易地使用Excel或任何其他方便的工具为任意多个维度生成代码。事实上,整个类以及任何其他必要的运算符重写(例如,
realmartix2x2x2
乘以
realmartix2x2x2
,…)都可以为所有必要的维度生成代码。

看看代码契约:ref是纯C,我们在这里讨论的是F。有些东西不能“公开”给网络。例如,已擦除的类型(=类型提供程序)和度量单位仅在F#中可见,但不能暴露于C#。如果您有F#代码示例,请您在这里发布。谢谢。我没有用F#试过。文档中说:“所有.NET Framework语言都可以立即利用契约;您不必编写特殊的解析器或编译器。”这些类型的依赖/约束类型需要解决方法。我想TPs是一种可能性,但你想沿着这条路走下去吗。。。有可能通过让你的构造函数私有化并进行一些验证,例如在C++ BTW中是可行的来强制执行这样的约束,但是我有一个想法,可以为矩阵和向量工作。不幸的是,我现在在工作,所以我需要等待前
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