如何使用通用字段名将 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,我可以使用 sequtils
的 mapIt
函数迭代 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”
我正在构建一个 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,我可以使用 sequtils
的 mapIt
函数迭代 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”