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)

35 个回答

0点赞

由dnolen评论

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

0点赞

由nbeloglazov评论

是的,这有效。太酷了,谢谢!

0点赞

由thheller评论

抱歉重新打开。

我在对我的代码进行性能分析时,注意到性能分析输出中有关于cljs.core/str的警告。

Chromes 报错:"未优化。参数值上下文错误",进一步查看 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('');
`

鉴于使用 1 个参数的 str 将基本上展开为

[["hello"].join(""), ...]

我认为可以完全移除宏,因为 cljs.core/str 会做到同样的事情,JIT 可能足够智能以找出这一点(或者是 Closure 在编译时)。

0点赞
_评论者:favila_

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

Chrome以相同的方式抱怨variadic函数分发代码,参见CLJS-916以及补丁。

bq. 我认为可以完全移除宏,因为cljs.core/str会做同样的事情,而且JIT可能足够聪明,能够理解这一点(或者甚至在Closure编译时也是如此)。

Closure编译器无法足够聪明地移除中间数组,这就是为什么我提交了CLJS-801(这个issue回滚了)。我认为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的一个bug。David通过使一个参数的cljs.core/str函数调用js-str而不是toString来修复它。js-str使用string-concat {{''+x}}。
# 然而,这对于定义valueOf的对象(即本ticket中的问题)造成破坏,因为在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无法优化的方式使用参数,现在str函数(不是宏)出现了性能倒退。他建议直接使用{{\[].join()}}。

因此,在这个问题上有很多来回讨论,这全是由于无法亲手复制的Safari 6.0.5的bug。为了有些视角,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)。唯一运行Safari 6.0.5的机器是(Mountain)Lion机器,这些机器直到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,但为Chrome浏览器6.0.5添加了一个{{number?}}校验(含注释)在{{cljs.core/str$arity$1}}上。数字情况应使用js-str,其余使用toString。我认为这会有效,但再次强调,我们没有测试的机会——我们真的需要拿到一个Safari 6.0.x浏览器的权限。

当然我应该对这些方案进行基准测试,但我有预感,2比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}}是布尔值、字符串、未定义、null或者数字,甚至通过函数调用!即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一样工作,而不再是数组-join。)
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. BrowserStack的系统在某种程度上缓解了该错误,意味着我们需要一个“真正的”Lion Safari 6.0.x来进行测试。
  3. 这些测试只会在正确相位下月亮时失败。

因此,我可以为str使用str-switch实现(这至少在某个浏览器上更快),但我不知道它是否可能在Safari 6.0.5上失败。我只知道它现在看起来正常。CLJS-801也应该安全重新应用,因为所有问题的根本原因是cljs.core/str函数的1-arity实现。

我还请求Kevin在CLJS-847中给予帮助。(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上比使用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点赞

评论者:favila

这个补丁的初步版本解决了这个问题,同时没有破坏 CLJS-847。应该等待确认这不会破坏 Safari 6.0.x。

...