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

问题描述 投票:2回答:3

我有一些科学项目。那里有各种长度的矢量/方形矩阵。显然(例如)长度为2的向量不能添加到长度为3的向量中(依此类推)。有几个.NET库,它们处理向量/矩阵。所有这些都有通用的载体/矩阵或者有一些非常特殊的载体/矩阵,它们不能满足需要。

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

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

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

非常感谢!

f# type-providers
3个回答
5
投票

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

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

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

这将允许我们为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。

如果它们适用于比浮子更多的类型,也许也可以使用单位措施。


2
投票

你要找的东西的总称是dependent types,但F#不支持它们。

我已经看到an experiment在使用类型提供程序来模仿依赖类型的一种特定风格(约束基本类型的域),但我不希望它可以使用当前形式的类型提供程序来实现你想要的。他们似乎过于异想天开。

打印格式字符串似乎正在这样做(实际上打印机是依赖类型的“Hello World”应用程序),但实际上它们是有效的,因为它们得到编译器的特殊处理,并且其机制不可扩展。

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

我最好的选择是使用结构来编码实际向量并确保API级别的正确性,将它们映射到与这些矩阵代数库交互的点上的数组,然后将结果映射回具有充足断言的结构完成后。


0
投票

来自@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,......)可以是为所有必要维度生成的代码。

© www.soinside.com 2019 - 2024. All rights reserved.