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的警告。

Chromes 抱怨: "未优化。参数值上下文值不佳",进一步查看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
答:

评论由:thheller发表

之前

;;; str [], (str "1"), 1000000次运行,254毫秒 [],(str 1),1000000次运行,266毫秒 [],(str nil),1000000次运行,80毫秒 [],(str "1" "2" "3"),1000000次运行,753毫秒

之后

;;; str [], (str "1"),1000000次运行,82毫秒 [],(str 1),1000000次运行,86毫秒 [],(str nil),1000000次运行,79毫秒 [],(str "1" "2" "3"),1000000次运行,242毫秒

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

0
答:

评论由: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次运行,40毫秒 [],(str 1),1000000次运行,43毫秒 [],(str nil),1000000次运行,96毫秒 [],(str "1" "2" "3"),1000000次运行,117毫秒

应该内联多少个参数?

0
答:

评论由:dnolen发表

我同意最多4个参数后变成可变参数。

0
答:

评论由:thheller发表

代码生成的代码和函数之间有一些奇怪的反应。

宏生成的

`
(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 *宏*,而不是str *函数*,后者仍然使用{{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 TypeError错误的影响(我已经验证过——我有一台装备Lion的iMac)。那些仍然运行在Safari 6.0.5上的机器是(Mountain)Lion 机器,这些机器使用了直到2012年底到2013年初的软件更新,然后停止了。我无法想象这会影响到大量人群。

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

我认为选择有
uso de 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 添加一个 {{number?}} 检查(带注释)到 {{cljs.core/str$arity$1}}。数字情况应使用 js-str,其余的用 toString。我觉得这样可以工作,但我们还是没有任何方法可以测试——我们真需要一台 Safari 6.0.x 浏览器。

当然我们应该基准测试这些方法,但我的直觉是2比3快,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}} 是 bool、str、undefined、null 或 number,即使通过函数调用!即 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}} 而不是 array-join。)
0
by

评论由:thheller发表

抱歉,我稍微走了一下神。

@Francis: 谢谢你的总结。

我也没有 Safari 6,但看来我们不应该因为还有一小部分人还在使用它(我的网站上 190k+ 用户中有 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进行了测试,结果已经在上传。

注意,我无法重现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实现(在有些浏览器上至少快一些),但我不知道它是否可能在Safari 6.0.5上失败。我只知道到目前为止它是有效的。CLJS-801也应该可以安全再次应用,因为所有问题的基础都是cljs.core/str函数的1-arity实现。

我还在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 文本:单一参数情况)并使用了一个使用switch从未使用toString的str。在Chrome上比使用switch或toString慢了近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错误。
([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。

...