Groovyで文字列の一部を破壊的に削除する(Groovyならできるらしいのだが・・・)

Groovyで文字列の途中に破壊的に文字列を挿入する
で以下のコメントをもらった



delegateがよく分からないので、試さずそのままコメントを載せてた。


Groovy イン・アクションを参考にdelegateを使って、
Groovyで文字列の一部を破壊的に削除する
をやり直そうと思って試した。
しかし、コメントで教えてもらったコードが動かなかった。。。

コード

String.metaClass.insert << { i, s ->
    def chars = delegate.value as List;
    chars.addAll(i, s as List);
    delegate.value = chars as char[];
    delegate.count = chars.size()
}

def s = 'groovy'
s.insert(3, 'groovy')
s

実行結果

Caught: groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: value for class: java.lang.String
        at Q061_文字列の一部を破壊的に削除する_delegate版$_run_closure1.doCall(Q061_文字列の一部を破壊的に削除する_delegate版.groovy:4)
        at Q061_文字列の一部を破壊的に削除する_delegate版.run(Q061_文字列の一部を破壊的に削除する_delegate版.groovy:9)

1.5.8、1.6.0、1.6.5, 1.7.8, 1.8.0で試してみたけどダメだった。


これは動いた。
『「プログラミングGroovy」のカバーに載せる短い(3行程度)プログラム。簡潔だがGroovyのパワーや特徴が良くでたやつ』

String.metaClass.hello = { "Hello, $delegate!" }
println "Groovy".hello()
Hello, Groovy!

Groovy イン・アクション P42,122,182を読んでみたが
なぜ、だめなのか、どうすればいいのか分からない。

疑問1

1行目は String.metaClass.insert << { であってるの?
String.metaClass.insert = { ではないの?
=でもだめだったけど。
正しいとするとするとこの << ってどういう意味?

疑問2

2行目のdef chars = delegate.value as List;
4行目のdelegate.value = chars as char;
Listとchar
で方が違うけどこれが原因かとも思ったけど、
コメントアウトしても、次の行の delegate.count = chars.size() で例外に
なってしまう。
delegateって使うのが正しいの??


教えてスーパーなGroovyの人〜




'groovy'から"groovy"に変更

String.metaClass.insert << { i, s ->
    def chars = delegate.value as List;
    chars.addAll(i, s as List);
    delegate.value = chars as char[];
    delegate.count = chars.size()
}

def s = "groovy"
s.insert(3, "groovy")
s

GroovyConsoleでの結果
変わらず・・・

Exception thrown
2011/06/22 21:58:29 org.codehaus.groovy.runtime.StackTraceUtils sanitize

警告: Sanitizing stacktrace:

groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: value for class: java.lang.String

	at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:2353)

	at groovy.lang.ExpandoMetaClass.setProperty(ExpandoMetaClass.java:1128)

	at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:3312)

	at org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:183)

	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecodeAdapter.java:480)

	at ConsoleScript5$_run_closure1.doCall(ConsoleScript5:4)

	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

	at java.lang.reflect.Method.invoke(Method.java:597)

	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)

	at org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod.invoke(ClosureMetaMethod.java:80)

	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)

	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)

	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:120)

	at ConsoleScript5.run(ConsoleScript5:9)

	at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:266)

	at groovy.lang.GroovyShell.run(GroovyShell.java:517)

	at groovy.lang.GroovyShell.run(GroovyShell.java:172)

	at groovy.lang.GroovyShell$run.call(Unknown Source)

	at groovy.ui.Console$_runScriptImpl_closure16.doCall(Console.groovy:910)

	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

	at java.lang.reflect.Method.invoke(Method.java:597)

	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)

	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)

	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)

	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:885)

	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)

	at groovy.ui.Console$_runScriptImpl_closure16.doCall(Console.groovy)

	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

	at java.lang.reflect.Method.invoke(Method.java:597)

	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)

	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)

	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)

	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:885)

	at groovy.lang.Closure.call(Closure.java:405)

	at groovy.lang.Closure.call(Closure.java:399)

	at groovy.lang.Closure.run(Closure.java:483)

	at java.lang.Thread.run(Thread.java:662)

groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: value for class: java.lang.String

	at ConsoleScript5$_run_closure1.doCall(ConsoleScript5:4)

	at ConsoleScript5.run(ConsoleScript5:9)




String.metaClass.insert << { i, s ->
    def chars = delegate.value as List;
    chars.addAll(i, s as List);
    delegate.@value = chars as char[];
    delegate.@count = chars.size()
}

def s = 'groovy'
s.insert(3, 'groovy')
s


GroovyConsoleの結果

groovy> String.metaClass.insert << { i, s -> 
groovy>     def chars = delegate.value as List; 
groovy>     chars.addAll(i, s as List); 
groovy>     delegate.@value = chars as char[]; 
groovy>     delegate.@count = chars.size() 
groovy> } 
groovy> def s = 'groovy' 
groovy> s.insert(3, 'groovy') 
groovy> s 
 
Exception thrown
2011/06/22 22:48:07 org.codehaus.groovy.runtime.StackTraceUtils sanitize

警告: Sanitizing stacktrace:

groovy.lang.GroovyRuntimeException: Cannot set the property 'value' because the backing field is final.

	at org.codehaus.groovy.reflection.CachedField.setProperty(CachedField.java:68)

	at groovy.lang.MetaClassImpl.setAttribute(MetaClassImpl.java:2518)

	at groovy.lang.MetaClassImpl.setAttribute(MetaClassImpl.java:3320)

	at org.codehaus.groovy.runtime.InvokerHelper.setAttribute(InvokerHelper.java:145)

	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setField(ScriptBytecodeAdapter.java:328)

	at ConsoleScript0$_run_closure1.doCall(ConsoleScript0:4)

	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

	at java.lang.reflect.Method.invoke(Method.java:597)

	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)

	at org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod.invoke(ClosureMetaMethod.java:80)

	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)

	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)

	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:120)

	at ConsoleScript0.run(ConsoleScript0:9)

	at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:266)

	at groovy.lang.GroovyShell.run(GroovyShell.java:517)

	at groovy.lang.GroovyShell.run(GroovyShell.java:172)

	at groovy.lang.GroovyShell$run.call(Unknown Source)

	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:124)

	at groovy.ui.Console$_runScriptImpl_closure16.doCall(Console.groovy:910)

	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

	at java.lang.reflect.Method.invoke(Method.java:597)

	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)

	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)

	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)

	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:885)

	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)

	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)

	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)

	at groovy.ui.Console$_runScriptImpl_closure16.doCall(Console.groovy)

	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

	at java.lang.reflect.Method.invoke(Method.java:597)

	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)

	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)

	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)

	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:885)

	at groovy.lang.Closure.call(Closure.java:405)

	at groovy.lang.Closure.call(Closure.java:399)

	at groovy.lang.Closure.run(Closure.java:483)

	at java.lang.Thread.run(Thread.java:662)

groovy.lang.GroovyRuntimeException: Cannot set the property 'value' because the backing field is final.

	at ConsoleScript0$_run_closure1.doCall(ConsoleScript0:4)

	at ConsoleScript0.run(ConsoleScript0:9)


確かに'value' が final だからだめのようだ。

1.5.6かよ・・・


ダンロード
http://dist.groovy.codehaus.org/distributions/groovy-binary-1.5.6.zip


GroovyConsleの結果

groovy> String.metaClass.insert << { i, s ->
groovy>     def chars = delegate.value as List;
groovy>     chars.addAll(i, s as List);
groovy>     delegate.value = chars as char[];
groovy>     delegate.count = chars.size()
groovy> }
groovy> def s = 'groovy'
groovy> s.insert(3, 'groovy')
groovy> s

Result: "grogroovyovy"

ホントだ。Groovy 1.5.6だと動くや。


コメントより

1.5.6では動きますが、現在のバージョンではfinal変数への再代入は
許可されなくなったようです。なんか悩ませてしまったようでごめんなさい。
なんか悔しいからfinal許可するように書き換えて
やろうかと思いましたが大人気ないので止めました。では。





結論

Groovy 1.5.7以降は、value、countは final だから更新できない。


えっ違うの?
さらにツッコミが来た。

String.metaClass.destructiveReverse << {
    def chars = delegate.value;
    chars.eachWithIndex {e, i ->
      chars[- i - 1] = e
    }
}

def s = 'groovy'
s.destructiveReverse()
assert s == 'yvoorg'


GroovyConsoleの結果(Groovy 1.8.0)

groovy> String.metaClass.destructiveReverse << { 
groovy>     def chars = delegate.value; 
groovy>     chars.eachWithIndex {e, i -> 
groovy>       chars[- i - 1] = e 
groovy>     } 
groovy> } 
groovy> def s = 'groovy' 
groovy> s.destructiveReverse() 
groovy> s 
 
Result: "yvoorg"

わぉ


詳細は『プログラミングGroovy』で!!
・・・・記載があるのだろうか??






スーパーなGroovyな方々に、いろいろアドバイスをいただいて助かりました。
本当にありがとうございました。m(__)m



実行環境

  • OS:Windows XP
  • Groovy Version:1.5.6、1.5.8、1.6.0、1.6.5、1.7.8、1.8.0
  • JVM: 1.6.0_26




Groovyの詳細についてはJavadocと以下の書籍を参考にしている。




Dierk Konig、Andrew Glover、Paul King、、Guillaume Laforge、Jon Skeet、杉浦 孝、櫻井 正樹、須江 信洋、関谷 和愛、佐野 徹郎、寺沢 尚史

Amazonでご確認ください。



問題自体は第2版のもの。rubyと似てる部分も多いので、ヒントにもなる。
写経でもいいが自分で考えるために他言語の例をGroovyで置き換えてる。




青木 峰郎、後藤 裕蔵、高橋 征義、まつもと ゆきひろ

価格: ¥ 2,940
価格は記載時点のものです。購入前にAmazonでご確認ください。




Groovyイン・アクションを読むならあった方が便利かな。

Rubyレシピブックは「ほんたった」で立ててる