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值连接结果。"
"With no args, returns the empty string. With one arg x, returns x.toString(). (str nil) returns the empty string. With more than one arg, returns the concatenation of the str values of the args."
"With no args, returns the empty string. With one arg x, returns x.toString(). (str nil) returns the empty string. With more than one arg, returns the concatenation of the str values of the args."
([] "")
([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/icon数组方法看起来有点巧妙的?

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

0
by

评论者:thheller

在之前

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

在之后

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

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

0
by

评论者:thheller

`
(defn str
"当没有参数时,返回空字符串。当有一个参数 x 时,返回x.toString()。当(str nil) 时返回空字符串。当参数多于一个时,返回参数的str值连接结果。"
"With no args, returns the empty string. With one arg x, returns x.toString(). (str nil) returns the empty string. With more than one arg, returns the concatenation of the str values of the args."
"With no args, returns the empty string. With one arg x, returns x.toString(). (str nil) returns the empty string. With more than one arg, returns the concatenation of the str values of the args."
([] "")
([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 次,11

应该内联多少个参数?

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 以类似的方式抱怨 variadic 函数分派代码,参见 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 级别上,调用某些东西(可能是只有未装箱数字?肯定不是字符串)会抛出 TypeError。这无疑是 Safari 的一个错误。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 *宏*,而没有解决字符串 *功能*,它仍然使用 {{js-str}} 并因此进行字符串连接。
# David 修复了 str *功能* 以使用 goog.string.buildString,该函数具有类似数组 join 的行为。现在即使在 Safari 6.0.5 上,行为也是正确的。
# Thomas 指出,buildString 以一种 v8 无法优化的方式使用参数,现在 str 函数(非宏)出现了性能下降。他建议直接使用 {{\[].join()}}。

因此,就这个问题有很多争论,这一切都是由于 Safari 6.0.5 中的一个错误造成的,没有人能够亲手重现因为这个错误,因为 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 年初期,然后停止了。我想这不会是很多人的数量。

从理论上讲,我可以在我的 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 添加了对 {{number?}} 的检查(带有注释)到 {{cljs.core/str$arity$1}}。数字情况应使用 js-str,其余使用 toString。我认为这会工作,但我们仍然无法测试--我们真的很需要一台 Safari 6.0.x 浏览器来测试。

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

评论者:dnolen

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

0
_评论者:favila_

关于我对这个问题的一些研究进展。

我创建了一个[替代 str 实现的 jsperf 测试|http://jsperf.com/cljs-core-str-possibilities],我正在试用。现在我只查看了一元 str。我发现了以下几点

* {{''+[x]}} 是 {{\[x].join('')}} 的更快替代品。
* 高级编译可以在编译时计算 {{''+[x]}},即使 {{x}} 是 boolean、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}} 而不是数组连接。)
0

评论者:thheller

抱歉,我有点分心。

@Francis:感谢你的总结。

我也没有可供使用的 Safari 6,但看起来不对,我们都得因为还有一小部分人仍在使用它(在我的网站上有 667 名用户,而网站总用户数超过 190k)而遭罪。我们没有解决方案,因为我无法测试它是否有效,我们可能尝试使用 String.concat。

"".concat(obj); // "42" "{{''+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](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实现的补丁(这在某些浏览器上至少更快一点),但我不知道它是否会在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(链接:一元情况)的试用,使用带switch的str而不使用toString。比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。

...