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

代码生成器生成的 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_

bq. Chrome 抱怨“未优化。错误的上下文字段值”,进一步查看goog.string.buildString的实现。

Chrome 同样以同样的方式对可变函数分派代码进行抱怨,参见CLJS-916及补丁。

bq. 我认为可以安全地完全删除宏,因为 cljs.core/str 会做同样的事情,JIT 可能足够聪明以识别这一点(或者编译时甚至 Closure)。

Closure 编译器不够聪明以删除中间数组,因此我创建了 CLJS-801(该票券回滚了)。我认为 JIT 也无法做到这一点。

我开始怀疑是否应该忽略从柳(CLJS-847)开始的字符串问题。总结一下:

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 中的一个错误。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 *函数*,以使用具有 array.join 行为的 goog.string.buildString。现在即使在 Safari 6.0.5 上,行为也是正确的。
Thomas 指出,buildString 以一种 v8 无法优化的方式使用参数,因此 str 函数(不是宏)出现了性能回退。他建议直接使用 {{\[].join()}}。

因此,这个问题的很多往返都是因为这个 Safari 6.0.5 中的错误,而没有人能够亲自复制这个错误,因为 Safari 6.0.5 已经过时且很少见。作为参考,Safari 6.0.x 仅在狮(Lion)和山猫(Mountain Lion)操作系统上存在于 2012 年 7 月 25 日到 2013 年 6 月 11 日期间。在 2012 年 7 月 25 日之前,狮子使用 Safari 5.1.x,没有山猫。2013 年 6 月 11 日,狮子和山猫都切换到 Safari 6.1.x,它不 suffer 从 toString TypeError 错误(我已经检查过——我有一台带狮子的 iMac)。唯一上 Safari 6.0.5 的机器是(Mountain)狮子机器,它们使用了直到 2012 年底或 2013 年初的软件更新,然后停止。我想这不会是大量的人。

理论上,我可以在我的狮子机器上运行 Safari 6.0.x 来实际测试这一点,但我找不到从 6.1.x 回退的方法。

我觉得选择有
这使用了数组的 .join() 方法来执行字符串转换,并且付出了性能的代价(我们应该量化这一点)。不超过一个注释表明这是仅针对 Safari 6.0.x(仅在六个0.4和六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 浏览器。

当然,我们应该对这些方法进行基准测试,不过我的直觉是第二个方法比第三个方法要快,第三个方法比第一个方法要快。
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)。可能最好不用 Stringbuffer+seq,而是用 reduce 替换。因为 Stringbuffer 现在只是一个简单的 {{''+x}},而不是数组连接。
0

评论者: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,结果在下面。

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

0

评论者:favila

仍然无法复现CLJS-847。

在(链接: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寻求过帮助。(Kevin是Safari 6.0.x问题的原始报告人。)

0

评论者:favila

还制作了一个关于可变参数的jsperf(链接:http://jsperf.com/cljs-core-str-variadic-possibilities 文本:)。Chrome似乎非常倾向于为向量使用IReduce而不是seq+stringbuilder(未测试其他集合),但在其他浏览器上没有差异或只有轻微的降速。不确定是否值得。

还更新了arity-one案例(链接: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
by

评论者:favila

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

...