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] [http://dev.clojure.org/jira/browse/CLJS-847](http://dev.clojure.org/jira/browse/CLJS-847)

35个回答

0

由:dnolen 评论

在master分支中我们已切换到{{goog.string.buildString}}[链接](https://github.com/clojure/clojurescript/commit/94eb8a960fef6aaca4ba44b251cefbfa04d0f6ac)

0
by

由: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 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)

得到的是

[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的bug。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 *函数*以使用具有array.join行为(在Safari 6.0.5上正确)的goog.string.buildString。
# Thomas指出,buildString以v8无法优化的方式使用参数,现在str函数(不是宏)的性能下降。他建议直接使用{{\[].join()}}。

因此,关于这个问题存在着许多来回的讨论,这全部都是由于Safari 6.0.5中的bug引起的,没有人能够亲手重现,因为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 bug(我检查了--我有配备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が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的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 文本:这个“最小化”脚本)的程序员表示他可以把它设置成可靠地失败。我无法使其失败。然而,原始帖子是从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中寻求过凯文的帮助。(凯文是Safari 6.0.x问题的原始报告人。)

0

评论由:favila发表

创建了一个关于可变数量参数的(链接: http://jsperf.com/cljs-core-str-variadic-possibilities 文本:jsperf测试)。Chrome似乎真的更喜欢IReduce使用vector(未测试其他集合),但在其他浏览器上没有不同或者只有很小的减速。不确定这是否值得。

还更新了(链接: http://jsperf.com/cljs-core-str-possibilities/2 文本:一元案例)与使用switch而不是使用toString的str使用。在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 后再进行修改。

...