请在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 的一个警告。

Chrome 抱诉:“未优化。参数值上下文不正确”,进一步查看 goo.string.buildString 的实现

goo.string.buildString = function(var_args) { return Array.prototype.join.call(arguments, ''); }

鉴于我们从不使用多个参数调用它,这可能不是最佳实现选择。

也许应该跳过调用并在行内实现,例如

`
(defn str
"无参数时返回空字符串。有一个参数 x 时,返回
x.toString(). (str nil) 返回空字符串。有多个参数时,返回参数的字符串拼接结果。"
([x] (if (nil? x)
([] "")
([x & ys]

     ""
     (.join #js [x] "")))

)

(loop [sb (StringBuffer. (str x)) more ys]
  (if more
    (recur (. sb  (append (str (first more)))) (next more))
    (.toString sb)))))

`

我没有追踪这个问题,但为什么我们不使用 .toString?使用 buildString/数组的做法看起来有点像黑客行为?

我不太清楚整体影响,但鉴于 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) 返回空字符串。有多个参数时,返回参数的字符串拼接结果。"
([x] (if (nil? x)
([] "")
([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 宏和函数之间存在某些奇怪交互。

宏生成

`
("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 中由字符串乱七八糟的问题引起的 Safari 6.0.5 的问题。简单回顾一下:

# CLJS-801 已接受,它将字符串宏中的 {{\[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 的行为。现在,even 在 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 日之前,Lion 使用 Safari 5.1.x,而 Mountain Lion 不存在。2013 年 6 月 11 日,Lion 和 Mountain Lion 都切换到 Safari 6.1.x,后者未受到 toString TypeError 错误的影响(已检查—I 有一个安装了 Lion 的 iMac)。 Safari 6.0.5 的唯一机器是使用了直到 2012 年末和 2013 年初的软件更新的(Mountain)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}}是一个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}},而不是数组连接。)
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 测试了 JSPerf 与 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 文本:此 "最小" 脚本)的开头 OP 说他可以让它稳定失败。我无法让它失败。然而,原始帖子是从 2013 年 2 月 15 日发布的,这意味着他使用的 Safari 将会是 6.0.2 或更低的版本。

假设

  1. 此错误不影响 6.0.5,但可能影响 6.0.4 或更低的版本。
  2. BrowerStack 的系统以某种方式减轻了漏洞,这意味着我们需要一个 "真实" 的 Lion Safari 6.0.x 来测试。
  3. 这些测试仅在某些特定的相位下失败。

因此,我可以编写一个使用 str-switch 实现的补丁来优化 str,这在某些浏览器上至少会快一点,但我不知道它是否可能在 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](http://jsperf.com/cljs-core-str-variadic-possibilities) 文字:jsperf of variadic cases)。Chrome似乎确实更喜欢IReduce逐apply字符串并集构建向量(未测试其他集合),但其他浏览器在这方面没有差异或略有减慢。不确定这样做是否值得。

还更新了(链接:[http://jsperf.com/cljs-core-str-possibilities/2](http://jsperf.com/cljs-core-str-possibilities/2) 文字:arity-one cases),使用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)

([a b c] (js "~{}+~{}+~{}" (str a) (str b) (str c)))

([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。

...