请在2024 Clojure状态调查中分享您的想法!

欢迎!请参阅关于页面获取更多关于如何使用本站的信息。

0
ClojureScript

示例

(str #js {"toString" (fn [] "hello") "valueOf" (fn [] 42)}) ; => "42"

问题在于ClojureScript使用连接来转换值到字符串,这对于覆盖了valueOf()方法的对象来说效果不好。

JavaScript中的示例

`
var obj = {

toString: function() { return 'hello'; },
valueOf: function() { return 42; }

};
console.log(String(obj)); => 'hello'
console.log(obj.toString()); => 'hello'
console.log('' + obj); => '42'
`

可能的解决方案可能是使用String()函数。使用toString()不会像这个问题的描述那样工作:http://dev.clojure.org/jira/browse/CLJS-847

35 个答案

0

评论者:dnolen

我们在master版本中切换到了{{goog.string.buildString}} https://github.com/clojure/clojurescript/commit/94eb8a960fef6aaca4ba44b251cefbfa04d0f6ac

0

评论者:nbeloglazov

是的,这可以工作。太棒了,谢谢!

0

评论者:thheller

抱歉重新打开。

我在对我的代码进行一些性能分析时,注意到性能输出中有关cljs.core/str的警告。

Chrome 报警:"未优化。参数值上下文值不正确",进一步查看goog.string.buildString的实现

goog.string.buildString = function(var_args) { return Array.prototype.join.call(arguments, ''); };

鉴于我们从未用它传递超过一个参数,这可能不是最佳实现选择。

或许可以跳过这个调用并在行内实现,类似于

`
(defn str
"无参数时,返回空字符串。有一个参数x时,返回
x.toString(). (str nil) 返回空字符串。有超过
一个参数时,返回参数str值的连接。"
([] "")
([x] (if (nil? x)

     ""
     (.join #js [x] "")))

([x & ys]

(loop [sb (StringBuffer. (str x)) more ys]
  (if more
    (recur (. sb  (append (str (first more)))) (next more))
    (.toString sb)))))

`

我没有理解这个问题的细节,但我们为什么不使用.toString呢?buildString.array方法看起来有点像 finnswork?

我对整体影响不太确定,但由于 cljs.core/str 在我的配置文件中显示得比较高,我认为这应该进一步调查。

0
by

评论者:thheller

之前

;;; str [], (str "1"), 1000000 runs, 254 msecs [], (str 1), 1000000 runs, 266 msecs [], (str nil), 1000000 runs, 80 msecs [], (str "1" "2" "3"), 1000000 runs, 753 msecs

之后

;;; str [], (str "1"), 1000000 runs, 82 msecs [], (str 1), 1000000 runs, 86 msecs [], (str nil), 1000000 runs, 79 msecs [], (str "1" "2" "3"), 1000000 runs, 242 msecs

但我只测试了V8,可能需要一些验证。

0
by

评论者:thheller

`
(defn str
"无参数时,返回空字符串。有一个参数x时,返回
x.toString(). (str nil) 返回空字符串。有超过
一个参数时,返回参数str值的连接。"
([] "")
([x1]

 (.join #js [x1] ""))

([x1 x2]

 (.join #js [x1 x2] ""))

([x1 x2 x3]

 (.join #js [x1 x2 x3] ""))

([x1 x2 x3 x4]

 (.join #js [x1 x2 x3 x4] ""))

...)
`

性能更好。

;;; str [], (str "1"), 1000000 runs, 40 msecs [], (str 1), 1000000 runs, 43 msecs [], (str nil), 1000000 runs, 96 msecs [], (str "1" "2" "3"), 1000000 runs, 117 msecs

应该内联多少个参数?

0
by

评论者:dnolen

我会接受最多4个,然后是可变参数。

0
by

评论者:thheller

在 cljs.core/str 宏和函数生成的代码之间有一些奇怪的交互。

宏生成

`
(str "hello" 1 "world" :yo nil)

结果是

[cljs.core.str("hello"),cljs.core.str((1)),cljs.core.str("world"),cljs.core.str(new cljs.core.Keyword(null,"yo","yo",1207083126)),cljs.core.str(null)].join('');
`

鉴于str有1个参数时将基本上展开为

[["hello"].join(""), ...]

我认为可以完全移除该宏,因为 cljs.core/str 会执行相同的操作,而 JIT 很可能足够智能以识别这一点(乃至编译时的 Closure)。

0
评论者:favila_

大括号引号。Chrome 抱怨:“未优化。参数值上下文不佳”,进一步观察 goog.string.buildString 的实现。

Chrome 以相同的方式抱怨关于可变参数函数分派代码,参见 CLJS-916 及补丁。

大括号引号。我认为可以完全移除该宏,因为 cljs.core/str 会执行相同的操作,而 JIT 很可能足够智能以识别这一点(乃至编译时的 Closure)。

Closure 编译器不够智能,无法移除中间数组,这就是为什么我提交了 CLJS-801(此问题已回滚)。我认为 JIT 也不能这样做。

我开始怀疑我们是否应该忽略 CLJS-847 中关于 Safari 6.0.5 问题的处理,该问题引发了一切字符串混乱。为了回顾

CLJS-801 已接受,移除了字符串宏中的 {{\[123, x].join('')}},转向 {{''\+123\+(cljs.core/str$arity$1 x)}} 样式代码, Closure 编译器可以预先计算。此时,一参数 cljs.core/str 函数(不是宏)对其参数调用 toString。
CLJS-847 已提交。在 Safari 6.0.5 的更高 JIT 级别中,对某些东西(可能是未装箱的数字?绝对不是字符串)调用 toString 会抛出一个 TypeError。这无疑是 Safari 的一个 bug。David 通过使一参数 cljs.core/str 函数调用 js-str 而不是 toString 来修复它。js-str 使用字符串拼接 {{''+x}}。
然而,这对于定义了 valueOf 的对象来说不适用(此票证的 issue),因为在 js 中 {{''+x}} 与 {{''+x.valueOf().toString()}} 相同,而不是 {{''+x.toString()}}。
David 考虑使用 {{String()}} 变种,但由于性能损失而拒绝。
David 回滚 CLJS-801,将字符串拼接回数组连接样式以修复。
Nikita 和我指出,回滚 CLJS-801 只修复了 str *宏*,而没有修复字符串 *函数*,该函数仍然使用 {{js-str}} 并使用字符串连接。
David 将 str *函数* 修复为使用 goog.string.buildString,它具有类似于 array.join 的行为。现在即使在 Safari 6.0.5 上,行为也是正确的。
托马斯指出,buildString 以一种 v8 无法优化的方式使用参数,因此 str 函数(不是宏)存在性能退化。他建议使用 {{\[].join()}} 直接。

所以在这个问题上有很多往返,这一切都源于 Safari 6.0.5 的一个 bug,而没有人能亲手重现,因为 Safari 6.0.5 已过时且很少见。为了从某个角度来说,Safari 6.0.x 只在 2012 年 7 月 25 日至 2013 年 6 月 11 日之间在 Lion 和 Mountain Lion 上可用。在 2012 年 7 月 25 日之前,Lion 使用 Safari 5.1.x,没有 Mountain Lion。在 2013 年 6 月 11 日,Lion 和 Mountain Lion 都切换到 Safari 6.1.x,它不受 toString TypeError bug 的影响(我检查过 – 我有一台装有 Lion 的 iMac)。唯一使用 Safari 6.0.5 的机器是(Mountain)Lion 机器,这些机器使用软件更新直到 2012 年后期至 2013 年初,然后停止。我想这不会是很多人。

从理论上看,我可以在我的 Lion 机器上运行 Safari 6.0.x 来实际测试这个问题,但我找不到从 6.1.x 回退的方法。

我认为有以下几种选择
使用数组的.join()进行所有字符串化处理,并接受性能损失(我们应该量化这一点)。添加注释说明这仅适用于Safari 6.0.x(仅在6.0.4和6.0.5上确认过,这仅是二手信息)以供后人为之奇怪的思考。
使用CLJS-801和toString(CLJS-847之前的现状),对于Safari 6.0.x忽略此问题。
使用CLJS-801,但在赛的.cljs.core/str$arity$1(针对Safari 6.0.5)中添加一个{{number?}}检查(附带注释)。在这种情况下应使用js-str,其余则使用toString。我认为这应该会起作用,但我们仍然无法测试--我们真的需要一台Safari 6.0.x的浏览器。

当然我们应该对这些方法进行基准测试,但我的直觉是,2比3快,比1快。
0
by

评论者:dnolen

我们不会忽视Safari 6.0.X。关于此票证的任何决策都将包括对其进行支持。

0
by
评论者:favila_

关于我在进行的一些研究的更新。

我创建了一个[不同str实现的jsperf测试|http://jsperf.com/cljs-core-str-possibilities],我现在正在尝试。目前为止,我只看了单参数的str。我发现了一些事情

星号 {{''+[x]}} 是比 {{\[x\].join('')}} 更快的替代方案。
* 加编译可以计算 {{''+[x]}},前提是{{x}}是布尔值、字符串、未定义、空或数字,即使是通过函数调用!也就是说,str_test.str_arr(123) 编译为 "123" 而不需要宏魔法。
但是,使用中间数组(即使是预分配的单例)仍然比旧的 {{\(if (nil? x) "" (.toString x))}} 慢。
使用switch语句的速度至少与str-tostr基线一样快,通常是更快的。
我99%确定所有这些实现(除了作为基线的str-tostr,这显然失败)在可怕的Safari 6.0.x上都能正常工作。如果有人有这个版本,请将其指向上面jsperf的链接并运行测试。我认为[Browserstack|http://www.browserstack.com]有这个版本的Safari。

我仍在调查变异性情况(str x y z a b c)。可能最好使用reduce而不是Stringbuffer+seq。(Stringbuffer现在只是做 {{''+x}} 而不是数组-join。)
0
by

评论者:thheller

抱歉,稍微偏离了主题。

@Francis: 感谢总结。

我也没有Safari 6,但似乎不正确,我们所有人都必须因为我们有少量份额仍然有这个问题(在我的网站上168/users中的667人)。我没有解决方案,因为我无法测试它是否有效,我们可能会尝试String.concat。

"".concat(obj); // "42" "".concat(obj, ""); // "hello" String.prototype.concat(obj, ""); // "hello" String.prototype.concat("", obj); // "hello"

但是不清楚 String.concat 是否有效,并且它在 valueOf 方面表现异常。

http://jsperf.com/js-string-concat-variants

性能测试结果仍然不明确,因为 Firefox 好像在作弊。

0

评论者:favila

使用 Safari 6.0.5 和 Browserstack 测试了 jsperf,结果已公布。

注意:我无法复现 CLJS-847,因为 str-tostr 并没有像预期的那样失败。现在有了浏览器来测试,我会更加努力。

0

评论者:favila

仍然无法复现 CLJS-847。

(链接:https://dl.dropboxusercontent.com/u/18386249/code/strtest.html 文本:这个脚本)包括我尝试的最小复现案例。我的理论是,在更高的 JIT 级别上某些类型可能会失败。我没有找到任何失败的案例。我还尝试在类型之间来回切换,并且一次使用一个类型,但仍然没有失败。

在(链接:https://github.com/wycats/handlebars.js/pull/440 文本:这个线程)中,我发现(链接:https://dl.dropboxusercontent.com/u/18386249/code/handlebar_str_typeerror.html 文本:这个“最小”脚本),帖子作者称他可以使其可靠地失败。我无法使其失败。但是,原始帖子是2013年2月15日发布的,这意味着他所使用的 Safari 必须是6.0.2或更低版本。

假设

  1. 该错误不影响6.0.5,但可能影响6.0.4或更低版本。
  2. BrowserStack 的系统以某种方式缓解了该错误,这意味着我们需要一个“真实”的 Lion Safari 6.0.x 进行测试。
  3. 这些测试仅在正确的月亮阶段下才会失败。

因此,我可以编写一个使用 str-switch 实现的补丁来修复 str(这在某些浏览器上至少更快一些),但我不知道它是否可能在 Safari 6.0.5 上失败。我只知道它目前工作正常。CLJS-801也可以安全重新应用,因为所有问题的关键原因是 cljs.core/str 函数的 1-arity 实现方式。

我还向 Kevin 寻求帮助,以解决 CLJS-847 问题。(Kevin 是 Safari 6.0.x 问题的原始报告人。)

0

评论者:favila

对变元进行了(链接:http://jsperf.com/cljs-core-str-variadic-possibilities 文本:jsperf 的测试)。Chrome 好像真的更喜欢 IReduce 到 seq+stringbuilder 的向量(未测试其他集合),但其他浏览器上没有区别或轻微的减速。不清楚这是否值得。

此外还更新了(链接:[http://jsperf.com/cljs-core-str-possibilities/2](http://jsperf.com/cljs-core-str-possibilities/2),文本:一元情况),使用switch语句和 never 使用 toString 构造字符串。在 Chrome 上比使用 switch 或 toString 缓慢近 50%,在 Safari 上更小。

在安全性方面,str-switch-no-tostr 完全不使用 toString,因此可能更安全。然而,我认为 str-switch 可能也会工作,而且速度会快得多。不过,我无法在 Safari 6.0.5 中得到任何 TypeError,因此这还是一个未知数。

我建议新 str 如下(不使用 reduce,但可以)

`
(defn str
([x]
(case (js "typeof ~{}" x)
"string" x
"object" (if (identical? nil x) "" (.toString x))
("boolean" "number") (js-str x)
"undefined" ""
(js-str #js [x]))) ;; 针对Safari 6.0.x TypeError错误的保护.
([a b] (js
"~{}+~{}" (str a) (str b)))
([a b c] (js "~{}+~{}+~{}" (str a) (str b) (str c)))
([a b c d] (js
"~{}+~{}+~{}+~{}" (str a) (str b) (str c) (str d)))
([a b c d & 多余项]
(loop [s (str a b c d) [e f g h & r] 多余项]
(let [s' (js* "~{}+~{}+~{}+~{}+~{}" s e f g h)]

(if (nil? r)
 s'
 (recur s' r))))))

`

0
by

评论者:favila

这是一个可能的补丁的第一个版本,它能解决这个问题,同时不会破坏 CLJS-847。应该等待确认,确保它不会破坏 Safari 6.0.x。

...