( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

プログラミング言語「ほむほむ」

なんか、極めると「ほむほむ」だけで会話できるみたいですね?


俺はまだそこまでの域には至ってないんですが、「ほむほむ」だけでプログラミングできたらステキですよね?

そこで、ちょっと草植えときますね型言語 Grassを元にして以前作ったプログラミング言語「天使ちゃんマジ天使」とか 「ブブゼラ」をベースに、 またまたネタ言語を作りました。


Grassの文法と異なる点は以下のとおり。

  • wがほむ
  • スペース・タブにはさまれた"ほむ"がW
  • vは改行


wを出力するプログラム:

ほむ ほむほむ ほむほむほむほむ


xを出力するプログラム:

ほむ ほむほむほむ ほむほむほむほむ ほむほむほむ ほむ


"Hello, world!"を出力するプログラム

ほむ
ほむ ほむ ほむほむほむほむ ほむ ほむほむほむほむほむほむ ほむほむほむほむほむ ほむ ほむほむほむほむほむ ほむほむほむほむ
ほむほむほむほむ ほむほむほむ ほむほむ ほむ ほむほむ ほむほむほむほむほむほむ ほむほむほむほむ ほむ ほむほむ
ほむ ほむほむ ほむ ほむ ほむほむ
ほむほむ ほむほむ ほむ
ほむ ほむほむほむ ほむほむ ほむほむほむほむほむ ほむほむほむ ほむ ほむほむ ほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむ ほむ ほむほむほむほむ ほむ ほむほむほむほむほむ ほむ ほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむ ほむ ほむ ほむ ほむほむほむ ほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむ ほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむ ほむほむほむ ほむほむほむほむ ほむほむほむほむ ほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむ ほむほむほむ ほむほむ ほむ ほむほむほむ ほむ ほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむ ほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ


ソースコードです。

import java.io.File
import scala.io.Source
import scala.util.matching.Regex
import scala.util.parsing.combinator._
import scala.util.parsing.input.{Position, NoPosition}

sealed abstract class Insn extends ( CED => CED ){
  val pos:Position
}
case class App( m:Int, n:Int, pos:Position ) extends Insn{
  override def apply( ced:CED ) = ced.e( m - 1 )( ced.e( n - 1 ), ced )
  override def toString = "App(%s,%s)".format(m, n)
}

case class Abs( m:Int, body:List[App] ,pos:Position ) extends Insn{
  override def apply( ced:CED ) =
    if( m == 1) CED( ced.c, Fn( body, ced.e ) :: ced.e, ced.d )
    else        CED( ced.c, Fn( Abs( m - 1, body, pos ) :: Nil, ced.e ) :: ced.e, ced.d )

  override def toString = "Abs(%s)".format(m)
}

case class CED( c:List[Insn], e:List[Value], d:List[CE] )
case class CE( c:List[Insn], e:List[Value] )

class GrassRuntime( val insn:List[Insn], val source:String){

  val e0 = Out :: Succ :: CharFn('w') :: In :: Nil
  val d0 = CE(Nil, Nil) :: CE( App(1, 1, NoPosition) :: Nil, Nil) :: Nil

  def run:Unit = {
    var c = eval( CED( insn, e0, d0 ) )
    while( c != None ){
      val Some(m) = c
      c = eval( m )
    }
  }

  def eval( ced:CED ) = ced.c match {
    case Nil => ced.d match {
      case Nil => None
      case x::xs  => Some( CED( x.c, ced.e.head:: x.e , xs ))
    }
    case code :: remains => Some( code( CED( remains, ced.e, ced.d )) )
  }
}

abstract class Value extends ( (Value, CED) => CED )

case class Fn(code : List[Insn], env : List[Value]) extends Value {
  override def apply( v:Value, ced:CED ) = CED( code , v :: env, CE( ced.c, ced.e ) :: ced.d )
  override def toString = "Fn"
}

case class CharFn(char : Char) extends Value {
  val ChurchTrue  = Fn( Abs( 1, App( 3, 2, NoPosition ) :: Nil, NoPosition ) :: Nil, Fn( Nil, Nil ) :: Nil )
  val ChurchFalse = Fn( Abs( 1, Nil,  NoPosition) :: Nil,  Nil)

  override def apply( v:Value, ced:CED ) = v match {
    case CharFn( c ) => CED( ced.c, ced.e ::: ( if( char == c ) ChurchTrue else ChurchFalse ) :: Nil, ced.d )
    case _ => throw new Exception("eval error value is not CharFn")
  }
  override def toString = "CharFn(%s, %s)".format( char , char.toInt)
}

object Succ extends Value {
  override def apply( v:Value, ced:CED ) = v match {
    case CharFn( c ) =>
      val char = ( (c + 1) % 256 ).toChar
      CED( ced.c, CharFn( char ) :: ced.e, ced.d )
    case _ => throw new Exception("eval error value is not CharFn")
  }
  override def toString = "Succ"
}

object Out extends Value {
  override def apply( v:Value, ced:CED ) = v match {
    case CharFn( c ) =>
      print(c)
      CED( ced.c, v :: ced.e, ced.d )
    case _ => throw new Exception("eval error value is not CharFn")
  }
  override def toString = "Out"
}
object In extends Value {
  override def apply( v:Value, ced:CED ) ={
    val c = readChar
    CED( ced.c, CharFn( c ) :: ced.e, ced.d )
  }
  override def toString = "In"
}

object Home2LangParser extends RegexParsers{
  import scala.util.parsing.input.CharSequenceReader._
  override def skipWhitespace = false

  val wToken = "ほむ".r
  val sep = """[ \t]""".r
  val fToken = rep1( sep ) ~> rep1(wToken) <~ rep1( sep ) ^^ { x => "W" * x.length }
  val vToken = """\n""".r

  def p(s:String):Parser[String] = s

  def wrap[A](p: Parser[A]) = Parser{r => Success(r.pos,  r)} ~ p

  def w :Parser[String] = rep( comment ) ~> wToken <~ rep( comment )
  def f :Parser[String] = rep( comment ) ~> fToken <~ rep( comment )
  def v :Parser[String] = rep( comment ) ~> vToken <~ rep( comment )
  val any :Parser[String] = elem("", _ != EofCh) ^^ { _.toString }

  def token   :Parser[String] = wToken ||| fToken ||| vToken
  def comment :Parser[String] = not( token ) <~ any ^^ ( (Unit) => "" )

  def app :Parser[App] = wrap(  f  ~ rep1( w ) ) ^^
    { case ~( p, x ~ y ) => App( x.size, y.size, p ) }

  def abs :Parser[Abs] = wrap( rep1( w ) ~ rep( app ) ~ rep(v) ) ^^
    { case ~( p, ws ~ body ~ vs ) => Abs( ws.size, body, p ) }

  def prog :Parser[List[Insn]] = rep( abs ) ~ rep( app ) ~ rep( v ) ^^
    { case a ~ p ~ v => a ::: p  }

  def parse( s:String ):Option[GrassRuntime] = parseAll( prog , s ) match {
    case Success( insn, _ )  =>  Some( new GrassRuntime( insn, s ) )
    case Failure( msg, _ ) => { println( msg ); None }
    case Error( msg, _ )   => { println( msg ); None }
  }
  def run( s:String ) = parse( s ) foreach{ _.run }

  def test( s:String ) = parse( s ) foreach{ r => dump( r.insn, 0 ) }

  def dump( x:List[Insn] , n:Int ):Unit = {
    val sp = (for( i <- 0 to n ) yield{ "  " } ).mkString
    x.foreach{ o => o match {
      case Abs( i,b,_ ) => {
        println( sp + "Abs( " + i + ")")
        dump( b , n + 1 )
      }
      case App( i,j,_) => println( sp + "App( " + i + ", " + j + " )")
    }}
  }
}

class GrassParser(
  wTokens:List[String],
  fTokens:List[String],
  vTokens:List[String]
)extends RegexParsers{
  import scala.util.parsing.input.CharSequenceReader._
  override def skipWhitespace = false

  def p(s:String):Parser[String] = s
  def make( tk:List[String] ) = ( p( tk.head ) /: tk.tail ){ _ ||| p( _ ) }

  def wrap[A](p: Parser[A]) = Parser{r => Success(r.pos,  r)} ~ p

  def w :Parser[String] = rep( comment ) ~> ( make( wTokens ) ) <~ rep( comment )
  def f :Parser[String] = rep( comment ) ~> ( make( fTokens ) ) <~ rep( comment )
  def v :Parser[String] = rep( comment ) ~> ( make( vTokens ) ) <~ rep( comment )
  val any :Parser[String] = elem("", _ != EofCh) ^^ { _.toString }

  def token   :Parser[String] = make( wTokens ) ||| make( fTokens ) ||| make( vTokens )
  def comment :Parser[String] = not( token ) <~ any ^^ ( (Unit) => "" )

  def app :Parser[App] = wrap( rep1( f ) ~ rep1( w ) ) ^^
    { case ~( p, x ~ y ) => App( x.size, y.size, p ) }

  def abs :Parser[Abs] = wrap( rep1( w ) ~ rep( app ) ~ rep(v) ) ^^
    { case ~( p, ws ~ body ~ vs ) => Abs( ws.size, body, p ) }

  def prog :Parser[List[Insn]] = rep( abs ) ~ rep( app ) ~ rep( v ) ^^
    { case a ~ p ~ v => a ::: p  }

  def parse( s:String ):Option[GrassRuntime] = parseAll( prog , s ) match {
    case Success( insn, _ )  =>  Some( new GrassRuntime( insn, s ) )
    case Failure( msg, _ ) => { println( msg ); None }
    case Error( msg, _ )   => { println( msg ); None }
  }
  def run( s:String ) = parse( s ) foreach{ _.run }

  def test( s:String ) = parse( s ) foreach{ r => dump( r.insn, 0 ) }

  def dump( x:List[Insn] , n:Int ):Unit = {
    val sp = (for( i <- 0 to n ) yield{ "  " } ).mkString
    x.foreach{ o => o match {
      case Abs( i,b,_ ) => {
        println( sp + "Abs( " + i + ")")
        dump( b , n + 1 )
      }
      case App( i,j,_) => println( sp + "App( " + i + ", " + j + " )")
    }}
  }
}

object Main{
  def main(args:Array[String]) = {

    println()
    println( "プログラミング言語 ほむほむ" )

    println("-" * 80)
    println( "print w" )
    val printW = "ほむ ほむほむ ほむほむほむほむ"

    println( "source code:")
    println( "  %s" format printW)
    println()
    println( "AST:")
    Home2LangParser.test( printW )
    println()
    println( "Result:")
    Home2LangParser.run( printW )
    println()

    println("-" * 80)
    println( "print x" )
    val printX ="ほむ ほむほむほむ ほむほむほむほむ ほむほむほむ ほむ"

    println( "source code:")
    println( "  %s" format printX)
    println()
    println( "AST:")
    Home2LangParser.test( printX )
    println()
    println( "Result:")
    Home2LangParser.run( printX )
    println()

    println("-" * 80)
    println( "Hello World" )
    val hw = Source.fromFile(new File( "./home2lang.grass" )).mkString

    println( "source code:")
    println( "  %s" format hw)
    // println()
    // println( "AST:")
    // Home2LangParser.test( printX )
    println()
    println( "Result:")
    Home2LangParser.run( hw)
    println()
    println("-" * 80)
  }
}

https://gist.github.com/1001863


実行結果

$ scala Main 

プログラミング言語 ほむほむ
--------------------------------------------------------------------------------
print w
source code:
  ほむ ほむほむ ほむほむほむほむ

AST:
  Abs( 1)
    App( 2, 4 )

Result:
w
--------------------------------------------------------------------------------
print x
source code:
  ほむ ほむほむほむ ほむほむほむほむ ほむほむほむ ほむ

AST:
  Abs( 1)
    App( 3, 4 )
    App( 3, 1 )

Result:
x
--------------------------------------------------------------------------------
Hello World
source code:
  ほむ
ほむ ほむ ほむほむほむほむ ほむ ほむほむほむほむほむほむ ほむほむほむほむほむ ほむ ほむほむほむほむほむ ほむほむほむほむ
ほむほむほむほむ ほむほむほむ ほむほむ ほむ ほむほむ ほむほむほむほむほむほむ ほむほむほむほむ ほむ ほむほむ
ほむ ほむほむ ほむ ほむ ほむほむ
ほむほむ ほむほむ ほむ
ほむ ほむほむほむ ほむほむ ほむほむほむほむほむ ほむほむほむ ほむ ほむほむ ほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむ ほむ ほむほむほむほむ ほむ ほむほむほむほむほむ ほむ ほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむ ほむ ほむ ほむ ほむほむほむ ほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむ ほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむ ほむほむほむ ほむほむほむほむ ほむほむほむほむ ほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむ ほむほむほむ ほむほむ ほむ ほむほむほむ ほむ ほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむ ほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむ ほむほむほむほむほむほむほむほむ ほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ ほむほむほむほむほむほむ ほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむほむ


Result:
Hello, world!
--------------------------------------------------------------------------------


ほむほむ

Scalaでmethod_missing的なことができる"applyDynamic"を使って"Term::ANSIColor"みたいなの作った

Scala2.9から、DynamicってのでRubyのmethod_missing的なことができるようになる.....ハズだったんだが、
2.9正式リリースの数時間前にいきなりこの機能はサポートされなくなった。


といっても全く使えなくなったわけではなく、scalacやscalaコマンドに-Xexperimentalオプションを使えば使えるようになる。


で、Dynamicってどんなのかというと、Dynamicというトレイトを実装したクラスは、定義されていないメソッドが呼び出されるとapplyDynamic(methodName:String)(args:Any*)というメソッドが代わりに呼び出されるようになる。

  scala> class Foo extends Dynamic {
       |   def applyDynamic(methodName:String)(args:Any*) = {
       |     println( "methodName = " + methodName )
       |     println( "args = " + args)
       |   }
       | }
  defined class Foo

  scala> val foo = new Foo
  foo: Foo = Foo@4c0a7e2e

  scala> foo.bar
  dynatype: $line7.$read.$iw.$iw.$iw.$iw.foo.applyDynamic("bar")()
  methodName = bar
  args = List()


で、こいつを使って、Rubyの"Term::ANSIColor"みたいなヤツを書いてみた。

f:id:yuroyoro:20110527203717p:image:w640

/**
 * This library can be used to color/uncolor strings using ANSI escape sequences.
 *
 * To implementation, referenced Ruby's "Term::ANSIColor"(http://term-ansicolor.rubyforge.org)
 *
 * NOTE
 *  this version's implementation is using Dynamic trait and applyDynamic.
 *  "applyDynamic" is not supported yet.
 *  to enable this feature, you should specify -Xexperimental option  when running scala/scalac command.
 *
 * Usage:
 *  val coloredStr =  "This text would be colored RED!!".colored.red + " and this part would be BLUE!!".colored.blue 
 *  println(coloredStr)
 *
 */

object Colored {
  implicit def stringToColered(s:String) = Colored(s)
  implicit def ColoredToString(c:Colored) = c.toString

  val attributes = Map(
    'clear         ->   0,
    'reset         ->   0,
    'bold          ->   1,
    'dark          ->   2,
    'italic        ->   3,
    'underline     ->   4,
    'underscore    ->   4,
    'blink         ->   5,
    'rapid_blink   ->   6,
    'negative      ->   7,
    'concealed     ->   8,
    'strikethrough ->   9,
    'black         ->  30,
    'red           ->  31,
    'green         ->  32,
    'yellow        ->  33,
    'blue          ->  34,
    'magenta       ->  35,
    'cyan          ->  36,
    'white         ->  37,
    'onBlack      ->  40,
    'onRed        ->  41,
    'onGreen      ->  42,
    'onYellow     ->  43,
    'onBlue       ->  44,
    'onMagenta    ->  45,
    'onCyan       ->  46,
    'onWhite      ->  47
  ).map{case (k, v) => (k, "\033[%dm" format v) }

  private val Reset = escape('reset) getOrElse(0)

  private def escape(attr:Symbol):Option[String] = attributes.get(attr)
  private def escape(attr:String):Option[String] = escape(Symbol(attr))

  private def escaped(attr:Symbol)(s: => String):Colored = Colored( escape(attr) + s )
  private def escaped(attr:String)(s: => String):Colored = escaped(Symbol(attr))(s)

  def apply(s:String) = new Colored{ val str = s }
  val colored = Colored("")
}

trait Colored extends Dynamic {
  val str:String

  def +(other:String):Colored = Colored( this.toString + other)
  def +(other:Colored):Colored = Colored( this.toString + other.toString )

  def colored = this

  def applyDynamic(attr: String)(args: Any*):Colored =
    coloring(attr) + args.headOption.getOrElse("")

  private def coloring(attr:String) =
    Colored.escape(attr) map{ esc => Colored(esc + str) } getOrElse(this)

  override def toString = str + Colored.Reset

}

object Main extends App {
import Colored._

println( "This text would be colored RED!!".colored.red + " and this part would be BLUE!!".colored.blue )
println( "白字に赤背景であばばばばばばばば".colored.onWhiltecolored.onRed.white)

}


F.Y.I

Scala2.9から導入されたバイナリ互換性確保のためのbridgeアノテーションについて調べた

InfoQのScala2.9リリースの記事を読んでいて、Scala2.8から2.9へのバイナリ互換性確保のための方法について、こんな記述があった

One such technological solution are compiler generated forwarders, so-called bridge methods, which delegate calls from an old to a new method.

http://www.infoq.com/news/2011/05/scala-29

" compiler generated forwarders"ってなんぞ?と思って調べてみた。多分このあたりのOderskyのメールにある@bridgeアノテーションのことだと思う。


Parallel collections binary compatibility | The Scala Programming Language

Binary compatibility: status and outlook -
scala-user |
Google Groups

ってわけで、@bridgeアノテーションScalaコンパイラがどんな挙動をするか調べてみた。

Version1

まず下準備として、バイナリ互換性の確保が必要なサンプルライブラリを用意する。適当なところにlibってディレクトリを掘って、そこに以下の内容でLib.scalaを置いてコンパイルしておく。"def combine(other:Seq[String]): Unit "ってメソッドがひとつだけ用意されているシンプルなobjectだ。

lib/Lib.scala

object Lib {
  def combine(other:Seq[String]): Unit = {
    println("-- this is first version of combine(other:Seq[String]): Unit")
    println(other.mkString("/"))
  }
}


こいつをコンパイルしてクラスファイルをlibに作っておく。

$ scalac -d lib lib/Lib.scala


次に、このサンプルライブラリを利用するクライアントを作成する。clientディレクトリにこんなカンジでMainオブジェクトを作る。上で作ったLib#combineに依存するようになっている。

object Main extends App {
  val seq = Seq("アップキャスト","ダウンキャスト",
    "クロスキャスト","静的キャスト", "ドリームキャスト")
  Lib.combine(seq)
}


で、このclient/Main.scalaもコンパイルしておく。

$ scalac -cp lib -d client client/Main.scala


この時点で、ファイル/ディレクトリ構成はこうなっている。

.
├── client
│ ├── Main$.class
│ ├── Main$delayedInit$body.class
│ ├── Main.class
│ └── Main.scala
└── lib
    ├── Lib$.class
    ├── Lib.class
    └── Lib.scala


実行してみる。まぁこうなるわな。

[2$ scala -cp lib:client Main
-- this is first version of combine(other:Seq[String]): Unit
アップキャスト/ダウンキャスト/クロスキャスト/静的キャスト/ドリームキャスト

ここまで下準備は終わり。

Version2 引数をSeqからTraversableへ変更する

Lib#combineの引数を、SeqからTraversableへ変更して、コンパイルする。


lib/Lib.scala

object Lib {
  def combine(other:Traversable[String]): Unit = {
    println("-- this is first version of combine(other:Traversable[String]): Unit")
    println(other.mkString("/"))
  }
}


先ほどと同様にコンパイルしてクラスファイルをlibに作っておく。

$ scalac -d lib lib/Lib.scala


で、このLib#combineに依存するMainは再コンパイルせずに、再度実行してみる。ここで、Libのバイナリ互換性は破壊されているので、Mainは実行できないはず。

$ scala -cp lib:client Main
java.lang.NoSuchMethodError: Lib$.combine(Lscala/collection/Seq;)V
        at Main$delayedInit$body.apply(Main.scala:5)
        at scala.Function0$class.apply$mcV$sp(Function0.scala:34)
        at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
  ...


予想通り、Lib#combineのシグニチャが変更になったため、NoSuchMethodErrorが発生した。

Version3 bridgeメソッドを用意する

バイナリ互換性が壊れたのは、Version2でcombine(other:Seq[String])がclassファイルから消えたためだ。そこで、最初のSeq[String]を引数に取るバージョンを復活させる。その際には、Traversableを引数にとるcombineへそのままforwardするようにしておく。このような、新しい実装へ単にforwardするようなメソッドを"bridge method"とよぶ。


このbridge methodに、@bridgeアノテーションを付けておくと後々いいことがある。

object Lib {
   
  import scala.annotation.bridge
  @bridge
  def combine(other:Seq[String]): Unit = {
    println("-- this is first version of combine(other:Seq[String]): Unit")
    combine(other:Traversable[String])
 }

  def combine(other:Traversable[String]): Unit = {
    println("-- this is second version of combine(other:Traversable[String]): Unit")
    println(other.mkString("/"))
 }
}


コイツをコンパイルして新しいLib.classを作る。

$ scalac -d lib lib/Lib.scala


Mainは再コンパイルせずに、再度実行してみる。

$ scala -cp lib:client Main
-- this is first version of combine(other:Seq[String]): Unit
-- this is second version of combine(other:Traversable[String]): Unit
アップキャスト/ダウンキャスト/クロスキャスト/静的キャスト/ドリームキャスト

こんどはちゃんと動いた。ここまでは、当たり前の話。

Version4 @bridgeアノテーションの効果

これで、シグニチャを変更してもbridge methodを用意すればバイナリ互換性は確保できることが分かったが、はっきり言って古いシグニチャのメソッドは余計なので、これから先このLibライブラリを利用するクライアントには無視させるようにしたい。実は、そのようなことを実現するために@bridgeアノテーションが存在する。


新しいVersion3のLibを利用するnewclient/NewMain.scalaを用意する。ソースファイルの内容は、さっきのMainと一緒だ。


newclient/NewMain.scala

object NewMain extends App {

  val seq = Seq("アップキャスト","ダウンキャスト",
    "クロスキャスト","静的キャスト", "ドリームキャスト")
  Lib.combine(seq)
}


これを、コンパイルして

$ scalac -cp lib -d newclient newclient/NewMain.scala


実行する。NewMainでも、combineにSeqを渡して居るので、bridge methodを経由してcombineが呼び出されるハズだが...

$ scala -cp lib:newclient NewMain 
-- this is second version of combine(other:Traversable[String]): Unit
アップキャスト/ダウンキャスト/クロスキャスト/静的キャスト/ドリームキャスト


このように、いきなりTraversableを引数に取る方のcombineが呼び出される。この仕掛けは、@bridgeアノテーションが付いたメソッドにはclassファイルの中に、このメソッドはbridge methodであるというフラグが埋め込まれるため。NewMainをコンパイルする際に、Scalaコンパイラはbridge methodのフラグが付いたメソッドを無視するようにするので、このような結果になる。 ちなみに、@bridgeアノテーションを付けなかった場合はちゃんと古いシグニチャのメソッドを経由して呼び出される。


これで、古いシグニチャのメソッドに依存するクライアントは再コンパイルなしに新しいバージョンのライブラリに移行でき、かつ新しいバージョンに依存するものはbridge methodのオーバーヘッドなしにライブラリを利用できる用になる。


TypeSafeでは、この@bridgeアノテーションを見てなんらかの処理を行う移行ツールを開発しているようだ。

Version5 奇声

っ!っあ…んくんどるすぁぁぁー! ひゃーっはーっぁーっぱぁんんっはー?んくんどるぷ? いやぁ…んんはー、 ふぇ!あふにょぱ? っ…んんっーーーー!! んんどるぽっー!!!!!おるすぁーっはぁぁん! にゅい!!!っぱぎゃっはにゅんどるぷ! モフモフモフモフ! モフモフ!!!!!!!!らめぇぇー、 い!ぃ、 あ!!!ぬぃ?にゅいっうううぅ…くんきゅわふーー!!! うっぱ!わぁ……きゅわーー? いっぱ、 おんくんっあ!きぇー! お!らぬぇ、わぁーーっ? んきゅいやっぱーーー…わぁぁぁ?ひゃっぁ? らめぇーー! きゃー…ぅ……んきゃぴーーーっきぇ。 ごるすぁっぁ! わふぁんどるすぁっはーーっー!!!!うわぁーっうううわぁんっ…きぇぁ、 らめぇぁーっあふーっはにゅいやぁっほぉおるぁあ?モフ!!! もるぽっほにょーーーっはにゅわーーっ…んきゃぴー?よぅぅ…い! おるぷぇぁ!ふぇー、ぎょぱぁ? …あんはにゃーー!にゅいっ…きぇー! ぃぱぁ…くんどるぽっ…あひぎゃう!!にょーっうわふぇ…ぅぅ……ん? ぃ?うぅうわぁぁ。はにょぱーっきゅわー! ほぉおるぁぁんはぁあふぇぁ…ん!もるすぁぁぁ…きぇぁああふにゃぴー!!いっ! ひぎゃっほぉおるぷ?ひゃぴー? あっきゅわふぅぅううっはぁぁあふぁぁーっぱ。 へぺぺ!ぅ……にゅんんどるぷぇぁ……くんどるぁ……あ? ぅぅ!!ん!へぺ! ひぃぃっーー!!!もるぁあぃ? っはぁー! おおんきぇ。 ぃぱぁぁ! おぺぺ! もるすぁー!ごるすぁんきゅわふぅ…あんきぇぁんはにゃー!! いっ…きゃぴー…よぅううっほにゅわふ、 もるぁあぁあ…ほぉおぺ!もるすぁあひぎゃあふぇー! へぺぺ… …あ!よぅ…きゅわぁ…くんどるすぁっぱぁっ!!!! モフ! ふぅ…きゅわふぬわふにゅんっ……くん!よぅ!! ぅ!うぅううふふ。ぅぅぅ……んどるぷ! ぅ! らめぇぇ!ふぁ…くんっうわー!きゃっ、 ぬわーっぱーっー!モフモフモフモフ! んきぇぁぁあ! きゅい!!ぅ!!!!ごぶぁっうわーっあ! ん!っぁー!!ごるぽっきゃぴー!!! …あっ…くん! っ…きぇぇぇぇぁーっほにゃぴー!!!!!! ひぃぃっ!はー! ぅうっあ? ぃぃっぁんっあふぅうふふふ、ひぃぱぁんはーーー、わーー。 っーー!!あひゃー… ふふぁあ…くんんどるぁ。 よぅぅううっきゃう!! よぅうぅぅ…んんくんきゃー?ぃぃっあんはにゅわぁ? っあひぎぃっはにょー!

Scala Interpreter Hacks!! ―REPLを組み込んだりevalしたり

最近REPLをHackするのが流行ってるみたいなんで俺もやってみた。scala.tools.nsc.interpreter.{IMain, ILoop}あたりのソースコードを読めば大体分かるよ。

REPLをコードに組み込む

これは結構簡単で。scala.tools.nsc.interpreter.ILoopを作ってやればよい。ちなみにscala2.8ではscala.tools.nsc.interpreter.InterpreterLoopって名前だったけど2.9ではILoopになった。

import scala.tools.nsc.interpreter._
import scala.tools.nsc.Settings
import java.io.{PrintWriter, FileOutputStream}

object MyREPL extends App{
  val settings = new Settings
  val out = new PrintWriter(new FileOutputStream("output.txt"))
  val iLoop = new ILoop(Console.in, out)
  iLoop process settings
}


このサンプルはREPLの出力をoutput.txtというファイルに吐く。それだけ。

文字列をevalする

次はeval。scala.tools.nsc.interpreter.IMainってクラスがあってコイツがinterpret(code:String):scala.tools.nsc.interpreter.Results.Resultってメソッドを持ってるのでコレを呼べばオーケーです。うまくいったらResults.Successってオブジェクトが返る。コンパイルエラーだったらErrorってのが返る。2.7の頃よりだいぶ楽になった。遅いけど、な。


Scala2.7の頃はevalした結果をリフレクションでほげほげするという涙ぐましい努力をしていたけど、2.8からはIMainにvalueOfTermとかvisibleTermNamesとか色々と便利なのができたのでそれでInterperter上の変数を取得できる。ま、evalなんて実用性はあまりないけど、REPLの中身はこんな風に動いているっていう参考程度に見てくださいなーーー。

import scala.tools.nsc.Settings
import java.io.{PrintWriter, ByteArrayOutputStream,  FileOutputStream}
import scala.tools.nsc.interpreter.{ IMain, Results => IR }

object Eval extends App{
  val settings = new Settings
  settings.usejavacp.value = true

  // interpret one line
  val main = new IMain(settings)
  main.interpret( "1 to 10 withFilter { _ % 2 == 0 } map{ _ * 2 }")
  main.visibleTermNames foreach println

  println( main.mostRecentVar )
  println( main.valueOfTerm("res0"))

  // import on interpreter
  main.quietImport("java.util.Date")
  main.interpret("val d = new Date")

  println( main.mostRecentVar )
  val d = main.valueOfTerm( "d" )
  println("evaluated value of d: %s" format d )

  // method definitions
  val code = """
def test(n:Int, s:String):String = {
  val now = new Date
  "%n:%s at %s" format(n, s, now)
}"""
  main.interpret(code)

  // calling defined method
  main.interpret("""test(99, "hoge")""")
  println( main.mostRecentVar )
  println( main.mostRecentVar )

  // complie error
  val rv = main.interpret("abababa nono )(!!")
  println(rv)

  main.close()
}


あとは、こんなユーティリティクラス作っておけばいつでもevalできるよね。

implicit conversionを定義する際に最低限チェックすべきこと

軽くイラッ☆っとしたので書く。


Scalaにはimplicit conversionってのがあってまぁ知らないならググれ。で、implicit conversionを定義する際に、最低限チェックすべきたったひとつのことを書く。


scala.Predefでimplicit conversionが定義されている型に対して新しいimplicit conversionを定義する際は、Predefで変換される型と、新しく定義する変換先の型とでメソッド名が重複しないかチェックしろ!!」


具体例で話す。scala.PredefにはStringからStringOpsへのimplicit conversion(もう長くてウゼェので以下ICな)が定義されている。StringOpsってのは文字列操作に関するわくわく便利メソッドが詰まってるtraitだ。

  implicit def augmentString (x: String): StringOps


で、 StringOpsにはlinesってメソッドがある。Stringから行のIteratorを作ってくれる非常によく使うアレな。

scala> val str = Seq("aaa","bbb","ccc").mkString("\n")
str: String = 
aaa
bbb
ccc

scala> val strops = Predef.augmentString(str)
strops: scala.collection.immutable.StringOps = 
aaa
bbb
ccc

scala> strops.lines
lines                 linesIterator         linesWithSeparators   

scala> strops.lines
res7: Iterator[String] = non-empty iterator

scala> str.lines
res8: Iterator[String] = non-empty iterator


augmentStringでStringOpsへのICがあるから、いきなりstr.linesとか読んでも大丈夫になってる。ここまではまぁおさらい。


では、ここでおもむろに便利すぎて汁漏らしたと評判のscala.sys.processパッケージをimportしてみる。


Scala 2.9.0のscala.sys.processパッケージが便利過ぎる件について - Scalaとか構文解析についてあれこれ書く日記

scala> import scala.sys.process._
import scala.sys.process._

scala> str.lines
<console>:12: error: type mismatch;
 found   : str.type (with underlying type String)
 required: ?{val lines: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method augmentString in object Predef of type (x: String)scala.collection.immutable.StringOps
 and method stringToProcess in trait ProcessImplicits of type (command: String)scala.sys.process.ProcessBuilder
 are possible conversion functions from str.type to ?{val lines: ?}
       str.lines
       ^

えっ?さっきまでちゃんと使えていたStringOps#linesが使えないよ?なぜなんだぜ?


これは、エラーメッセージにもあるように、scala.sys.process.ProcessImplicitsによってStringから ProcessBuilderへのICが定義されたからだ。

implicit def stringToProcess (command: String): ProcessBuilder


scala.sys.process.ProcessBuilderには、StringOpsと同じlinesって名前のメソッドが定義されている。だから、単にStringに対してlinesを呼び出した場合は、コンパイラがaugmentStringかstringToProcessかどちらのICを適用すべきか判断できないのでコンパイルエラーになってるって訳だよファック!!氏ね!!


これを回避するためには、以下のように型アノテーションを付けてメソッド呼び出ししてやれば回避できる。が、本来はライブラリ作成者が注意すればいいだけの話だ。

scala> (str:StringOps).lines
res14: Iterator[String] = non-empty iterator

このようなICでのメソッド名重複による影響は、救いがたいことにScalaSDK内でも結構ある。加えて、specs2などの著名なライブラリでもよくかち合ってしまう。クソが。


今のところ、このような重複をユーザー側のコードで解決させるには型アノテーションしかない。将来のScalaで、ICに関するもう少し細やかな制御(優先度付けや、スコープ内のICの無効化)などができるようになるといいねファッキン!!


ぎゃぴーっはぁっきゃっきゅい!!いやぁ…ほにゃっ、ぅうぅ!ぅ…きゅわーー!!!いっああ!!!ぎゃう!ぎょーっきゅんんはにゃっぱぎょーー!!っきゅい!!!!!!ぬい!!ぃぱ!モフモフモフモフ!!らぬわふにゃう!!!!!!!!!!!おるすぁん!きゅわふぇぁっきゅわぁっはーーっ…ああ!!うふぇぇ、わーっほぉおおるぁぁあひぎょーっあっ!ごるぁ…ふーー!っ、ぎゃっきゅんんどるぷ?ぎょぱー!モフモフ!おぺぺぺ?ぅ…あ…くんんはにょーっ!んくんっ!ぬぇ!はにゅいやぁんどるぁー!へぺっぁぁんっほにゅわふ、おるぽっきゃーっー!あ、らぬぇ、んはにょぱー?ぎぃい!!!!ぬぃぱ…きゃぴー!ぎぃっ!うぅ!!!!!ぃぱ!ごぶぁんはにゅわぁ…い!!!ふふぬぇー!ぬわぁー!きぇ!うっあぁんんきゃーっーー、きゅわふぬふ!ぎゃう!!!!!う!!!!!!にょーっほにゅわぁあふふぬふぁぁあひぃ?ごるぽっう!よぅぅ!!!!!っぁぁーーー、へぺぺぺっ!!!!きゃっぱぁぁー?ふぅ…くんどるぷぇぁっほにゃー!ふぁーーーっぁん!!!!ぬるぽっぱー!っううふぇー!い!!へぺっう!はー!!ああぁ…よぅ!ごるぷ?うわーーー!へぷ。モフ!!!はぁー!!!きぇぁ…あ…ん。へぷぇぇぁぁっほぉおるぁあっほにょぱー!!らぬぇぇ!っほぉおぺっー?モフモフモフ!!!らめぇぇぁー!!!はにょぱぁん!にゃっぱぎょぱぁああっぱ!きゃー!ぃいっ…きゃぴーっうっほにゅんきぇー。ふにゃーーーー!へぬい!あぃぃっうわふぁーっほぉおぺ!ん?っぱーっ!んんんくんくんん!ごぶぁーー。ぎぃ…っきゅわふぇぁんっきゃーっほぉおるぽっほぉおん!ぬわふ!ひぎゃあ、ぃいっ!!!い!!っほにょー!!!ひゃっほにゅわーーっはにゅいっぱぁんっ…あぁっ…あぃぱぁっ…くんくんくんきぇー!ぬふふふぅぅうふぅ!!!!ぅう!っはにょー?う!!うっ!はぁん。ほぉおんんっ!ほにゅんはぁ…あんどるぷぇぁんきゅんんどるすぁんっきゃぴーー!ひぎょぱぁーーーー!ふーっぁ……きぇ!ぬるすぁぁあ!ぬわぁぁっはー、へぺ?ふぁぁ…ああふぬふぬふ!らめぇー!ひぎゃあ…きゅいやぁんくんはにゅんきゅわー!はにゅい!モフ!!もるすぁあ、あひゃぴーっぁっううっ…あんん!!ひぃぱぁ…あ……あひぃぃぃぱ?ほぉおぺ、ごぶぁあ!!よぅ!わふぬふ!っぁ…あふぅ……んきゅいっほぉおおんっぁあ…あんっあふぬふー。わふーっあふー?いっあ!ぎゃう!…あぁ…あ…らぬぃぱぎょー!!!!モフモフモフ!ぎぃ…あんはーっぱ?よぅぅ…んくんどるぁあ…おおぺっあふにゅわぁー!!はーー、へぺっ?ん、へぬいっはー、らめぇ。にょぱーーっうっ!ほにょぱぁーーー?おるぷ、っぁっー。…んはぁ?っうふー!わふぬぃ?わふーーっあひゃあんきゃあ!いっあんどるぷ…うわー!きぇぇぇぁーーー!!ぬいやっうぅう!!ぎゃっきぇ!ひゃぴー!へぬわふぬるぁ…へぷぇぁんくんはーっ!ぬぃいやっー!!!!ぅ!ぬぃっあ!!!ぃぃぃっああん!わーっあ…くんどるすぁーーー!ふぁっー!っ!!にょーーっーーっああぁ…う!い!いっ…んきゃううぅ…ん。きゃっううっほぉおおんはにょーっうぅう!にゃあふぁあぁあんはぁ…きゃうわーー、…きゅわふにゃぴーっ…もるぁ。んはーっーっほにゅわふ、ふぇ?ほにょぱー!!!らぬぇーっぁ…あ…もるぷぇ?ううぅ!あ!!!!!おおるぽっ!へぬい!!!!!モフモフモフ!!きゅんはにょーーっ…くんっ!にょー、わぁんんきぇぇぁぁっ…くん!わぁ!ぅううわー、っーー!!モフモフ!!い!!ぃぱぎゃーー!らめぇぁーーーーー!!ほぉおるすぁぁ…きゃーっうぅ!へぬるすぁ。……くん!わふーっはぁあひゃぴーー!!へぷぇー…ぬるぷ。おぺぺぺ!わー!!あっ!scala> println(g) ぬいっ!わー!!!モフモフ!!おるぽっうふふぇぇぇぇ。モフモフ!!ぃぱーーっ!!んはにゃぴー。あふ?にゃぴーーー!うわふ?わふぇ。っほぉおおぺぺ…わー!モフ!お!っー…っぁー!ひゃー!いやぁぁ…あんはにゃあひぎゃーーーーー!あぃいっきぇー。ぬるすぁ…んきゃっうわぁーっぁあぃぱぁーっきぇぁんどるぷぇぁっ!!!ほにゅん。はにょーっあんっきゅんはーーー!ぅ…きゅい!んきぇー!!ぅ…んはぁー!!!!ぬるぷぇぁっうぅ…きゃー、ほにゃー!ぅ!おんはにょぱぎゃっー?へぺぺぺっぱぎょーっあひぎぃいやぁー!モフモフモフ!!ぬるすぁ………あんはぁぁん…はぁん…ふふ。ごぶぁあぃっ!んんどるぽっ。モフ!ぃ、にょぱぎゃう!!!!あっ…くん!ぬわぁぁ…きゃあんはにゃあふふふふにゅいっほにゅわふふー!!きぇ?っぁ。わふ!よぅう!モフ!らめぇーー…もるぽっ…くん!!!あっぱ、あふぇー!はにゅいっ。っ…くんはーー、ぎぃっうふぁ?っはぁ!ん。もるすぁあふぁ、ぅうっきゃうっぱぎぃぱーっ?いやぁん…へぷ?はぁっはーー!おぺ?うわー。ぎぃ!にゃうぅ…きぇぇぇぇ?よぅううう!!うふぁあん!!!!!よぅぅ!はーっ……くん!ぎゃあぁあひぎょぱぁーー?よぅううっ!!!んんっほぉお!ごるぁあひぃぃいやっう!もるぽっー!ごぶぁあ…くんくんきゅん。よぅううふぅ…くんんんはぁ…おぺっ!うふぬるすぁっきゃぴー?ほにょぱぎぃいっあ!!!!ひゃあぃぱぁぁんくんんはぁーーー?へぺ!もるぁあ…あっ!ぬいやっー?ぅうふぇ、はぁ、はにゅい!ぬぇ。ぎぃぱー!いやっぁ、ふぁ?あひゃーーっ!はぁああっーーっぁ。ほぉおるぽっー!ぅうぅうぅ!!いやっ…ん!っーー!んきぇ、ぎゃあ、ほにょぱーー?ふにゅわー、っぱ!はー!ほにゃうぅ……んんきゃっ…あ!!ひぎゃぴー!ぅ…あ!んどるぷぇぇ。ひぎょーーっぁ…くんどるすぁんくん!っあひゃー!!!!ああぁんどるぷぇ、ごるすぁー!!!!おぺぺぺ…ごぶぁん!にょぱぁあぁぁぁぁ…んっー!よぅう!!よぅぅぅ…ぬぇぇー。きゅんきゅんっ……いっあ?もるぁ…ああ!よぅぅぅ……きゅんきぇぁあ!ん!きぇぁあひぃいっ!ぎゃっうわふ…ぃ?ぬふぁぁ…んんどるぷ。はぁっうわふ?……くんん!らぬいやっうふぬわぁ、あ。…ぃっああ…あんんんはー!きぇぇ。ほぉおぺぺぺっきぇぁんきゃう!きぇぁぁ。っほぉお!!おぺ?へぺ、ぃぃ!うう!!!おぺっ…あふぬぃっあひぎょぱーっきゃっ、…あっあ!!らぬぇー、おぺ…あ?らめぇ、うっー!!!ぃぃっーっほにゃうわふにょーっあ……きゃうわぁぁっぱぎゃっぁん!にゅわーっぁ…わふぬぃぃっぁあんくん…モフ!!ぃぱー!ふぬわぁんどるぽっ…くんくんはぁぁんんはーーー?ぅ…くんどるぷ?らぬいやぁ…きゅん?にょぱぎゃぴーっ…はぁーっ!モフ!にゅんきゅいっぱ!うわふふ。ぬぃ!いやぁ……きゅわふぅぅ…あ…くん!!おぺぺぺっ…あっ!い!きぇーー!モフモフ!!!…もるすぁあぃいっうう!!!うわふにょーー、ぃ…ああふぬぇぁ…んきゃうううう!!!おんはーーー、よぅ…わぁあひゃっ…んはにょーーっきゃー。らめぇ!っ?ぬぃ。よぅ…あ!ぎぃぱ!よぅ…にゅいやぁっう!!!!!!ふふぇ!んはぁー!!はにゃぴー!!!はー!!ひぃいやっ。モフ!!!!!よぅ…きゅんくんどるぷ!いっあふ?よぅ…んっ…くんっあぃい!らめぇぁぁーーーー。あぁ!へぬぇ。ごぶぁーっ…ひぃっほぉお!もるすぁー!ぃっぱぎょぱぁあ…ん!…よぅぅぅ…あぁ…きぇ…はにょぱ?よぅ!…くんくん!…んきゅいっうぅぅううっー、ぎぃ!ぎょーっー!!

Java SE 7 - Project Coinの整数リテラル区切り文字をScalaにも

Java SE7も Developer Preview Releaseが出てもうみんな当然のように入れてると思います。
で、JavaSE7に入るProject Coinに、整数リテラル区切り文字ってのがあります。ようは、数値リテラル中で"_"による区切りを入れることを許すって仕様です。

 int a = 123_456_789; 
 int b = 0xFF_99_66;
 int c = 0b1010_1000_1111;

Java技術最前線 - Java SE 7徹底理解 第1回 言語仕様の小さな変更 - Project Coin:ITpro


この仕様は最新のScala2.9.0にも入ってません。ScalaJavaとはコンパイラが違うから当然ですね。


f:id:yuroyoro:20110510181246p:image


で、これをScalaでも使えるようにしてみます。やり方はとっても簡単。最新のScalaコンパイラソースコードをGithubから取得して、以下のパッチを当てるだけです。

diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
index 4323dd5..4dd4c58 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
@@ -769,8 +769,8 @@ trait Scanners extends ScannersCommon {
       def isDigit(c: Char) = java.lang.Character isDigit c
       val base1 = if (base < 10) 10 else base 
         // read 8,9's even if format is octal, produce a malformed number error afterwards.
-      while (digit2int(ch, base1) >= 0) {
-        putChar(ch)
+      while (digit2int(ch, base1) >= 0 || ch == '_') {
+        if( ch != '_') putChar(ch)
         nextChar()
       }
       token = INTLIT


このpatchを当ててコンパイルし直すと、整数リテラル区切り文字を使ってもコンパイルエラーになりません。実装はものすごい適当だから仕様的に正しいかわからんけどねーアハハハハハハハ。


f:id:yuroyoro:20110510181247p:image


0b0111とかの2進数リテラルは面倒だからやってないけどそんなに難しくないと思う。コード修正するよりコンパイルの方が時間かかったのは内緒だぜ?

モテるScala女子力を上げたい人向けにScalaの本が出ます。

秀和システムさんからScalaの本が出ます。発売は多分6月くらいです。 "オープンソース徹底活用"シリーズとして、Scalaをより実践的に使うための色々な事を盛り込みました。タイトルはまだ未定です。


@kmizu, @ryugate ITプランニングさんから(@osiire @keigoi 他)、 監修 @keisuke_n というメンバーと俺で共著という形で執筆しました。 4月末に脱稿して今校正作業中です。自分で言うのもアレですけど、今までのScala本とはまったく異なった面白い内容に仕上がったと自負しています。ITプランニングさんには貴重な経験を提供して頂きましたし、@kmizu や @ryugate は精力的に原稿の取り纏めやスケジュールの管理をしてもらいました。俺はほとんど何もできてないんですが、宣伝くらいはさせてもらおうと思ってコレを書いてます。

どんな内容なの?

最初に断っておきますが、プログラミング初心者向けの本ではありません。ある程度Javaなどのオブジェクト指向プログラミング言語を理解してる読者に向けたものです。 この本のコンセプトは


Scalaに少しでも興味を持っている層(主にJavaユーザー)にむけて、 既存の書籍ではあまり記述されていない実践的な内容を提供する」


です。この本は、Scalaの言語仕様や機能を網羅的に解説するものではありません。そういう本は、コップ本やプログラミングScalaが既にあります。むしろ、それらの解説書を読み終わった読者にこそ読んで欲しいと思っています。


構成は、概要編と実践編の2部に別れています。


概要編では、Scalaについての紹介や文法、機能についての解説、そして関数型プログラミング言語としてScalaについてです。特に、関数型プログラミングのところは@kmizu 渾身の出来映えで、関数型になじみの無いプログラマには是非読んで欲しいです。


実践編は、実際にScalaを使いこなすための色々が盛り込まれてます。開発環境やテスト、Webアプリケーションフレームワークデザインパターン、そしてITプランニングさんが実プロジェクトでScalaを投入した経験など盛りだくさんの内容です。多分こういった生々しいことに触れてるScala本はないと思います。あと、Scalaでのデザインパターンでは、ジェネリックスを利用した様々な手法が紹介されてます。Scala以外の言語でも応用できるものが多くありますし、このあたりのパターンの情報が日本語リソースになったのは多分始めてだと思います。あと、Play! frameworkや限定継続について書いてる本もないと思う。


自分は、主に文法や機能の紹介とLiftを利用したWebアプリケーション作成のところを担当しました。


もともと、この本は初心者も含めてScalaの入門書的な位置づけだったのですが、紆余曲折があり、趣味に走った内容に途中で路線変更しました。で、この本は執筆陣が「こんなScala本があったらいいなー」と思ったトピックをブチ込みました。加えて、コップ本やプログラミングScalaの次に読むべき本として、できる限り"実践"を意識した内容になっています。是非、ご一読ください。

目次

  ・まえがき

  ・概要編
    1章 Scalaの概要 
    2章 文法・構文・言語としてサポートする機能 
    3章 関数型プログラミング
    4章 他言語との比較、関連 

  ・実践編
    5章 開発環境 
    6章 Webフレームワークを利用してアプリケーションを作成してみる 
    7章 デザインパターンとScala 
    8章 現場でのScala 
    9章 Javaとの連携
    10章 特徴的なライブラリ
  ・付録:標準API簡易リファレンス