请在2024 Clojure 调查问卷中分享您的想法!

欢迎!请查看关于页面以获取更多有关如何使用本页的信息。

0
ClojureScript

示例

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

问题在于ClojureScript使用连接操作将值转换为字符串,这在与valueOf()方法被覆盖的对象上表现不佳。

js中的示例

`
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的方式看起来有点蹩脚?

我不太确定整体影响,但鉴于.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带有一个参数将基本展开为

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

我认为完全可以删除宏,因为cljs.core/str将执行相同的功能,JIT可能足够智能以解决这个问题(或者甚至Closure在编译时能够做到)。

0
评论者:favila_

bq. Chrome出现错误:“未优化。参数值上下文错误”,进一步查看goog.string.buildString实现。

Chrome对相同的可变参数函数分发代码也有类似的抱怨,参见CLJS-916及补丁。

bq. 我认为完全可以删除宏,因为cljs.core/str会做同样的事,JIT可能足够智能以解决这个问题(或者甚至在编译时Closure也能做到)。

Closure编译器不够智能以删除中间数组,这就是为什么我提交了CLJS-801(这是此次提交回滚的原因)。我相信JIT也无法做到这一点。

我开始怀疑是否应该忽略CLJS-847中提到的Safari 6.0.5问题,这个问题引发了一系列字符串问题。总结一下

# CLJS-801已被接受,它删除了str宏中的{{\[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的对象来说会失效(当前议题中的问题),因为在js中{{''+x}}与{{''+x.valueOf().toString()}}相同,而不是{{''+x.toString()}}。
# David考虑使用{{String()}}和变体,但由于性能下降而拒绝。
# David回滚CLJS-801,从字符串连接回数组连接风格进行修复。
# Nikita和我指出,回滚CLJS-801仅修复了str *宏*,而没有修复string *函数*,后者仍然使用{{js-str}}和因此字符串连接。
# David修复了str *函数*使用goog.string.buildString,它具有array.join的行为。现在,即使在Safari 6.0.5上,行为也是正确的。
# Thomas指出,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 TypeErrorbug的影响(我已经检查过——我有一台装有Lion的iMac)。只有使用Safari 6.0.5的机器是(Mountain)Lion机器,它们使用软件更新直到2012年末至2013年初,然后停止。我想不出这会是一个大量的人。

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

我认为有几种选择
使用 `array.join()` 来进行所有字符串化操作,并承担性能损失(我们应该对其进行量化)。为后人留下注释,指出这仅适用于 Safari 6.0.x(仅在 6.0.4和 6.0.5 上间接确认)以避免他们对此感到奇怪。
使用 CLJS-801 和 toString(在 CLJS-847 之前的状态),对于 Safari 6.0.x 忽略此问题。
使用 CLJS-801,但对 Safari 6.0.5 上的 {{cljs.core/str$arity$1}} 添加一个 {{number?}} 检查(带注释)。数值情况应使用 js-str,其余使用 toString。我认为这会起作用,但我们再次没有测试的方法——我们真的需要使用 Safari 6.0.x 浏览器。

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

评论者为:dnolen

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

0
评论者:favila_

关于我正在研究的一些成果的更新。

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

* 使用 {{''+[x]}} 代替 {{[x].join('')}} 是一个更快的替代方案。
* 高级编译可以在 {{x}} 是 bool、str、undefined、null 或 number 时在编译时计算 {{''+[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}} 而不是数组连接。)
0

评论者为:thheller

抱歉,稍微走了一下弯路。

@Francis:感谢总结。

虽然没有可用的Safari 6,但认为所有人都得忍受这种情况似乎是不对的,因为我们网站上有少数用户(190,000多人中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

使用Browserstack用Safari 6.0.5测试了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的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 文本:一元案例)使用 str 和 switch,并永不使用 toString。在 Chrome 上几乎慢了 50%,在 Safari 上更少。

从安全性的角度看,str-switch-notostr 完全不使用 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 bug 的保险。
([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 & more]
(loop [s (str a b c d) [e f g h & r] more]
(let [s' (js* "~{}+~{}+~{}+~{}+~{}" s e f g h)]

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

`

0

评论者:favila

这是解决此问题同时不会打破 CLJS-847 的可能的补丁的第一次尝试。应等待确认这不影响 Safari 6.0.x。

...