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 投票

评论者:thheller

在之前

;; str [], (str "1"), 1000000 次运行,254 毫秒 [], (str 1), 1000000 次运行,266 毫秒 []

之后

;; str [], (str "1"), 1000000 次运行,82 毫秒 [], (str 1), 1000000 次运行,86 毫秒 []

但我只测试了 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 毫秒 []

应该内联多少个参数?

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 带一个参数将基本上展开为

[[["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 中的错误。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,它具有 array.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 更快,1 最快。
0 投票

评论者:dnolen

我们不会忽视 Safari 6.0.X。关于此票据的决定将包括支持它。

0 投票
_评论者:favila_

我对这个问题的研究进展。

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

* {{''+[x]}} 是 {{[x].join('')}} 的更快的替代品。
* 高级编译能在编译时计算 {{''+[x]}},如果 {{x}} 是 bool、str、undefined、null 或数字,即使有函数调用!即 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" "".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 文本:这个“最小”脚本),OP说,他可以让它可靠地失败。我做不到。然而,原始帖子是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发表的评论:

制作了一个关于变长案例的(链接:http://jsperf.com/cljs-core-str-variadic-possibilities 文本:jsperf)。Chrome似乎真的更喜欢使用IReduce而不是vec+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 & 更多])
(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。

...