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

35 答案

0

评论者:dnolen

我们在master中切换到{{goog.string.buildString}} 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
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

代码生成器生成的 cljs.core/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_ 添加

引言:Chromes 抱怨:“未优化。参数值上下文不正确”,进一步查看goog.string.buildString 的实现。

Chrome 对相同的可变参数函数分发代码也有类似的抱怨,参见 CLJS-916 及补丁。

我认为可能可以完全删除该宏,因为 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 方法的对象来说不起作用(现有票证的 Issue),因为 JavaScript 中 {{''+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 中的一个错误造成的,而没有人能够亲手重现它,因为 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 错误的影响(我已经检查过了—I有一台安装在 Lion 上的 iMac)。Safari 6.0.5 的唯一机器是早期 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 的兼容性,需要添加一个 {{number?}} 检查(带有注释)到 {{cljs.core/str$arity$1}}。数字类型应该使用 js-str,其他使用 toString 方法。我猜想这会奏效,但我们真的没有测试的方法--我们非常需要一台安装了 Safari 6.0.x 浏览器的机器。

当然,我们应该对这些方法进行基准测试,但我的直觉是第二种方法比第三种方法快,而第三种方法又比第一种方法快。
0

评论者:dnolen

我们不会忽略 Safari 6.0.X 版本。关于这个问题的任何决定都将包括对其的支持。

0
评论由:favila_ 添加

关于我对此问题的研究更新。

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

* 使用 {{''+[x]}} 比 {{[x].join('')}} 更快。
* 如果 {{x}} 是布尔值、字符串、undefined、null 或数字,高级编译可以在编译时计算 {{''+[x]}},即使通过函数调用也是如此!即 str_test.str_arr(123) 编译为 "123" 而不是使用宏魔法。
不过,使用中间数组(哪怕是一个预分配的单例)仍然比旧的 {{(if (nil? x) "" (.toString x))}} 更慢。
使用 switch 语句至少与 str-tostr 基线一样快,通常更快。
我非常确定所有这些实现(除了基线的 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,但鉴于仍有少数用户(在 my 站点上 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 文本:此“最小”脚本),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实现编写一个补丁,但是我不知道它是否可能在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 文本:一阶案例),使用带有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 类型错误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。

...