webネタ

Webエンジニアが業務に関係することをメモしていく

Scalaのリファクタリング小ネタ (例外)

例外時にデフォルトを取るような場合

// not bad
try { "a".toInt } catch { case e: Throwable => 0 }
// good
Try { "a".toInt }.getOrElse(0)

例外をOptionで取る場合

// not bad
try { Some("a".toInt) } catch { case e: Throwable => None }
// good
Try { "a".toInt }.toOption
// good
allCatch.opt { "a".toInt }

例外をEitherで取る場合

// not bad
try { Right("a".toInt) } catch { case e: Throwable => Left(e) }
// good
allCatch.either { "a".toInt }

Tryで取りたいがfinallyもしたい

// not bad
Try { "a".toInt } match { case t => println("logged"); t }
// good?
allCatch.andFinally { println("logged") }.withTry { "a".toInt }

関係ないけどOptionはIterableになるのか

// OptionにはIterableへのimplicitが定義してある
scala> Option(1).zip(Seq(2))
res14: Iterable[(Int, Int)] = List((1,2))

// こういうことも
scala> for {
     |   s <- Seq("1", "two", "3")
     |   i <- Try(s.toInt).toOption
     | } yield i
res13: Seq[Int] = List(1, 3)

try-catchよりTryかCatchを使うと大体の場合短く分かりやすく書ける気がする。

パスワードを省略したssh接続

一番いいのは、鍵認証(コマンド限定)。

鍵認証できないのなら、sshpass。(expectはやめよう)

ssh

引数から
./sshpass -p “password" ssh root@192.168.1.1

ファイルから
echo "password" > pw
./sshpass -f pw ssh root@192.168.1.1

環境変数から
export SSHPASS=password
./sshpass -e ssh root@192.168.1.1

rsync

./sshpass -p “password" rsync ./fuga.txt root@192.168.1.1:~/

sshpassもレスポンスの文字列を見てるから、基本的にはそうするしかないもよう。

rsyncは、--password-fileオプションがあるが、rsyncデーモンに接続する場合にのみ使用可能のよう。

gitの便利なfilterまとめ

gitのfilterは、checkout時 と add時 に任意のフィルターをかけることができる。

  • svnでやってた $Id$ みたいな変数の展開したり
  • コードを自動で整形したり
  • 任意のコマンド/スクリプトを実行できる

概念図

出元

$Gcid$にコミットIDを埋め込むフィルターを作ってみる

やりたいこと

git checkout時に指定ファイルの、

echo "$Gcid$"echo "$Gcid: bcc4bbe68b26a65a1597752a$"に展開。

git add時は逆に、

echo "$Gcid: bcc4bbe68b26a65a1597752a$"echo "$Gcid$"に戻す。

手順

  1. gitの設定でfilterを定義 (.git/config)
  2. フィルターの対象を定義 (.gitattributes)
  3. フィルター時に実行するスクリプトを用意 (embed-commit-id.rb)
  4. フィルターされるファイルを用意 (fuga.sh)
  5. 試す
.
├─ .git/config (~/.gitconfigでも/etc/gitconfigでも)
├─ .gitattributes
├─ embed-commit-id.rb
└─ fuga.sh

1. gitの設定でフィルターを定義 (.git/config)

フィルターの設定をする。

$ vi .gitconfig

[filter "embed-commit-id"]
  smudge = ruby embed-commit-id.rb
  clean = perl -pe \"s/\\\\\\$Gcid[^\\\\\\$]*\\\\\\$/\\\\\\$Gcid\\\\\\$/\"

2. フィルターの対象を定義 (.gitattributes)

フィルターをかけるファイルを指定する。

$ vi .gitattributes

fuga.sh filter=embed-commit-id

3. フィルター時に実行するスクリプトを用意 (embed-commit-id.rb)

smudgeフィルターで実行するスクリプト

$ vi embed-commit-id.rb

#! /usr/bin/env ruby

puts STDIN.read.gsub('$Gcid$', '$Gcid: ' + `git rev-parse HEAD`.strip + '$')

4. フィルターされるファイルを用意 (fuga.sh)

フィルターされるファイルに、変数を埋め込む。

$ vi fuga.sh

echo "$Gcid$"

ここで一旦全部コミットしておく

5. 試し

変数は未展開

$ cat fuga.sh
echo "$Gcid$"

checkoutする

$ rm fuga.sh
$ git checkout fuga.sh

チェックアウトを経由するとフィルターにより展開される

$ cat fuga.sh
echo "$Gcid: 3c6c5646726ba5948a9c0198f856bdce0be330de$"

変更してaddしてみる

$ echo "fuga" >> fuga.sh
$ git add fuga.sh

addするとフィルターにより変数が元に戻り差分が出ない

$ git diff --cached
 echo "$Gcid$"
+fuga

(もちちんcheckoutじゃなくてcloneでも同様の効果が得られる)

まとめ

スタイルチェック通らないとaddできないようにするとか、

任意のスクリプトをcheckout/add時に動かせるので色んなことが出来そう。

Scalaのリファクタリング小ネタ (コレクション)

mapしてflattenするなら、flatMap

// bad
.map { ... }.flatten
// good
.flatMap { ... }

filterしてmapするなら、collect

// bad
.filter { ... }.map { ... }
// good
.collect { ... }

mapしてreverseするなら、reverseMap

// bad
.map { ... }.reverse
// good
.reverseMap { ... }

flatMap内でmapするなら、for

例えば、List[Option[A]]のようなありがちな構造に何かするとき

// not bad..
l.flatMap { o => 
  o.map(_ * 2)
}
// good!
for {
  o <- l
  i <- o
} yield i * 2

さらにfilterを追加してみる

// not bad..
l.flatMap { o =>
  o.filter(i => i == 2).map(_ * 2)
}
// good!
for {
  o <- l
  i <- o if i == 2
} yield i * 2

そもそも内部的にはflatMapに変換される

Listへ要素を追加するときは先頭に (参考表)

末尾への追加やsizeの取得はO(n)かかってしまう。ケースごとに適切なコレクションを使う。

// bad
Seq(3, 2) :+ 1
// good
1 +: Seq(2, 3)

map等の中でmatchは省略

// bad
.map { op => op match { case Some(i) => i * i case None => 0 } }
// good
.map { case Some(i) => i * i case None => 0 }

filterしてsizeするならcount

// bad?
.filter(_ % 2 == 0).size
// good
.count(_ % 2 == 0)

タプルの数値アクセスは極力控える

個人的には許したくない。絶対に。

// bad
Seq((1, 2), (3, 4)).map { t => t._1 + ":" + t._2 }
// good
Seq((1, 2), (3, 4)).map { (id, age) => id + ":" + age }

タプルは多くなったらcase classに

// bad
type ResultSet = (Int, String, Long)
// good
case class ResultSet(id: Int, name: String, width: Long)

Option等はコレクションの関数を使う

// bad
Option(1) match {
  case Some(1) => true
  case _ => false
}
// good
Option(1).exists(_ == 1)

// bad
if (o.isDefined && o.get == "a") Some(o.get * 2) else None 
// good
o.collect { case q if q == "a" => q * 2 }

大量の要素を色々するときは、viewを使う

// bad
(0 to 10000000).map { ... }.filter { ... }.scan { ... }
// good
(0 to 10000000).view.map { ... }.filter { ... }.scan { ... }.force

EffectiveScalaは読むべし。

Scalaでソート

  • 数え上げソート (要素の最大値分箱を作っていれるだけ)
  • バケツソート (hash(要素の最大値/n)分の箱にいれて、箱の中でソート)
  • スリープソート (スリープしてechoするだけ)

数え上げソート。バケツソート。スリープソート。

Erlangだとpmapでスリープソート

sleepsort(L) ->
  rpc:pmap({sort, fun}, [], L).

fun(N) ->
  timer:sleep(N),
  io:format("~p ", [N]).

Scalaで最小全域木

  • プリム法 (スタートから重みの小さい道を辿る)
  • クラスカル(辿るのではなく重みの小さい道を選ぶ)
  • UnionFind (グループを作ることができる構造。クラスカル法で閉炉を見つけるのに使用)

最小全域木。プリム法とクラスカル法。UnionFind。