请在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报错:“未优化。参数值上下文不正确”,进一步查看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呢?这种构建字符串为数组的做法似乎有些不正规?

我对整体影响不太确定,但鉴于.cljs.core/str在我的配置文件中排名很高,我认为需要进一步调查。

0投票

评论由:thheller发表

在之前

;;; str [], (str "1"), 1000000 runs, 254 msecs [], (str 1), 1000000 runs, 266 msecs [], (str nil), 1000000 runs, 80 msecs [], (str "1" "2" "3"), 1000000 runs, 753 msecs

在之后

;;; str [], (str "1"), 1000000 runs, 82 msecs [], (str 1), 1000000 runs, 86 msecs [], (str nil), 1000000 runs, 79 msecs [], (str "1" "2" "3"), 1000000 runs, 242 msecs

但我只测试了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 runs, 40 msecs [], (str 1), 1000000 runs, 43 msecs [], (str nil), 1000000 runs, 96 msecs [], (str "1" "2" "3"), 1000000 runs, 117 msecs

应该内联多少个参数?

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('');
`

由于带有1个参数的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 无法 optimizer 的方式使用参数,现在 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)。只有 (Mountain) Lion 机器上运行的是 Safari 6.0.5,它们使用软件更新直到 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 比 1 快,1 比 3 快。
0投票

评论由:dnolen发表

我们不会忽视 Safari 6.0.X。关于这个票证的任何决定都将包括对其的支持。

0投票
_评论者:favila_

我对这项研究所做的更新。

我创建了一个替代 str 实现的 [jsperf 测试|http://jsperf.com/cljs-core-str-possibilities],正在尝试使用。现在我只为单参数 str 进行了检查。我发现了一些事情

* {{''+[x]}} 是 {{\[x\].join('')}} 更快的替代方案。
* 如果 x 是布尔值、字符串、未定义、空值或数字,即使在函数调用中,高级编译也可以在编译时计算 {{''+[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)。可能最好使用 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

使用 Safari 6.0.5 和 Browserstack 进行了 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 文本: 此“最小化”脚本) ,原创者说他可以可靠地让它失败。我无法让它失败。然而,原始帖子来自 2013 年 2 月 15 日,这意味着他所使用的 Safari 必须是 6.0.2 或更低版本。

假设

  1. 此错误不会影响 6.0.5,但可能影响 6.0.4 或更低版本。
  2. BrowserStack 的系统似乎在一定程度上缓解了该错误,这意味着我们需要“真实”的 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 文本: 变量情况)jsperf。Chrome 似乎确实偏好 IReduce 到 seq+stringbuilder 对向量(未测试其他集合),但其他浏览器没有差异或轻微的减慢。不确定这是否值得。

Also 更新了(链接:http://jsperf.com/cljs-core-str-possibilities/2 文字:arity-one cases)使用了 str 和 switch,没使用 toString。在 Chrome 上比使用 switch 或 toString 几乎慢 50%,在 Safari 上更小。

从安全性角度讲,str-switch-notostr 完全不使用 toString,因此可能更安全。我认为 str-switch 也可能有效,而且速度更快。然而,我没有在 Safari 6.0.5 上收到任何 TypeError,所以谁也无法确定。

建议 spaceship new 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。

...