2024 Clojure状态调查!(a)中分享您的想法。

欢迎!请参阅关于页面以了解更多有关如何工作的信息。

0
ClojureScript by

示例

(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
by

评论者:dnolen

我们在主分支中使用{{goog.string.buildString}}进行了切换 https://github.com/clojure/clojurescript/commit/94eb8a960fef6aaca4ba44b251cefbfa04d0f6ac

0
by

评论者:nbeloglazov

是的,这样可以。很好,谢谢!

0
by

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

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

产生

['hello', '1', 'world', cljs.core.Keyword(null, "yo", "yo", 1207083126), 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中无疑的bug。通过使一个参数的cljs.core/str函数调用js-str而不是toString,David修复了它。js-str使用字符串连接{{''+x}}。
# 然而,这会破坏定义valueOf的对象(当前票中的问题),因为在js中{{''+x}}等同于{{''+x.valueOf().toString()}}而不是{{''+x.toString()}}。
# David考虑使用{{String()}}及其变体,但因其性能影响而拒绝。
# David将CLJS-801从字符串连接回数组连接样式以修复。
# Nikita和我指出,回滚CLJS-801仅修复了str *macro*,而不是字符串 *function*,后者仍然使用{{js-str}}并因此进行字符串连接。
# David修复了str *function*以使用goog.string.buildString,该函数具有类似于array.join的行为。现在,行为在Safari 6.0.5上也是正确的。
# Thomas指出,buildString以v8无法优化的方式使用参数,现在str函数(不是宏)存在性能倒退。他建议直接使用{{[].join()}}。

因此,关于这个问题有很多来来回回,这一切都是因为Safari 6.0.5中的一个bug,没有人能够亲身体验到这个bug,因为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 bug的影响(我检查了——我有一台带有Lion的iMac)。只有(Mountain) Lion机器上才运行着Safari 6.0.5,这些机器直到2012年后期至2013年早期还使用软件更新,然后停止使用更新。我想不出这会有很多人。

理论上,我有在狮子操作系统上运行Safari 6.0.x来实际测试它的可能性,但我找不到降级到6.1.x的方法。

我认为选项包括:
Methods for stringification using array.join() and tolerate the resulting performance loss(这是我们应该量化的)。为未来的一代人留下注释,他们会觉得这是一种奇怪的做法。这仅在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
by

评论者:dnolen

我们不会忽略Safari 6.0.X。关于此票的问题将包括对其的支持。

0
by
_由favila_评论

关于我对这一问题所进行的某些研究的更新。

我创建了一系列关于替代str实现[的jsperf测试|http://jsperf.com/cljs-core-str-possibilities],我现在正在尝试。现在我只看了单参数str。我发现了一些事情:

注:`''+[x]`是一个比`[x].join('')`更快的选择。
编译器可以计算`''+[x]`,如果`x`是布尔值、字符串、未定义、null或数字,即使在函数调用中!也就是说,str_test.str_arr(123)编译为"123",而不需要有宏魔法。
但是,使用中间数组(即使在预分配的单例数组的情况下)仍然比旧的`\(if (nil? x) "" (.toString x))`慢。
使用switch语句至少与str-tostr基准一样快,通常是更快。
我99%确信所有这些实现(除了str-tostr基线,这肯定失败)都在令人讨厌的Safari 6.0.x上工作。如果有人有这个版本,请指向上面的jsperf链接并运行测试。我想[Browerstack|http://www.browserstack.com]有这个版本的Safari。

我仍在研究变参数情况(str x y z a b c)。可能最好使用reduce而不是Stringbuffer+seq。(Stringbuffer现在只做`''+x`,而不是一个数组join。)
0
by

评论者: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 进行了测试,结果已公布。

注意,我无法复制 CLJS-847,因为 str-tostr 并未像预期那样失败。现在有了浏览器进行测试,我将更加努力。

0

评论者:favila

仍然无法复制 CLJS-847。

(链接:[https://dl.dropboxusercontent.com/u/18386249/code/strtest.html](https://dl.dropboxusercontent.com/u/18386249/code/strtest.html) 文本:此脚本)包括我尝试的最小可重复案例。我的理论是,在某些较高的 jit 级别类型下会失败。我没有得到任何案例失败。我还尝试了在类型之间来回切换以及一次使用一个类型,但仍然没有失败。

在 GitHub 的(链接:[https://github.com/wycats/handlebars.js/pull/440](https://github.com/wycats/handlebars.js/pull/440) 线程)中我发现(链接:[https://dl.dropboxusercontent.com/u/18386249/code/handlebar_str_typeerror.html](https://dl.dropboxusercontent.com/u/18386249/code/handlebar_str_typeerror.html) 线文:此最小脚本) amely szerint az OP (原始问题提出者) 能可靠地引发失败。但我无法让它失败。但是原始帖子是在 2013 年 2 月发布的,这意味着他使用的 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和seq+StringBuilder来处理vector(未测试其他集合),但在其他浏览器上没有差别或轻微的减速。不确定这是否值得。

还更新了(链接: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 & 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。

...