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?buildString/数组方法看起来有些蹩脚?

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

0
by

评论者: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
by

评论者: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
by

评论者:dnolen

我会接受最多4个然后变体。

0
by

评论者:thheller

代码生成器与函数之间存在一些奇怪交互。

宏生成

`
("hello" 1 "world" :yo null)

生成

[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 *宏*,而不是 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 的唯一机器是使用软件更新直到 2012 年末至 2013 年初然后停止的(Mountain)Lion 机器。我想这不会是很多人。

从理论上讲,我可以在我的 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 的 cljs.core/str$arity$1 添加一个 {{number?}} 检查(带注释)。数字情况应使用 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.0.X可用,但似乎我们所有人都因为这微小的比例仍在忍受痛苦(在我的网站上,有6,677人使用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

性能测试结果也不明朗,因为看起来火狐有点作弊。

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-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的向量(其他集合尚未测试),在其他浏览器上没有差异或略有减慢。不确定是否值得。

还更新了(链接: http://jsperf.com/cljs-core-str-possibilities/2 文本:单参案例)以使用str 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)
"string" x
"object" (if (identical? nil x) "" (.toString x))
("boolean" "number") (js-str x)
"undefined" ""
(js-str #js [x]))) ;; 防止 Safari 6.0.x TypeError bug.
([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。

...