コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2014年1月26日 >>> 2014年1月17日
link もっと後

2014年1月26日

Scalaの中間記法の優先度

ScalaとJavaでシフト演算子の優先度が違うのは、決して嫌がらせではなくて「Scalaの優先度は、メソッド名の先頭の1文字で決まる」ルールから来ているようです。

素人目線だと2文字見れば良かったんじゃ…?と思いますが、そうすると何か大変なことがあったのでしょう。たぶん。

それはさておき、Scalaで中間記法を使ったときの優先度は、低い順から、


(all letters) 注: アルファベットとか
|
^
&
< >
= !
:
+ -
* / %
(all other special characters) 注: ~ とか ? とか

注: Scala言語仕様Version 2.8の6.12.3 Infix Operationsより

となっています。シフト(<<)やビット演算(& |)は、比較演算(== !=)よりも「優先度が低い」はずです。

普通の & と =
scala> 1 & 2 == 5
<console>:8: error: overloaded method value & with alternatives:
  (x: Long)Long <and>
  (x: Int)Int <and>
  (x: Char)Int <and>
  (x: Short)Int <and>
  (x: Byte)Int
 cannot be applied to (Boolean)
              1 & 2 == 5
                ^


(& が優先なら)

scala> (1 & 2) == 5
res40: Boolean = false

(== が優先なら、こっちが期待値)

scala> 1 & (2 == 5)
<console>:8: error: overloaded method value & with alternatives:
  (x: Long)Long <and>
  (x: Int)Int <and>
  (x: Char)Int <and>
  (x: Short)Int <and>
  (x: Byte)Int
 cannot be applied to (Boolean)
              1 & (2 == 5)
                ^

ビット演算の方が優先度が低いので、上記の例のように「1 & (2 == false)」と解釈されて、型エラーになります。

謎の < と =
scala> 1 < 2 == false
res41: Boolean = false


(< が優先なら)

scala> (1 < 2) == false
res42: Boolean = false

(== が優先なら、こっちが期待値)

scala> 1 < (2 == false)
<console>:8: error: overloaded method value < with alternatives:
  (x: Double)Boolean <and>
  (x: Float)Boolean <and>
  (x: Long)Boolean <and>
  (x: Int)Boolean <and>
  (x: Char)Boolean <and>
  (x: Short)Boolean <and>
  (x: Byte)Boolean
 cannot be applied to (Boolean)
              1 < (2 == false)
                ^

比較演算の方が優先度が低いので、「1 < (2 == false)」と解釈され、型エラーになるかと思ったら、なりません。意味がわかりません。

編集者:すずき(2014/01/26 03:48)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2014年1月25日

演算子はメソッドだ

Scalaの特徴の1つは引数が1つのメソッドを演算子のように呼び出せることです(Rubyも同じですね)。背景には、基本型を廃して「全てオブジェクト」とする、言語の文法から演算子を廃する、という設計哲学があるようです。

しかし演算子の優先順位まで消滅して1 + 2 * 3 = 9になってしまうと(全て左結合させると ((1 + 2) * 3) になる)非常に混乱しますので、コンパイラが特殊なメソッド名だけ結合の優先度を変更します。

特殊なメソッド名を付けると優先度が変わる

class MyInt(v: Int) {
  val i = v

  def +(a: MyInt): MyInt = {
    new MyInt(i + a.i)
  }

  def *(a: MyInt): MyInt = {
    new MyInt(i * a.i)
  }

  def plus(a: MyInt): MyInt = {
    new MyInt(i + a.i)
  }

  def mult(a: MyInt): MyInt = {
    new MyInt(i * a.i)
  }
}

val a = new MyInt(1)
val b = new MyInt(2)
val c = new MyInt(3)

val r1 = a + b * c
val r2 = a plus b mult c

println("r1:" + r1.i)
println("r2:" + r2.i)
r1:7
r2:9

演算子もメソッドの一種ではありますが、上記のように旧来の演算子と同名のメソッドはやはり、優先度において特別です。単純に別名のメソッドで置き換えると、結合順序が狂います。

こうまでして文法上から演算子を廃したのはなぜだろう?誰かにメリットがあるはずなのだけど、何が嬉しいのかわからん…。

Scalaのシフト演算と比較

優先度と言えばScalaは優先度付けのルールがJavaと違います。例えば、Javaはシフト演算が比較より優先ですが、Scalaは優先度が同じです。

シフトと比較が同一優先度
(JavaならOK、ScalaではNG)

scala> 1 < 1 << 2
<console>:8: error: value << is not a member of Boolean
              1 < 1 << 2
                    ^

(本来やりたいこと)

scala> 1 < (1 << 2)
res1: Boolean = true

上記の例のように、Scalaで「(1) < (1 << 2)」のつもりで「1 < 1 << 2」と書くと、左から結合されて「(1 < 1) << 2」になって型エラー(※)が起き、ちょっと不思議な気持ちになります。

あえてJavaと違う優先度にしたのはなぜだ…。

(※)1 < 1はBooleanを返し、Booleanクラスは左シフトメソッド << を定義していないため、エラーになる。

編集者:すずき(2014/01/25 15:08)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2014年1月24日

Scalaでバイナリ解析2

前回の続き(2014年1月6日の日記参照)です。

おさらいすると、Booleanのリストをビット列に見立てて、任意のビット数を上位ビット〜下位ビットにOR演算し、Intのリストとして返すという処理をします。

ビット列 (0, 1, 0, 0, 1, 1, 0, 0, 0) に対して、(1ビット取る, 3ビット取る, 5ビット取る) という処理を行って、結果として (0, 4, 24) が返るイメージです。

やりたいこと

(入力1)
List(
  true, false, false, true, true, false, true, false,
  true, true, true, false, true, false, true, true
)

(入力2)
List(1, 2, 3, 4, 5, 6)

(出力)
List(1, 0, 6, 11, 21)

ビット列に相当するリストが入力1、何ビットずつまとめるか、という別のリストが入力2になります。

前回は特に触れませんでしたが、入力1と入力2の合計が異なるときの扱いについては悩みどころです。余ったリストを返すのが一番親切かもしれませんが、出力が3つになってしまいます…。

今回は、一番楽に(入力1>入力2)なら例外スロー、(入力1<入力2)なら切り捨て、としています。

前回の反省

前回使ったmapでは(入力:出力)の要素数を(1:1)にする必要があります。そのため(n:1)にしたければ、入力はあるが、出力はない「穴が空いた」状態を表現する必要が生じます。

Option型を使って、値はSome(値), 穴はNoneとし、最後にflattenを呼んでNoneを全部捨てましたが、あまり効率的とは思えません。

foldLeftはどうか?

入出力の要素数が一致していなくても構わないのがfoldLeftです。

foldLeftの場合

object BooleanToInt {
  def apply(g: List[Int]): ((List[Int], Boolean) => List[Int]) = {
    val o = new BooleanToInt(g)
    o.folder
  }
}

class BooleanToInt(g: List[Int]) {
  private var v = 0
  private var p = 0
  private var mp = g(0)
  private var gg = g.drop(1)

  def folder(a: List[Int], b: Boolean): List[Int] = {
    v <<= 1
    if (b) v |= 1
    p += 1

    if (p >= mp) {
      val result = a :+ v
      v = 0
      p = 0
      mp = gg(0)
      gg = gg.drop(1)
      result
    } else {
      a
    }
  }
}


val a = List(
  true, false, true, false,
  true, true, false, false,
  true, false, false, true,
  false, true, true, false
)

val b = List(1, 2, 3, 4, 5, 6)

val c = a.foldLeft(List[Int]())(BooleanToInt(b))

println(c)

コードがScala素人くさいのはさておき、SomeとかNoneが出てこない分、mapより良さそうです。

foldLeftの別形式

val c = (List[Int]() /: a)(BooleanToInt(b))

初期値のリストが長いときはfoldLeftを /: で書く手もあります。個人的には右側が被演算子になると読みづらいし、使うことはないですね…。

編集者:すずき(2014/01/25 12:57)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2014年1月21日

Scalaまだまだトライ中

最近はScalaとお友達ですが、未だに文法やら動きがワケわかりません。俺、この言語向いてないのかなー、なんて思いつつもチマチマと書いています。

そんな状態なので、Scalaっぽくない書き方をして後で気づいたり、間違えてバグらせたりで、気づいたら「死んでる…」となっていることが多いです。

グチャグチャになる前にテストを書こう、後でリファクタリングするときの役にも立つ、と思い立ちました。

思い立ったまでは良かったものの、Scalaのメジャーなテストフレームワークについて何も知りません。JavaでいうJUnitのようなスタンダードがあるととても助かりますが…。

ScalaTestがおすすめ?

Scalaのテストを調べてみるとScalaTestというのが有名処のようです。ScalaTestでは「テスト」ではなく「スペック」というようです。

私の理解だと「テストのためにプログラムの動作結果の羅列するのはおかしいんよ。振る舞いや期待する仕様を書けばテストになる方が自然なんよー。」というスタイルみたいです。振る舞いを書くところから開発するスタイルをBDD(Behavior Driven Development、Wikipedia ビヘイビア駆動開発の項)と言うそうです。へぇー。

思想は素晴らしいのですが、ScalaTestは従来のJUnitのような「テスト」から、振る舞いを書く「スペック」まで、何でも出来るようにしてしまったせいで、ゴチャゴチャです。初心者には何を使えば良いか判断がつきません。

とりあえず、良くわからないままですけどFlatSpecで書くことにしました。

ScalaTestとIntelliJ IDEA

私のScalaプログラミング環境はIntelliJ IDEA+Scala Pluginですので、これとScalaTestを連携させたいと思います。

といっても何も難しいことは無く、下記のようなFlatSpecのスペッククラスを書いたらProjectのツリー図から右クリックして、Runを選ぶ、ほぼそれだけです。

スペックの例

import org.scalatest.FlatSpec

class HogeSpec extends FlatSpec with Matchers {
  val hoge = "hoge"
  val fuga = "fuga"
  "hoge" should "starts with 'h'" in {
    hoge.startsWith("h") should be (true)
  }
}

テスト結果はIntelliJ IDEAの結果表示ウインドウに、ツリー状に見やすく配置されます。IntelliJ IDEA+Scala Plugin+ScalaTestのトリオはなかなか素敵です。

複数テストを同時に行うには?

スペックを書く方法はわかりましたが、1度に複数のスペックを動作させるにはどうしたら良いのでしょう?

さらに言うと1度に複数のスペックを動作させた結果が、IntelliJ IDEAの結果表示ウインドウにも出て欲しいですが、どう書いたら良いのでしょう??さっぱりわかりません…。

まとめ用のクラスAllSpecから、先ほどのHogeSpecと新たにFugaSpecを1度に動作させるという想定で、思いつく手だてを試してみたいと思います。

executeしてみる

まずは素直に、まとめ用のクラスで各クラスのオブジェクトを作りexecute() を呼びました。

executeでまとめる

import org.scalatest.FlatSpec

class HogeSpec extends FlatSpec with Matchers {
  val hoge = "hoge"
  "hoge" should "starts with 'h'" in {
    hoge.startsWith("h") should be (true)
  }
}

class FugaSpec extends FlatSpec with Matchers {
  val fuga = "fuga"
  "fuga" should "starts with 'f'" in {
    fuga.startsWith("f") should be (true)
  }
}

class AllSpec extends FlatSpec {
  new HogeSpec().execute()
  new FugaSpec().execute()
}
executeでまとめた結果
HogeSpec:
hoge
- should starts with 'h'
FugaSpec:
fuga
- should starts with 'f'
AllSpec:
AllSpec

コンソールへの出力ではまとまって実行されているように見えますが、入れ子になっていないし、IntelliJ IDEAは「NO TEST」という悲しい結果を表示します。ダメですね。

シングルトンの関数

次にHogeSpecとFugaSpecをシングルトンにしてスペックは関数内に入れ、まとめ用のクラスから呼んでみました。

シングルトンでまとめる

import org.scalatest.FlatSpec

object HogeSpec extends FlatSpec with Matchers {
  def hogeTest {
    val hoge = "hoge"
    "hoge" should "starts with 'h'" in {
      hoge.startsWith("h") should be (true)
    }
  }
}

object FugaSpec extends FlatSpec with Matchers {
  def fugaTest {
    val fuga = "fuga"
    "fuga" should "starts with 'f'" in {
      fuga.startsWith("f") should be (true)
    }
  }
}

class AllSpec extends FlatSpec {
  HogeSpec.hogeTest
  FugaSpec.fugaTest
}
シングルトンでまとめた結果
AllSpec:
AllSpec

何も出なくなりました。ダメだこりゃ…。

トレイトでMix-in

最後にHogeSpecとFugaSpecをトレイトにして、まとめ用のクラスにMix-in してみました。

トレイトでまとめる

import org.scalatest.FlatSpec

trait HogeSpec extends FlatSpec with Matchers {
  val hoge = "hoge"
  "hoge" should "starts with 'h'" in {
    hoge.startsWith("h") should be (true)
  }
}

trait FugaSpec extends FlatSpec with Matchers {
  val fuga = "fuga"
  "fuga" should "starts with 'f'" in {
    fuga.startsWith("f") should be (true)
  }
}

class AllSpec extends FlatSpec 
with HogeSpec
with FugaSpec {
  //empty
}
トレイトでまとめた結果
AllSpec:
hoge
- should starts with 'h'
fuga
- should starts with 'f'
AllSpec

コンソールへの出力ではまとまって実行されていて、IntelliJ IDEAにもテスト項目が認識されています。これはなかなか良さそうです。しかし、

  • 異なるスタイルのスペックを混ぜられない(FlatSpecとFunSpecなど)
  • HogeSpecやFugaSpecを単独で実行できない

という欠点もあってイマイチです。でも良い方法がわからんす、困ったもんです。

編集者:すずき(2014/01/21 02:59)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2014年1月20日

折り返し乗車は不可

「大回り乗車」ができる区間であっても、同じ経路を折り返して2度通ると不正乗車になります。下記路線でB駅からD駅に行く切符で、B → A → B → C → Dと乗ることはできません。B → A → Bの分を払わずに乗っているためです。


A - B - C - D
|       |
E - F - G

どうしてもB → A → B → C → Dという経路で行きたいなら、どこかでB → A → Bの運賃を追加で払う必要があります。

ええ?そんなのどうやって払うの?と一瞬考えましたが、A駅で一度降りて再び乗れば特に問題ないですね。面倒くさいけど。

折り返せると便利?

折り返したら何が嬉しいか?というと、

  • 隣接のA駅が主要駅(快速停車)かつ近い
  • 最寄りのB駅がヘボ駅(快速通過)
  • 目的地のD駅が遠い
  • A駅方向への電車がすぐ来る
  • D駅方向への電車がしばらく来ない

という条件のとき、通常の経路B →(普通)→ A → C → Dを使って行くより、B →(普通)→ A →(快速)→ B → C → Dと乗る方が早く着くことがあるので、嬉しいのです。

例えば、つくばに居た時の、研究学園駅(車停めたい時の最寄り)とつくば駅(主要)ですとか、今住んでる摂津富田駅(最寄り)と高槻駅(主要)が当てはまります。

実際には良くても5〜15分早く着くだけでしょうし、そのために追加運賃と乗り換えの手間を掛けることを考えると効果は微妙…。よほど急いでいる時でない限り、積極的にはやりたくないですね。

編集者:すずき(2014/01/20 03:26)

コメント一覧

  • IKeJIさん(2014/01/20 05:14)
    研究学園駅とつくば駅を想像すると、いまいちわからないと思いますが、座れるというメリットがあったりしません?
  • すずきさん(2014/01/20 09:16)
    >IKeJI さん
    始発駅が隣にある場合、座れるというメリットもありますね。
    Wikipedia には横浜駅→みなとみらい駅の例が載っていました。
    http://ja.wikipedia.org/wiki/%E4%B8%8D%E6%AD%A3%E4%B9%97%E8%BB%8A
open/close この記事にコメントする



2014年1月19日

大回り乗車

Facebookに「路線がループしているときの運賃の扱いがよくわからん」的なことを書いたら「大回り乗車」を教えていただいたので、ちょっと調べてみました。

電車の基本ルールとしては、乗る時に使った切符の範囲外に行ってはいけません。例えば、下記路線でB → Cの切符で乗りDまで行った場合、C → Dの運賃を追加で払う必要があります。


A - B - C - D

しかし途中に環状線が入ってたらどうなるでしょう?下記路線でB駅からD駅に行く切符で、B → A → E → F → G → C → Dと乗る場合です。一見するとB駅からG駅までの運賃も払う必要がありそうです。


A - B - C - D
|       |
E - F - G

ところがこの場合、ある条件を満たせばB駅からD駅の切符で乗車できます。これがいわゆる「大回り乗車」です。

大都市圏近郊区間

路線がループしている大都市圏には、運賃計算の特殊ルールが存在します。JRの説明によると「実際に乗った経路に関わらず、最も安い経路で運賃が計算される」というルールです。

このルールを逆手にとって、1駅分の切符を購入し、出発駅→目的の駅→出発駅の隣接駅、と乗ること(大回り乗車)が可能です。条件としては、

  • 圏内を出てはならない
  • 同じ駅を2度通れない(交差はダメなのか??)
  • 途中下車できない
  • 定期券は使えない
  • (条件ではないが)駅員に乗車経路を聞かれることがある

です。駅から出ない乗り方、例えば電車や駅の写真を撮りたい人、電車に乗るのが好きな人に向いていますね。駅の外に出たい場合には使えませんけどね。

鉄道関連の法律

ちなみに、不正乗車の罰については、鉄道営業法という古い法律(明治三十三年施行)に述べられているようです。罰則を述べていそうなところを摘んでみると、

第十八条の二
有効ノ乗車券ヲ所持セス又ハ乗車券ノ検査ヲ拒ミ又ハ取集ノ際之ヲ渡ササル者ハ鉄道運輸規程 ノ定ムル所ニ依リ割増賃金ヲ支払フヘシ
第二十九条
鉄道係員ノ許諾ヲ受ケスシテ左ノ所為ヲ為シタル者ハ五十円以下ノ罰金又ハ科料ニ処ス
第四十二条
左ノ場合ニ於テ鉄道係員ハ旅客及公衆ヲ車外又ハ鉄道地外ニ退去セシムルコトヲ得

要は、運賃チョロまかすなんてナメた真似しようもんなら「割増料金を取る」「罰金刑」「電車から叩きだす」ぞ、わかったかこの野郎。ということですね。

個人的には、罰金50円(※)の辺りに時代を感じます。さすが明治施行の法律です。

(※)罰金50円なら超安いしw余裕wwってのは思い違いで、実際には1万円か2万円になるようです(罰金等臨時措置法)。
JRの割増料金も洒落にならないし(鉄道運輸規程 第十九条)、不正乗車はやめた方が良いですねー。

編集者:すずき(2014/01/20 03:13)

コメント一覧

  • hdkさん(2014/01/20 23:12)
    大回り乗車、東京から埼玉、栃木、茨城、千葉を回って東京に戻るのをやったことがあります。何時間もかけて大回りすると、途中でちょっと改札の外に出たくなりますねw
    http://www.e-hdk.com/diary/d201010a.html#02
  • すずきさん(2014/01/21 00:51)
    >hdk さん
    うは、すごい大回り。良いですね。
    私もせっかく大都市圏(大阪)に居るので、景色の良い季節に環状線でやってみようかな。
open/close この記事にコメントする



link もっと前
2014年1月26日 >>> 2014年1月17日
link もっと後

管理用メニュー

link 記事を新規作成

<2014>
<<<01>>>
---1234
567891011
12131415161718
19202122232425
262728293031-

最近のコメント5件

  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」
  • link 24年1月24日
    KKKさん (02/19 02:30)
    「追伸です。\nネットで調べたらマイクロソ...」
  • link 24年1月24日
    KKKさん (02/19 02:25)
    「私もエラーで困ってます\n手動での回復パ...」

最近の記事3件

  • link 24年3月25日
    すずき (03/26 03:20)
    「[Might and Magic Book One TASのその後] 目次: Might and Magicファミコン版以前(...」
  • link 21年10月4日
    すずき (03/26 03:14)
    「[Might and Magicファミコン版 - まとめリンク] 目次: Might and Magicファミコン版TASに挑...」
  • link 24年3月19日
    すずき (03/20 02:52)
    「[モジュラージャックの規格] 古くは電話線で、今だとEthernetで良く見かけるモジュラージャックというコネクタとレセプタク...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 03/26 03:20