如何使用通用字段名将 seq[type] 映射到 seq[type.field]?

How to map seq[type] to seq[type.field] with a generic fieldname?

我正在构建一个 Web 应用程序并使用一个名为 norm 的 ORM。因此,我有一个包含一堆表的 SQL 数据库,所有这些表都对应于我在 nim 中的各种模型。通常情况下,它们处于多对多关系中,例如:

type 
  A = ref object of Model
    name: string
  B = ref object of Model
    anotherName: string
  C = ref object of Model
    myA: A
    myB: B

由于目前实施规范的方式,如果我想要给定 A 的所有条目 B 或给定 B 的所有条目 A,我必须查询 C。

当我真正想要 seq[A] 或 seq[B] 时,我得到了 seq[C]。

当然,对于一组特定的 A、B 和 C,我可以使用 sequtilsmapIt 函数迭代 seq:

import sequtils
let myASeq: seq[A] = myCSeq.mapit(it.myA)

但考虑到我有大约 20 个这样的关系,我不想在那里实现相同的事情 20 次。我更喜欢一种通用的方式,我可以在其中以某种方式输入 fieldName(例如 myA)并即时解析它。我怎样才能做到这一点?

感谢 nim discord 服务器上非常乐于助人的人们(向 ElegantBeef 大喊大叫),他们教会了我这方面的知识。您可以通过两种方式解决此问题:

1。如果在写代码的时候知道字段名,可以使用模板

这是首选,因为模板比宏更容易理解。
import norm/[model, pragmas]
import std/[sequtils, typetraits, macros, json] 

template mapModel[T: Model](mySeq: seq[T], field: untyped): seq[untyped] = mySeq.mapIt(it.field)

用法示例:

type
    A = ref object of Model
        name: string
    D = ref object of Model
        myothernameid: string
        myDA: A

var myDSeq: seq[D] = @[]
let anA: A = A(name: "this is an A")
myDSeq.add(D(myothernameid: "la", myDA: anA))
myDSeq.add(D(myothernameid: "le", myDA: anA))

echo %*myDSeq # [{"myothernameid":"la","myDA":{"name":"this is an A","id":0},"id":0},{"myothernameid":"le","myDA":{"name":"this is an A","id":0},"id":0}]

let myASeq: seq[A] = mapModel(myDSeq, "myDA")

echo %*myASeq # [{"name":"this is an A","id":0},{"name":"this is an A","id":0}]

2.如果你在编译时只知道特定字段的名字是一个静态字符串,你可以使用宏

如果您只能在编译时访问字段名称而不能访问字段本身,则此方法很有用,因为根据 when 条件,这可能会有所不同。

import norm/[model, pragmas]
import std/[sequtils, typetraits, macros, json] 

macro mapModel[T: Model](mySeq: seq[T], field: static string): untyped =
    newCall(bindSym"mapIt", mySeq, nnkDotExpr.newTree(ident"it", ident field))

用法示例:

var myDSeq: seq[D] = @[]
let anA: A = A(name: "this is an A")
myDSeq.add(D(myothernameid: "la", myDA: anA))
myDSeq.add(D(myothernameid: "le", myDA: anA))

echo %*myDSeq # As Before

let myASeq: seq[A] = mapModel(myDSeq, "myDA")

echo %*myASeq # As Before

为了解释宏的作用,我自己并不完全了解它,但写下我从与 ElegantBeef 聊天中得到的内容:

newCall --> 您即将收到要执行的东西,我将其称为可调用的,执行此操作

bindSym"mapIt" --> 调用绑定到符号“mapIt”的函数,并使用以下参数

mySeq --> 传递给“mapIt”符号函数的第一个参数

nnkDotExpr.newTree --> 这将是一个执行树或任何他们称之为的东西,它解析为一个可调用对象,例如 proc 或 func。特别是一个可调用的点表达式。

ident"it" --> 具有符号“it”的变量的值,这是由“mapIt”函数提供的

ident field --> 具有包含在字段变量中的符号的变量的值。

nnkDotExpr.newTree(ident"it", ident field) --> 在其上调用点表达式以获取其字段的值,该字段由包含在字段中的名称组成。这相当于 it.la 如果字段包含“la”