F#中是否存在带键的记录类型

Does record type with key exist in F#

在 OPL(优化编程语言)中,我们有一个数据结构名称 tuple。 OPL元组对应F#中的Record。它是这样定义的:

tuple Point {
       int x;
       int y;
    };

像在 F# 中一样,我们可以使用点表示法访问字段

int x = p.x;

我们可以将元组分组到一个集合中:

{Point} points = {<1,2>, <2,3>};

不同之处在于,就像在数据库系统中一样,元组结构可以与键相关联。元组键允许使用一组唯一标识符访问以元组组织的数据。在以下示例中,护士元组使用字符串类型的键名声明。

tuple nurse {
  key string name;
  int seniority;
  int qualification;
  int payRate;
}
{ nurse } nurses = …;

键的好处在于我们可以用这种方式初始化数组

int NumberOfChild [n in nurses] = 0;

并仅使用键访问值:

NumberOfChild[<"Isabelle">]=20;

省略没有键的字段。这相当于:

NumberOfChild[<"Isabelle",3,1,16>]=20;

此外,使用键意味着不会有两个具有相同键的元组。就像数据库中的主键。

问题是:F# 中是否存在这样的类型?用密钥记录?

我的目标: 我想定义一个有很多属性的节点结构。并通过仅提供节点的键而不是整个记录来加载图形结构,因为我将从数据库加载图形。

type Node = {
    nodeKey : int;
    nodeName : string;
    nodeAttribute1 : string;
    nodeAttribute2 : string }

let Graph = [
    (1, 2);
    (1, 3); 
    (2, 4);
    (3, 4) ]

其中图元组中的整数表示nodeKey.
我想使用图表进行操作,但仅使用密钥访问节点信息。

OPL Grammar

不,没有这样的语言级概念。可以说,所有记录字段都是平等创建的。

这并不妨碍您:

  1. 根据一个或多个字段值为记录合成一个键,
  2. 使用这样的键作为 Map 中的键来保存您的记录或任何其他值。

所以你可以有这样的东西:

type Nurse = { name: string; seniority: int; qualification: int; payRate: int }

let nurses = [ { name = "Isabelle"; seniority = 3; qualification = 1; payRate = 16 } ]

let numberOfChildren = 
    [ "Isabelle", 20 ]
    |> Map.ofSeq

let nursesWithNumberOfChildren = 
    [ for nurse in nurses do 
        match numberOfChildren |> Map.tryFind nurse.name with
        | Some children -> yield nurse, children
        | None -> yield nurse, 0 ]

使用类似的方法,您可以分离图形和节点数据 - 仅在图形中存储键并维护从键到完整节点记录的映射。

//If I read data from a database, I would receive the data in the following form:

type XYZ = {X:int;
            Y:string;
            Z:float} 

let recordsXYZ = [{X=1;Y="A";Z=1.0};{X=2;Y="b";Z=1.0};{X=3;Y="A";Z=1.0}]

//I can create a map this way
let mapXYZ1=recordsXYZ|>Seq.groupBy (fun a ->a.X)|>Map.ofSeq
//But I don't want a Map<int,seq<XYZ>>
//This is what I want
let mapXYZ2=recordsXYZ|>Seq.map (fun a -> (a.X,{X=a.X;Y=a.Y;Z=a.Z}))|>Map.ofSeq

//Or maybe this is cleaner but this need to define another type
type YZ = {Y:string;
            Z:float}
let mapXYZ3=recordsXYZ|>Seq.map (fun a -> (a.X,{Y=a.Y;Z=a.Z}))|>Map.ofSeq

如果我没理解错的话,您最好的选择只是 Seq.groupBy 的一个更简洁的替代方案,以达到您的目的。这是它的核心,一行:

let inline project projection value = projection value, value

给定一个简单的辅助函数,不特定于 XYZ

let projectToMap projection values = values |> Seq.map (project projection) |> Map.ofSeq

从任何 "key":

干净地创建 XYZ 的地图变得微不足道
let mappedByX  = xyzs |> projectToMap (fun { X=x }      -> x)    // Map<int, XYZ>
let mappedByY  = xyzs |> projectToMap (fun { Y=y }      -> y)    // Map<string, XYZ>
let mappedByZY = xyzs |> projectToMap (fun { Y=y; Z=z } -> z, y) // Map<float*string, XYZ>

Online Demo