Dockerを使い始めて半年弱

Dockerを使い始めて半年弱ぐらい経ったような気がする。

Dockerを知ったきっかけは、はてブTwitterのTLに流れていたのを見たのがきっかけだと思う。

(みんな新しい技術どうやって知ってるの?と思う人はそういうところをこまめに見ればよい。気に入ったブログはFeedlyに登録すればいい。)

仕事では開発サーバにDocker on Ubuntu on KVM on Ubuntu on BM(Bare Metal)な状態で、JenkinsやRedmineなどの開発用サービスコンテナとして使ってる。

(Dockerを使ってJenkinsでビルドとか面白いことは残念ながらやってません。:-P )

Docker meetup Tokyo #3でもあったんだけれども、開発用サーバに使う人は1マシン複数コンテナが普通のようだ。

逆に不確定要素を排除したいプロダクションだと1マシン1コンテナだったりもするらしい。

私が、なぜ開発用サーバにDockerを使っているかといえば、私が以前立てていた開発用サーバは、私が便利とあらば、生産性をあげるために、ガンガン新しいサービスやアプリを入れてしまっていた。結果、そいつらの構成が1つのOS上でぐちゃぐちゃになってしまい、何がどう絡んでいるのか把握しづらいというプチ問題に発展した。

だから、とりあえず各開発用サービスがコンテナとして明確に分離されれば、少しはこのカオス状態がまともになると思い、Dockerを使った。Dockerfileがクソみたいになっても、Dockerが1.0に達していなくて不安な夜を過ごしても、このメリットを何度も自分に言い聞かせた。

やはり良い垣根というのはソフトウェアにおいては大事である。

また、一つの開発用サービスを用途ごとに複数立てたいという要求もあり、Dockerを使えばコンテナイメージを環境変数etcをかえて使い回しすればいいので、そういう意味でもDockerは役に立った。

たとえば、普通にJenkinsをYUMやらで入れたとして、これを複数インスタンス立ち上げるのはちょっと苦痛だろう。

そういう場合はDockerを使って、Jenkinsのイメージを使いまわせばいいのである。細かい要件はJenkinsスレーブ(こちらもDockerイメージ)に吸収させれば少なくともJenkinsマスタのイメージは複数インスタンスで使いまわせる。

Dockerfileは多くの開発者が慣れ親しんでいるCentOSベースに、ある程度万能な状態のコンテナを派生させて色々作ったが、今思えばもっとフラットに構成すればよかったと思っている。結局、万能層なんて置くと外から見て何がどうなっているのかわかりづらい。

docker pullして済むようなものについては、特に自分でイメージもつくらずに使っている。ただ、そんなことができるのはレアケースで、大概本格的に使うなら、自分でケツを拭けるコンテナじゃないと行き詰まることが多いはずだ。

既存のLinuxソフトウェアはDockerなんぞ知らんという作りになっているので、うまくイメージに落とせないことも多いし、開発用メールサーバとしてpostfixdovecotをコンテナに閉じ込めるのは私にとっては非常に苦痛だった。

この類の苦痛の回避策として、アプリをうまく事前準備なしで初期化動作をさせようとすると、コンテナに起動用シェルが待ち構えて本丸を最後にforkかexecかするケースが多い気がする。

タイムゾーンとかズレたり、好きなDNS見させるのに苦労したり、ログの吐き場所とか苦労したりなんか色々苦労した。

そもそもDockerを扱っていて、これLinuxの知識割りと要るよねと思うこと多数。今Dockerを自由自在につかえている人はLinuxに相当詳しいはず。

まあこういう雑魚が引っかかる問題はバージョン上がったら古い人しか知らないアホみたいな問題になっているのかもしれないけど。

それとプロキシを噛ませないといけない環境だと死ぬほど苦労した。

コンテナのyumやaptがそのままじゃプロキシ見ないので、じゃあ、どうするかっていう…。Dockerのデーモン自体がプロキシ見るのは簡単なんですけどね。プロキシの設定がコンテナに残るとか気持ち悪すぎる。

そもそもプロキシがなくてもビルド中のインターネットアクセスが割りと苦痛だったりする。キャッシュプロキシは不可避だろうか。

Dockerとは何か?

Docker初心者が口にする最も多い質問だと思う。

Linux専門の)VMの1種というと分かりやすいけど、違う。

(ただ、初心者にとってはSSHDをDockerで立てれば、それがたぶんいっぱしのサーバに見えてくる的な感覚?はあると思う。)

Dockerの哲学をあらわすのはアプリケーションコンテナという言葉だと思う。

たとえばJavaで何かのアプリケーションを作っていたとして、それがどんなJREで動くのか非常に曖昧だったり、どういうディレクトリ構造が実際にあるのか曖昧だったりで、やる気のある人はサーバ環境をChefとかで構成してみるものの、やや実環境の構成の不明瞭さに一抹の不安感が漂ったりすると思う。

そういった場合に、アプリケーションがどこかのディレクトリにインストールされ、どこかの/usr/binにあるバイナリが存在するのを期待するという受け身のアプリという姿勢から、1段階引き上げて、OSの環境ごとアプリを封じ込めて、それをそのままいろんな環境に持っていければ、あまり実環境の構成の不明瞭さを気にしなくてもよくなる。

これがDockerのアプリケーションコンテナという考え方であると思うし、Dockerfileもそれに準拠する形で作れば、そうそう狂ったものにならない、と思っている。

ただ、Dockerはあくまで道具だし、綺麗にならないものもいっぱいあるし、時間は有限だし、私イジけちゃうしってことで、まあただのアプリの檻として好きに使っても後ろ指さされることはないと思います。

Dockerの檻としてのセキュリティはどうなんですかね~。Dockerチームは確かそういうのにケアしているとか言っていたような気が1mmぐらいはしますが、セキュリティ確保は副次的なものだと思ってます。

信用できないコンテナとかは動かせないんじゃないんでしょうか。

Dockerで個人的に消化不良起こしているもの

  • Ambassadorパターン
  • supervisordという存在
  • ロールベース

Dockerで個人的に期待すること

AntでEclipseのECJ(Eclipse Compiler for Java)を使う

はじめに

知っている人にはよく知られていることですが、EclipseJava開発環境、JDTがJavaソースのコンパイルに使っているコンパイラは、OracleJDKなどとは違い、Eclipseが独自実装しているコンパイラ ECJ (Eclipse Compiler for Java)です。

ECJとOracle JDK(OpenJDK)のjavacと何が違うのかというと、

  • コンパイラの許容ルールが異なる。(おそらくOracle JDKより緩いケースが多そう)
    • これのせいでEclipseコンパイルできたとしても、javacでコンパイルできないソースが出るので、そういうのやめたい人はきちんとJenkinsとかで常時チェックかかるようにしましょう。
  • コンパイルが一部失敗しても成功したclassファイルは生成される。
    • それどころか失敗したクラスでさえ仮のclassファイルを生成させることができる。
    • ある程度のコンパイルエラーを許容しなければならない荒っぽいプロジェクトでは使える性質でしょう。
  • コンパイラオプションが異なる。
  • 警告などの出力フォーマットが異なる。
    • JenkinsでWarnings Pluginを使っている場合は、ECJ用のルールがデフォルトで存在しています。

ECJはEclipseを立ちあげなくてもAntから使えます。(batch compiler)

んじゃあ、AntでEclipseJavaソースコンパイラ使ってみよう

プレーンなbuild.xml

たとえば、下記のようなフォルダ構成だったとします。

project-root/
  build.xml
  src/
    Hoge.java
  compiled/
    (Hoge.classがここにできればいいな)

この場合、build.xmlはこうなるでしょう。(わざとシンプルにしてます。Ant1.8以上)

<project default="build">
       <target name="build">
             <delete dir="compiled" />
             <mkdir dir="compiled" />
             <javac srcdir="src" destdir="compiled" />
       </target>
</project>

上記例だとパスの通ってるjavacを叩いているので、これをECJに切り替えましょう。

ECJを手に入れよう

ECJを構成するJARを手に入れましょう。

入手方法1. 直接ダウンロード

下記のMavenリポジトリ

http://mvnrepository.com/artifact/org.eclipse.jdt.core.compiler/ecj/

で見つかりました。 (groupId=org.eclipse.jdt.core.compiler, artifactId=ecj)

どっか公式DLページあるんですかね?(検索あきらめた)

ecj-..jar をダウンロードしましょう。JARの中にAnt用のアダプタとコンパイラコア本体が入っています。

ここでは project-root/ant-lib/ にそのjarをおいておきます。

入手方法2. Eclipseから取得

Eclipse本体からでも取得できます。

まずは下記の場所からJDTのコアのプラグインJARを見つけましょう。

eclipseインストールパス/plugins/
  org.eclipse.jdt.core_3.10.0.v20140604-1726.jar みたいなの

んで、上記jarをzipファイルとして解凍してAntへのアダプタとなる jdtCompilerAdapter.jar をとっておきましょう。

ここでは下記2つのJARを project-root/ant-lib/ にいれておきます。

  • org.eclipse.jdt.core_3.10.0.v20140604-1726.jar
  • jdtCompilerAdapter.jar

Ant関係なくECJを起動してみよう

ECJはスタンドアロンでjavacのように扱うことが可能です。javacと基本ほとんど同じですね。

java -jar ECJJARファイル コンパイルしたいソース.java

※ECJJARファイルはそれぞれ下記を使用。
 入手方法1の場合はecj-*.*.jar
 入手方法2の場合はorg.eclipse.jdt.core_3.10.0.v20140604-1726.jar
※マニフェストで起動したくない場合は起動クラスとして org.eclipse.jdt.internal.compiler.batch.Main を指定すればよい。
※依存するライブラリがある場合は-classpath "依存.jar"みたいな感じでjavaに渡してあげる。
※細かいオプションみたい人は -? 引数を指定してみてね。

ECJをbuild.xmlに組み込んでAntから呼んでみよう

ECJをtypedefし、それをjavacで参照、javacのcompilerでorg.eclipse.jdt.core.JDTCompilerAdapterを使うように指定すればOKです。

<project default="build">
     <typedef name="ecj" classname="org.eclipse.jdt.core.JDTCompilerAdapter">
          <classpath>
               <fileset dir="ant-lib" includes="*.jar" />
          </classpath>
     </typedef>
     <target name="build">
          <delete dir="compiled" />
          <mkdir dir="compiled" />
          <javac srcdir="src" destdir="compiled" compiler="org.eclipse.jdt.core.JDTCompilerAdapter">
               <ecj />
          </javac>
     </target>
</project>

typedef+<ecj />が面倒な人は、下記のいずれかのようにすれば、compiler属性の指定だけで使えます。

  • Ant起動時に-lib ECJのjarを指定する。
  • ${ANT_HOME}/lib~/.ant/lib にそのままECJ関連のjar放り込む。
  • typedefを作らず下記のようにcompilerclasspathをインラインで書く。
          <javac srcdir="src" destdir="compiled">
               <compilerclasspath>
                    <fileset dir="ant-lib" includes="*.jar" />
               </compilerclasspath>
          </javac>

compiler属性の指定が面倒な人は、下記のいずれかをすればいいでしょう。

  • <property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter" />をどこかに入れておいたり、
  • Antの起動引数でプロパティ指定しておけば

まあ、macrodefとか使ってもいいと思うし、お好きに。

動作させてみよう

さっそくこれを動かしてみると、(わざとエラー出させています)

Buildfile: F:\eclipse-luna\workspace\CompileWithEclipseCompiler\build.xml
build :
   [ delete] Deleting directory F:\eclipse-luna\workspace\CompileWithEclipseCompiler\compiled
    [ mkdir ] Created dir: F:\eclipse-luna\workspace\CompileWithEclipseCompiler\compiled
    [ javac ] F:\eclipse-luna\workspace\CompileWithEclipseCompiler\build.xml:6: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [ javac ] Compiling 1 source file to F:\eclipse-luna\workspace\CompileWithEclipseCompiler\compiled
    [ javac ] ----------
    [ javac ] 1. ERROR in F:\eclipse-luna\workspace\CompileWithEclipseCompiler\src\Hoge.java (at  line 5)
    [ javac ]       String missing_semicolon
    [ javac ]              ^^^^^^^^^^^^^^^^^
    [ javac ] Syntax error, insert ";" to complete BlockStatements
    [ javac ] ----------
    [ javac ] 1 problem (1 error)

BUILD FAILED
F:\eclipse-luna\workspace\CompileWithEclipseCompiler\build.xml:6: Compile failed; see the compiler error output for details.

Total time: 752 milliseconds

ちょっと出力ログ形式がOracle JDK(下記)と違いますね。

    [ javac ] F:\eclipse-luna\workspace\CompileWithEclipseCompiler\src\Hoge.java :5: エラー: ';'がありません
    [ javac ]             String missing_semicolon
    [ javac ]                                     ^
    [ javac ] エラー1個

もちろんコンパイルが成功した場合はclassファイルがcompiledフォルダに出力されます。

独自オプション

コンパイラのオプションは http://help.eclipse.org/juno/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_batch_compiler.htm とかにまとまっています。

たとえば-proceedOnErrorというのを下記のように指定すれば、(compilearg要素@line属性)

          <javac srcdir="src" destdir="compiled" compiler="org.eclipse.jdt.core.JDTCompilerAdapter">
               <ecj />
               <compilerarg line="-proceedOnError" />
          </javac>

エラーがでているクラスでも仮のclassファイルを生成してくれます。

もちろんコンパイルエラーが発生しているメソッドを呼んだりすると Exception in thread "main" java.lang.Error: Unresolved compilation problem: などと怒られます。

まとめ

Eclipseコンパイラを使うことでしか動作できないプロジェクト作るのやめてほしいです…(切実)

試しにDockerリポジトリにイメージ(knjname/jenkins)登録してみた

試しにDockerリポジトリに自作イメージ登録してみました。

無料アカウントだと1つまでプライベートDockerリポジトリを持てるみたいです。

今回はknjname/jenkinsというDockerイメージを公開リポジトリにpushしてみました。

SCM : https://github.com/knjname/docker-jenkins

Dockerレポジトリ : https://registry.hub.docker.com/u/knjname/jenkins/

登録する時はあらかじめリポジトリを作っておいて(今回はknjname/jenkinsとして作成)、下記のようにすれば勝手に作成したリポジトリに収まります。

docker push knjname/jenkins
# この後アカウント情報訊かれる

使い方は簡単。

docker run --name testjenkins -d -p 8080:8080 knjname/jenkins

http://localhost:8080 にアクセスすればJenkinsが立ち上がっているのが見えるはずです。

(2014-06-13 追記)なお、このあとGithubと連携するAutomated Buildを試すためにリポジトリを作りなおしたところ、未だにビルドがされていない模様…。

(2014-06-14 追記)ようやくビルドされました!なんかDockerhub自体はまだまだ不安定なところがあるかもしれませんね。

Docker 1.0が出たし、Windows(x64)でDockerが使えるようになりました

Docker 1.0が出ました。待ってたよ!

http://blog.docker.com/2014/06/its-here-docker-1-0/

そして64bit Windows用の、boot2dockerによるWindows用Dockerも出ました。

https://docs.docker.com/installation/windows/

とは言ってもWindowsLinuxが動くわけがないわけで、単純にVirtualBox上に小さなLinux(Core Linux)を動かし、その上でDocker(boot2dockerというイメージ)を動かしているだけですね。(OS X版も同じ)

別にWindowsファイルシステムをフル利用したUnionなFSが使われてるとかそういう夢のある話はありません。

てなわけで、結局は

  • DockerをWindowsでバリバリ使いたい人は、VirtualBoxでUbuntu14.10 Serverとか立ち上げてマニュアルでDockerインストールして使う
  • Dockerのイメージを動かしたいだけとか、忙しい人はWindows用のDocker(boot2docker)を使う

という具合になりそうです。どちらもVirtualBox上でLinuxが動くというだけの話です。

ちなみに入れてみたところ、感触としてはVagrantっぽい感じですね。

Swiftをチラ見しよう

なんかアップルから新しい言語、出たみたいですよ?

Welcome to Swift

どうせ多分一ヶ月後には忘れると思うけど、言語を一巡りしてみよう。体力ないのでLanguage Guideまでで失礼します。

ちなみに現時点において動作環境を手に入れるには、iOSデベロッパ契約が必要です。僕はありません。

このエントリは全て憶測で書いています。

このエントリは全て憶測で書いています。

このエントリは全て憶測で書いています。

このエントリは全て憶測で書いています。

ひとめぐり

(途中、私自身にしか通じないような表現が出てきます。)

// コメント
/* マルチラインコメント */
/* /* ネストしたコメントも可能! */ */

// main関数不要
println("Hello world") // C系にありがちなセミコロンも要らない

//// 変数 
// Swiftは強い型付き。
var v = 10 (型宣言しないと推論される)
var v: Double = 10.0 
var (x, y) = (1, 2)
(y, x) = (x, y) // ちなみに = 自体は値を返さないそうだ

// 定数
let c = 10

//// 文字列への埋め込み式 \(...)
let message = "I need to learn \( c ) languages."

///// コレクション
// 配列 (構造体の一種)
let list = ["apple", "bacon", "cheese"] // let list: String[] あるいはlet list: Array<String>、型[]は Array<型>の短い書き方
let intList = 0..10 // 0, 1, 2, 3, .. , 9
let intHalfList = 0...10 // 0, 1, 2, 3 .. , 10
var list = [1, 2, 3]
var doubleList = list + list // リストの結合
var mixture: Any[] = [0, "asdf", (1, 2)] // なんでもあり型はAny
list[0] // もちろんインデックスでアクセス可能
list[0..1] = ["BIG APPLE", "BACON PIE"] // 複数要素代入可能
let emptyIntList = Int[]() // 空リスト
// 構造体の一種とされているが、配列は代入によってコピーされないし、代入元とリスト内容を共にする。
// インデックスへの代入も共に影響をうける。
// しかし、配列に値を追加した場合 or array.unshare() した場合に配列がコピーされたものにいれかわる。
// 配列を代入するときに array.copy() で代入すれば最初からコピーされたものになる。

// ディクショナリ(マップ 構造体の一種)
let map = [
"New York": "The Big Apple",
"Las Vegas": "Sin City", // マップの末尾が , で終わっていてもいい
] // let map: Dictionary<String, String>
// 構造体の一種なので、ディクショナリを代入したり関数に渡したりするとディクショナリは値型のようにコピーされる。

//// 型

// deftype的な
typealias EnoughMemoryForHumanLife = UInt16

// String
// 値型の一種。
// \0, \\, \t, \r, \n, \", \'  などバックスラッシュ記法もある。
// \xnn (2桁 16進法), \unnnn (4桁 16進法), \Unnnnnnnn (8桁 16進法)
"🐶🐱" // 絵文字OK。
// そしてUTF-16エンコーディングなどされない。絵文字1文字も正確に1文字である。
// 文字数のカウント、任意位置の文字の取り出しは効率がO(n)であり、巨大文字列の文字数カウントはコストがかかる。
// カウントのコストはCと同じ。
// 1文字ずつ先頭からインデックスでイテレートしたらO(n^2)。恐怖。for-in文を使うこと。
"" // == String() 空文字

// Character
// StringはCharacterのリスト。
let c: Character = "ユニコード1文字"

// 数
// Javaとだいたい同じ
10
1000_000.000_001 // 1000000.000001
0b01010 // 10
0o12 // 10
0xc // 10

// Int8 = signed char,  UInt8 = unsigned char 相当, UInt16 なども
// キャストは構造体newと同じく キャスト後の型(値) でOK

// Bool(ean)
// if 文はBoolしかうけつけない
true
false

// Optional値。nullableってことです。
var optionalValue: String? = findSomething()
optionalValue! // これで中身アクセス
if optionalValue { }  // if文の条件とかにそのまま使える。Boolコンテキストで使える?
// Optionalにはnilを代入できる。nullではなくnilです。
// nilには厳しく、optional以外はnilを扱えません。
optionalValue = nil

// オブジェクトの作成は "クラス()" で。
let newObject = Class()

// タプル
let tupleValue: (String, Int) = ("あ", 1)
let namedTuple = (hiragana: "あ", at: 1) // namedTuple.hiragana でアクセス可能。名前も型の一部みたいな。
func returnTuple() -> (name: String, age: Int) {
  return ("ほげほげ", 100)
}
// 関数
// パラメータに=つければデフォルト値
func subtract(left: Int, right = 0) -> Int {
  return left - right
}

// あるいは
func subtract(l left: Int, r right: Int) { ... }
func subtract(#l: Int, #r: Int) -> Int { ... }
// で、呼ぶときはsubtract(l: 20, r: 10) といったように名前付き引数で指定可能。

// 引数変更したい人はパラメータにvarつけりゃいいよ
func foo(var youCanAssignThisVar: string) { ... }

// 参照渡ししたい人はパラメータにinoutつけりゃいいよ
func inc(inout i: Int){ return i++ }
var foo = 3
inc(foo)
foo // == 3
inc(&foo)
foo // == 4

// 関数は値として使える。レキシカルスコープを持つ。
// "-> 型" で関数の返りの型
func greeting(name: String) -> String {
  return "Hey, \( name )-san."
}
// func greeting(name: String...) と書けば可変長引数(VLA)

// レキシカルスコープかつ関数は値として使えるのでこういうのも可能
func curryAdd(x: Int) -> (Int -> Int) {
  func internalAdd(y: Int) -> Int {
    return x + y
  }
  return internalAdd
}
curryAdd(10)(20) // => 30

// 関数名を省略して、こうも書ける
func curryAdd(x: Int) -> (Int -> Int) {
  (y: Int) -> Int in
  return x + y
}

// 型がわかっている場合はこれも可能
func double(intList: Int[]) {
  intList.map({ eachInt in each 2 * eachInt })
}
double([1..100])

// クロージャ内の引数は "$数字" でも参照可能(ゼロベース)
{ $0 > $1 }

//// 演算子オーバーロード
// 中置演算子 + をオーバーロード       
@infix func + (left: MyType, right: MyType) -> MyType { return ... }
// ほか @prefix func - (a: A) -> A { return ... }
// @assignment func += (inout left: A, right: A){ left = ... }
// @prefix @assignment func ++ (inout a: A) -> A { a = ... return a }
// @infix func ==, @infix func != 

// カスタム演算子の定義(のち、演算子オーバーロード)
// prefix / infix / postfix で出現位置を指定:
operator prefix +++ {}

// クラス。C#と同じく対立する値型の概念として構造体がある。
// 継承関係は : のあとに親クラスでOK
class Hoge : Fuga{
  // クラスフィールド
  var myName
  @lazy var myStrictName = getMySecretNameFromFarDB() // @lazy使うと最初のアクセス時まで初期化を遅らせられる

  // コンストラクタはinit, thisはself, superはsuper
  init(name: String) {
    self.myName = "Pretty \(name)-chan"
  } // deinit() がデストラクタ

  // 普通のメソッド。オーバーライドを防ぎたい場合は@final fun thisIsAMethod() みたいにする。
  func thisIsAMethod() -> String {
    return myName
  }

  override func overwrittenMethod() -> Int {
    // オーバーライドしたメソッドはoverrideと絶対に書かなければならない
  }

  class func methodBelongingToHoge() {
    // Javaのstaticメソッド. クラスメソッド. Hoge.methodBelongingToHoge()
    self // ここでのselfは型自体
  }

  var height: Double
  var weight: Double
  // C#のようなプロパティ構文
  // willSet {}, didSet {} でプロパティ設定前後の動作を指定可能
  // overrideしてる場合は override var bmi: Double { ... } みたいにしてね
  var bmi: Double {
    get {
      return weight / (height * height)
    }
    set {
      // newValueとして代入文の右辺の値を貰える
      height = sqrt( 1 / ( newValue / weight ) )
      // newValueが気に入らなければ set( newBMI ) とかすればnewBMIという名前にできる
    }
  }
  // get { ... } のみなら、 get というキーワードは省略可能

  // subscript
  // 要するに hoge[...] のインデックス指定のアクセサ構文。
  // 1個以上の引数を指定可能.
  subscript(index: Int) -> String {
    get { ... } // indexを使って内部的なリストから値引っ張るなり
    set { ... } // indexを使って内部的なリストに値を引っ張り込むなり
  }

  // Nested types
  // 中に入れ子のenum / structure / classが定義可能
  // (たぶんJavaみたいに複雑な非static/staticクラスの区分けみたいのは無く、一律staticな感じかも)

}
// プロパティ/メソッドは . でアクセスできる
foo.bar
foo.bar()
// Optionalな変数の場合は ?. でアクセスできる
optionalFoo?.bar

// 構造体
// C#と似たらしい感じ。オブジェクトなら参照がコピーされるところで値ごとコピーされる。
struct Color {
  var r: Int
  var g: Int
  var b: Int
  func distance() -> Double {
    return sqrt( r * r + g * g + b * b)
  }

  // static変数もちたければどうぞ
  static var asdf = 1
  static func modifyAsdf(newVal: Int) {
    asdf = newVal
  }

  // 構造体のプロパティを更新するものはmutating修飾子が必要
  mutating func superRed() {
    self.r = 255
    self.g = 0
    self.b = 0
  }

  // そのままself自体入れ替えも可能
  mutating func superRed2(){
    self = Color(255, 0, 0)
  }

  // func init(){ } コンストラクタもあり
}
var c: Color = Color(255, 255, 255)

// Enum
enum Foods: Int {
  case Apple = 1
  case Bacon, Cheese, Donuts
  func tastes() -> String{
    switch self { // Enumのswitchは
    case .Apple, .Donuts:
      return "Sweet"
    case .Bacon, .Cheese:
      return "Salty"
    default:
      return "What the heck is this?"
    }
  }
  // enumもmutating持てる
  mutating func iAmAnApple() {
    self = Apple
  }
}
Foods.Apple.tastes() // "Sweet"
Foods.Apple.toRaw() // 1
fromOne: Foods? = Foods.fromRaw(1) // == Foods.Apple が来るだろうけどOptional
// 代数的データ型 (Algebraic Data Type) みたいな? のも可能
enum TreeEntry {
  case Node(TreeEntry, TreeEntry)
  case Leaf(String)
}
var t:TreeEntry = TreeEntry.Node(TreeEntry.Leaf("Big bro"), TreeEntry.Node(TreeEntry.Leaf("Me"), TreeEntry.Leaf("You")))

func traverse(t: TreeEntry){
  switch t {
  case let .Node(leftNode, rightNode):
     traverse(leftNode)
     traverse(rightNode)
  case let .Leaf(name):
     println("Found \(name).")
  } 
}

//// Protocol
// インタフェースみたいなの
protocol HavingName {
  var name: String { get }
}
class Cat: HavingName {
  // プロトコルの実装
  var name: String = "吾輩は猫である"
}
// プロトコルを複数実装したオブジェクトの型は obj: protocol<ProtocolA, ProtocolB> みたいに扱うことも可能で
// それぞれ obj.protocolAMember obj.protocolBMember みたいにそのままプロトコルメンバにアクセス可能   

//// Extension
// C#のExtension, Rubyにも似たようなのあったね
extension Int { // よく Int : ProtocolName1, ProtocolName2 のようにプロトコルをおっかぶせたりするらしい
  func double() -> Int{
    return self * 2
  }
}
let pi = 3
pi.double() // == 6

//// Generics
enum Tree<LeafType> {
  case Node(Tree<LeafType> left, Tree<LeafType> right)
  case Leaf(LeafType)
}
func findAll<LeafType>(t: Tree<LeafType>) -> LeafType[] {
  switch(t) {
    case let t.Node(l, r):
      return findAll(l) + findAll(r)
    case let t.Leaf(v):
      return [v]
  }
}
// 型の制約
// <A, B, C where ... > というように where 以降に書く。細かい制約もかけるっぽい。

//// 演算子 だいたい普通の演算子は使える。
i++
i += 10
9.2 % 3.0 // == 0.2 doubleの除算もできる
bool ? true-case : false-case // いわゆる三項演算子
a === b // アイデンティティ比較
a is Type // a は Typeを実装しているか?
a as DownCastType // a を DownCastTypeとしてキャスト
a as? DownCastType // a を DownCastType?としてキャスト(キャストできなきゃnil)

//// 制御構造
//// if 
if Bool式 { } else if { } else { }

//// Optional Binding(if-let 式)

if let foo = optionalな値を返す式 {
  message = "The found value is \(foo)" // ちゃんと値があった場合の分岐 
} else {
  message = "The value is not found." // デフォルト値処理とかこっちでやればいいんじゃない?
}

//// for-in
for member in memberList {
}

// for-in+分配。マップのエントリごとループなど。
let map = [ "New York": "The Big Apple", "Las Vegas": "Sin City"] 
for (cityName, cityNickname) in map {
}

// 普通のfor文
for var i = 0; i < 10; ++i {
}

// while / do-while
while i > 0 { }
do { } while i > 0

// switch
switch customerName { // スイッチ対象の値の型はいろいろできるらしい
case "knjname":
  println("All our property are belong to you.")
case "knjname2": // 基本的にfall-throughしない
  println("You're faking.")
case "knjname3", "knjname4" // 複数の値にマッチさせたい場合
  println("3 or 4")
case "knjname5"
  println("5 is good and also ...")
  fallthrough // fall throughしたい違いのわかる大人用
case "knjname6"
  println("6 is better.")
default: 
  println("Your life is under knjname's control.")
}

// こういうこともできる
switch hoge {
  case 0 as Int:
    ...
  case 0 as Double:
    ...
  case is Double:
    ...
  case let (x, y) as (String, String):
    ...
  case 0..10:
}

case 1..100: // みたいなのも数値の振り分けには使える

// ラベル
// Javaと同じ。制御構造にラベルをつけてbreak/continueに飛べる
outside: while ... {
  while ... {
    if(...) break outside
  }
}

感想

  • とーってもC#っぽい。Optionalとか値型とかプロパティとか。ここらへんの感想は人によって違うんだろうけれども。
  • 正直、言語としてパクりだらけだとか、新しいアイディアがないとか、そういうのはわかりますけど、ちょっと違うと思うんです。既存に比べて、使いやすい、わかりやすい、これ大事じゃないでしょうか。
  • 文字列は文字のリストなのは普通だけど、文字がユニコード1文字というのはすごい。UTF-16でお茶を濁す言語とちがって、🐶(絵文字)が1文字です。当然文字数カウントはO(n)みたいです。
  • 意外に言語の機能多い。

JDK8のjdepsは普通の業務開発でも便利

JDK8からjdeps(http://docs.oracle.com/javase/8/docs/technotes/tools/unix/jdeps.html)というJavaのクラス間の依存性を調べるツールが追加されました。

Java Day Tokyo2014ではこのツールを使ってJava9のProject Jigsawのための作業をしていると言っていたような気がしましたが…、そんなことより、このツールすごくSIer向けだと思いました。

使い方

使い方は簡単。あらかじめ依存性を調べたいソースをコンパイルしておいて

jdeps コンパイルしたクラスのディレクトリ
 または
jdeps JARファイル

とするだけ。(依存クラス・JARがJDK以外にいる場合は別途クラスパス指定可能)

そうすると

$> jdeps clojure
clojure -> /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar
   clojure (clojure)
      -> java.awt
      -> java.beans
      -> java.io
      -> java.lang
      -> java.lang.annotation
      -> java.lang.reflect
      -> java.math
      -> java.net
      -> java.sql
      -> java.text
      -> java.util
      -> java.util.concurrent
      -> java.util.regex
      -> javax.swing
      -> javax.swing.table
      -> javax.xml.parsers
      -> org.xml.sax
      -> sun.misc                                           JDK internal API (rt.jar)
   clojure.asm (clojure)
      -> java.io
      -> java.lang
      -> java.lang.reflect
   clojure.asm.commons (clojure)
      -> java.io
      -> java.lang
      -> java.lang.reflect
      -> java.security
      -> java.util
   clojure.core (clojure)
      -> java.lang
      -> java.util
      -> java.util.concurrent
      -> java.util.concurrent.atomic
(以下略)

(例としてClojure - https://github.com/clojure/clojure を使いました)

-vで、クラスごと出すことができます。

$> jdeps -v clojure
clojure -> /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar
   clojure.asm.AnnotationVisitor                      -> clojure.asm.Opcodes                                clojure
   clojure.asm.AnnotationVisitor                      -> java.lang.IllegalArgumentException
   clojure.asm.AnnotationVisitor                      -> java.lang.Object
   clojure.asm.AnnotationVisitor                      -> java.lang.String
   clojure.asm.AnnotationWriter                       -> clojure.asm.AnnotationVisitor                      clojure
   clojure.asm.AnnotationWriter                       -> clojure.asm.ByteVector                             clojure
   clojure.asm.AnnotationWriter                       -> clojure.asm.ClassWriter                            clojure
   clojure.asm.AnnotationWriter                       -> clojure.asm.Item                                   clojure
   clojure.asm.AnnotationWriter                       -> clojure.asm.Opcodes                                clojure
   clojure.asm.AnnotationWriter                       -> clojure.asm.Type                                   clojure
   clojure.asm.AnnotationWriter                       -> java.lang.Boolean
   clojure.asm.AnnotationWriter                       -> java.lang.Byte
   clojure.asm.AnnotationWriter                       -> java.lang.Character
   clojure.asm.AnnotationWriter                       -> java.lang.Object
   clojure.asm.AnnotationWriter                       -> java.lang.Short
   clojure.asm.AnnotationWriter                       -> java.lang.String
   clojure.asm.Attribute                              -> clojure.asm.ByteVector                             clojure
(以下略)

-pで指定のパッケージだけ出すということもできます。

$>  jdeps -v -p clojure.core clojure
   clojure.core$fn__6136$__GT_VecNode__6138           -> clojure.core.VecNode                               clojure
   clojure.core$fn__6145$__GT_ArrayChunk__6147        -> clojure.core.ArrayChunk                            clojure
   clojure.core$fn__6150$__GT_VecSeq__6158            -> clojure.core.VecSeq                                clojure
   clojure.core$fn__6165$__GT_Vec__6193               -> clojure.core.Vec                                   clojure
   clojure.core$reify__6203                           -> clojure.core.ArrayManager                          clojure
   clojure.core$reify__6206                           -> clojure.core.ArrayManager                          clojure
   clojure.core$reify__6209                           -> clojure.core.ArrayManager                          clojure
   clojure.core$reify__6212                           -> clojure.core.ArrayManager                          clojure
   clojure.core$reify__6215                           -> clojure.core.ArrayManager                          clojure
   clojure.core$reify__6218                           -> clojure.core.ArrayManager                          clojure
   clojure.core$reify__6221                           -> clojure.core.ArrayManager                          clojure
   clojure.core$reify__6224                           -> clojure.core.ArrayManager                          clojure
   clojure.core$vector_of                             -> clojure.core.ArrayManager                          clojure
   clojure.core$vector_of                             -> clojure.core.Vec                                   clojure

-e正規表現も使えます。

$> jdeps -v -e '.*\$.*' clojure
clojure -> /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar
   clojure.asm.commons.SerialVersionUIDAdder          -> clojure.asm.commons.SerialVersionUIDAdder$Item     clojure
   clojure.asm.commons.SerialVersionUIDAdder$Item     -> clojure.asm.commons.SerialVersionUIDAdder$Item     clojure
   clojure.core$_                                     -> clojure.core$_                                     clojure
   clojure.core$_DOT__DOT_                            -> clojure.core$_DOT__DOT_                            clojure
   clojure.core$_EQ_                                  -> clojure.core$_EQ_                                  clojure
   clojure.core$_EQ__EQ_                              -> clojure.core$_EQ__EQ_                              clojure
   clojure.core$_EQ__EQ___inliner                     -> clojure.core$_EQ__EQ___inliner                     clojure
   clojure.core$_EQ___inliner                         -> clojure.core$_EQ___inliner                         clojure
   clojure.core$_GT_                                  -> clojure.core$_GT_                                  clojure
   clojure.core$_GT_0_QMARK_                          -> clojure.core$_GT_0_QMARK_                          clojure
   clojure.core$_GT_1_QMARK_                          -> clojure.core$_GT_1_QMARK_                          clojure
   clojure.core$_GT__EQ_                              -> clojure.core$_GT__EQ_                              clojure
   clojure.core$_GT__EQ___inliner                     -> clojure.core$_GT__EQ___inliner                     clojure
   clojure.core$_GT___inliner                         -> clojure.core$_GT___inliner                         clojure
   clojure.core$_LT_                                  -> clojure.core$_LT_                                  clojure
   clojure.core$_LT__EQ_                              -> clojure.core$_LT__EQ_                              clojure
   clojure.core$_LT__EQ___inliner                     -> clojure.core$_LT__EQ___inliner                     clojure

-profileで、JREをどれだけ切り詰められるか、プロファイルをしりたい人にも親切。

jdeps -profile clojure
clojure -> /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar (Full JRE)
   clojure (clojure)
      -> java.awt                                           Full JRE
      -> java.beans                                         Full JRE
      -> java.io                                            compact1
      -> java.lang                                          compact1
      -> java.lang.annotation                               compact1
      -> java.lang.reflect                                  compact1
      -> java.math                                          compact1
      -> java.net                                           compact1
      -> java.sql                                           compact2
      -> java.text                                          compact1
      -> java.util                                          compact1
      -> java.util.concurrent                               compact1
      -> java.util.regex                                    compact1
      -> javax.swing                                        Full JRE
      -> javax.swing.table                                  Full JRE
      -> javax.xml.parsers                                  compact2
      -> org.xml.sax                                        compact2
      -> sun.misc                                           JDK internal API (rt.jar)

-dotoutput 出力フォルダ のオプションで、Graphviz形式で依存性の有向グラフも出すことができます。

$> jdeps -dotoutput dot clojure
$> ls dot
clojure.dot summary.dot
$> dot -Tgif dot/clojure.dot > clojure.gif

f:id:knjname:20140603010217g:plain

SIerやらとの親和性

「○○というクラスを修正したいけど、どこに影響が及ぶか知りたい。」というケースはよくあります。

まあEclipseでCtrl-Shift-G(シンボルの参照の検索)を連打してもいいのですが、やっぱめんどいですね。

SonarQubeとかでもクラスの依存性の解析は出たりしますが、ちょっとめんどいですね。

そういう人はJenkinsでコンパイルのついでにjdepsの出力結果を常に出しておくと便利です。私はそうしています。

複数プロジェクトがくんずほぐれつしている場合でも引数にクラスパスをちゃんと指定しておけばちゃんと複数プロジェクト間の依存性を見られます。

変なモジュールの絡み方してるやつも一発で見つけられますね。

みなさんもぜひ活用しましょう。

まとめ

  • jdepsコマンドを使えばクラス(パッケージ)間の依存性をチェックできる。
  • 業務開発で出がちなモジュール間の依存性チェックに使える。

並列で書き込めないデータ構造へのアプローチの考察(はしていない)

Collectors#toMap(): 00000215
Write to ConcurrentHashMap simultaneously: 00001567
Write to HashMap but write tasks are serialized: 00001787
Write to HashMap but write tasks are serialized (with parallel stream): 00001075

たとえばPOIのワークシートにマルチスレッドに書き込むと、もれなくぶっ壊れるわけですが、そういうマルチスレッド非対応のものに書き込む時はシングルスレッドなエグゼキュータにタスクをぶん投げると問題を解決できることが多いです。

特にオチ無し!