具体来说:我正在尝试使用 Julia 的 DataFrames 包,特别是带有 names 选项的 readtable(( 函数,但这需要一个符号向量。
- 什么是符号?
- 为什么他们会选择它而不是字符串向量?
到目前为止,我只找到了少数几个在朱莉娅语言中提到符号这个词。似乎符号由":var"表示,但我远不清楚它们是什么。
旁白:我可以跑
df = readtable( "table.txt", names = [symbol("var1"), symbol("var2")] )
我的两个项目符号问题仍然有效。
Julia 中的符号与 Lisp、Scheme 或 Ruby 中的符号相同。然而,在我看来,这些相关问题的答案并不令人满意。如果你读过这些答案,似乎符号与字符串不同的原因是字符串是可变的,而符号是不可变的,符号也是"被拘留"的——不管这意味着什么。字符串在 Ruby 和 Lisp 中确实是可变的,但它们在 Julia 中不是,这种差异实际上是一个红鲱鱼。符号被扣留的事实——即由语言实现进行哈希处理以进行快速相等比较——也是一个无关紧要的实现细节。你可以有一个不实习符号的实现,语言将完全相同。
那么,符号到底是什么呢?答案在于 Julia 和 Lisp 的共同点——将语言代码表示为语言本身的数据结构的能力。有些人称之为"同象性"(维基百科(,但其他人似乎并不认为仅凭这一点就足以使一种语言成为同象。但术语并不重要。关键是,当一种语言可以表示自己的代码时,它需要一种方法来表示赋值、函数调用、可以写成文字值的东西等。它还需要一种方法来表示自己的变量。也就是说,您需要一种方法来表示 - 作为数据 - 左侧的foo
:
foo == "foo"
现在我们进入问题的核心:符号和字符串之间的区别是比较左侧foo
和右侧"foo"
之间的差异。在左侧,foo
是一个标识符,它的计算结果是绑定到当前作用域中变量foo
的值。在右侧,"foo"
是一个字符串文本,它的计算结果为字符串值"foo"。Lisp 和 Julia 中的一个符号是将变量表示为数据的方式。字符串只是表示自身。您可以通过对它们应用eval
来查看差异:
julia> eval(:foo)
ERROR: foo not defined
julia> foo = "hello"
"hello"
julia> eval(:foo)
"hello"
julia> eval("foo")
"foo"
符号:foo
的计算结果取决于变量foo
绑定到什么(如果有的话(,而"foo"
总是只计算为"foo"。如果你想在Julia中构造使用变量的表达式,那么你正在使用符号(无论你是否知道(。例如:
julia> ex = :(foo = "bar")
:(foo = "bar")
julia> dump(ex)
Expr
head: Symbol =
args: Array{Any}((2,))
1: Symbol foo
2: String "bar"
typ: Any
转储的东西表明,除其他外,表达式对象内部有一个:foo
符号对象,您可以通过引用代码foo = "bar"
获得。下面是另一个示例,使用存储在变量sym
中的符号构造表达式:foo
:
julia> sym = :foo
:foo
julia> eval(sym)
"hello"
julia> ex = :($sym = "bar"; 1 + 2)
:(begin
foo = "bar"
1 + 2
end)
julia> eval(ex)
3
julia> foo
"bar"
如果在sym
绑定到字符串"foo"
时尝试执行此操作,则不起作用:
julia> sym = "foo"
"foo"
julia> ex = :($sym = "bar"; 1 + 2)
:(begin
"foo" = "bar"
1 + 2
end)
julia> eval(ex)
ERROR: syntax: invalid assignment location ""foo""
很明显,为什么这不起作用 - 如果您尝试手动分配"foo" = "bar"
,它也不会起作用。
这是符号的本质:符号用于表示元编程中的变量。当然,一旦您将符号作为数据类型,就很容易将它们用于其他事情,例如作为哈希键。但这是对具有另一个主要目的的数据类型的偶然机会主义用法。
请注意,不久前我不再谈论 Ruby。这是因为 Ruby 不是同象的:Ruby 不会将其表达式表示为 Ruby 对象。所以 Ruby 的符号类型是一种退化的器官——一种遗留下来的改编,继承自 Lisp,但不再用于其原始目的。Ruby 符号已被选用于其他目的 - 作为哈希键,将方法从方法表中提取出来 - 但 Ruby 中的符号不用于表示变量。
至于为什么在数据帧中使用符号而不是字符串,这是因为将列值绑定到用户提供的表达式中的变量是数据帧中的常见模式。因此,列名是符号是很自然的,因为符号正是您用来将变量表示为数据的符号。目前,您必须编写df[:foo]
才能访问foo
列,但将来,您可以改为df.foo
访问它。如果可能,则只有名称为有效标识符的列才能使用此方便的语法进行访问。
另请参阅:
- https://docs.julialang.org/en/v1/manual/metaprogramming/
- 像Elixir和Julia这样的语言在什么意义上是同象的?
参考目前的原始问题,即 0.21 版本(以及将来(DataFrames.jl 允许将Symbol
s 和字符串用作列名,因为支持两者不是问题,并且在不同情况下,用户可能更喜欢Symbol
或字符串。
下面是一个示例:
julia> using DataFrames
julia> df = DataFrame(:a => 1:2, :b => 3:4)
2×2 DataFrame
│ Row │ a │ b │
│ │ Int64 │ Int64 │
├─────┼───────┼───────┤
│ 1 │ 1 │ 3 │
│ 2 │ 2 │ 4 │
julia> DataFrame("a" => 1:2, "b" => 3:4) # this is the same
2×2 DataFrame
│ Row │ a │ b │
│ │ Int64 │ Int64 │
├─────┼───────┼───────┤
│ 1 │ 1 │ 3 │
│ 2 │ 2 │ 4 │
julia> df[:, :a]
2-element Array{Int64,1}:
1
2
julia> df[:, "a"] # this is the same
2-element Array{Int64,1}:
1
2