Takuji->find;

株式会社はてなでアプリケーションエンジニアやってます、技術的な記事を書いているつもり

Atomの自動補完プラグイン「autocomplete-plus」のPerl用Providerを書いている話

これは はてなエンジニアアドベントカレンダー2016 23日目の記事です。

qiita.com

developer.hatenastaff.com

昨日は id:takuya-a さんの文字列アルゴリズムの学びかたでした。

こんにちは、はてなでアプリケーションエンジニアとしてWebサービスAndroidアプリ(たまにiOSアプリ)を開発しているid:takuji31です。

私は普段VimPerlを書いているのですが、最近AtomPerlを書きたくなってAtomの自動補完プラグインである「autocomplete-plus」のPerl用Providerを書いているので、今日はそのことについて話します。

先に謝罪しておきますと、本来はこのエントリーで公開しましたと告知する予定でしたが、まだ実用的なクオリティーには達していないので、開発中とお知らせするだけになります 🙇

github.com

Atomとは

Atomとは(恐らくこの記事を読むような方はご存知だとは思いますが)、Githubが公開しているオープンソーステキストエディターです。

atom.io

A hackable text editorとある通り、プラグインを書くことでかなり自由にHackすることができます。

autocomplete-plus

autocomplete-plusはAtomの自動補完プラグインです。

github.com

最近のバージョンのAtomに標準でバンドルされていて、インストールするだけで様々な言語のコード補完を行うことができます。

また、標準で用意されている言語以外にも、Providerを用意することで自分でコード補完の候補を作ることができます。

既に存在するProviderの一覧はGitHubのautocomplete-plusのWikiに一覧があります

github.com

見てみるとPerl用がないですね、ググってみた感じもないようでしたので、これは作るしかないと思いました。

Providerを作る

雛形を自動生成する

ProviderはAtomのPackageとして作る必要があります。

Atomでは開発用にPackageを生成してくれるメニューがあります、メニューの Packages -> PackageGenerator -> Generate Atom Package を実行しましょう。

コマンドパレットを開いて Package Generator: Generate Package を探して実行してもよいです。

f:id:takuji31:20161222183702p:plain

実行するとPackageの作成先を指定するダイアログが出ますので、適当な名前を決めて入力します。

決定すると指定したパスにサンプルコードと共にPackageが生成されます。

hatena-advent-calendar-2016/
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── keymaps
│   └── hatena-advent-calendar-2016.json
├── lib
│   ├── hatena-advent-calendar-2016-view.js
│   └── hatena-advent-calendar-2016.js
├── menus
│   └── hatena-advent-calendar-2016.json
├── package.json
├── spec
│   ├── hatena-advent-calendar-2016-spec.js
│   └── hatena-advent-calendar-2016-view-spec.js
└── styles
    └── hatena-advent-calendar-2016.less

以下のファイルとディレクトリはProviderには不要なので消します。

  • keymaps
    • このPackageのキーマップを定義するファイル
  • menus
    • メニューのエントリーを定義するファイル
  • styles
    • Packageのスタイルを定義するファイル
  • lib/*.js
    • サンプルコードですが(Providerでは)一切使わないので消します。

Packageに必要な設定

不要なファイルを整理したら、 package.json の一番上の階層に以下の設定を追加します。

{
  "providedServices": {
    "autocomplete.provider": {
      "versions": {
        "2.0.0": "provide"
      }
    }
  }
}

provide の部分はJS側で定義するメソッド名なので、自由に決めて構いません。

Packageのメインオブジェクトを作る

lib 以下のPackage名と同じjsファイルからexportしたオブジェクトがプラグイン機構の本体になります。

JavaScript以外にもCoffeeScriptも直接使うことができますが、私は業務でTypeScriptを使っていることもあり、TypeScriptで書いています。

/// <reference path="../typings/bundle.d.ts" />

import {Config} from "./config";
import {Provider} from "./provider";
import {UseAndRequireCompletionProvider} from "./use-and-require-completion-provider";

class AutocompletPerlProvider {
  providers: Provider[] = null
  config =  Config.config
  activate() {

  }
  deactivate() {
    this.providers = null
  }
  provide() {
    if (this.providers === null) {
      this.providers = [new UseAndRequireCompletionProvider()];
    }
    return this.providers
  }

}
export = new AutocompletPerlProvider()

このように provide メソッドの中で実際の補完候補を提供するProviderのインスタンスを作って返してやります。Providerは複数指定可能です。

設定項目を追加

メインのオブジェクトに config フィールドを追加することで、そこに定義されている設定項目がAtomの設定画面に自動的に追加されます。

先ほどのコードでは config フィールドの中身は Config.config を参照しているので、その中身を見てみましょう。

class Config {
  static config = {
    perlPath: {
      type: 'string',
      default: 'perl'
    },
    cartonPath: {
      type: 'string',
      default: 'carton'
    },
    useCarton: {
      type: 'boolean',
      description: 'Use carton when building completion suggestions. only effects when cpanfile and carton executable exists.',
      default: false,
    }
  }
  static get perlPath():String {
    return atom.config.get('autocomplete-perl.perlPath');
  }
  static get cartonPath():String {
    return atom.config.get('autocomplete-perl.cartonPath');
  }
  static get useCarton():boolean {
    return atom.config.get('autocomplete-perl.useCarton');
  }
}
export {
  Config
}

上記のコードでは3つの設定項目が指定されています。

このような定義を作成することで、Atomの設定画面にあるPackageの設定項目が自動的に生成されて以下のようになります。

f:id:takuji31:20161223090629p:plain

このコードでは設定の定義以外にも、設定値を取り出すのに便利なプロパティーを定義しています。このようにしておくと、実際のコード側で簡単に取り出せて便利でしょう。

Providerを作る

Providerは決まったメソッドとフィールドを持ったクラスである必要があります。

その仕様については、autocomplete-plusのWikiにまとめられています。

Provider API · atom/autocomplete-plus Wiki · GitHub

最低限 selector フィールドと getSuggestions メソッドがあればProviderとしては機能します。

/// <reference path="../typings/bundle.d.ts" />

import {Provider, SuggestionInfo} from "./provider";
import {ISuggestion} from "./suggestion";
import {Range, Point} from "atom";

class UseAndRequireCompletionProvider extends Provider {
  selector : string = ".source.perl"
  getSuggestions(info : SuggestionInfo) : Promise<ISuggestion> {
    return new Promise((resolve) => {
        var suggestions =  // ここで補完候補を生成する
        resolve(suggestions)
    });
  }
}
export {
  UseAndRequireCompletionProvider
}

atom-autocomplete-perl では型でこの辺りの制約をはっきりさせておきたかったので、ベースのクラスやインターフェイスを別ファイルで定義しました。

provider.ts

/// <reference path="../typings/bundle.d.ts" />
import {ISuggestion} from './suggestion';

interface SuggestionInfo {
  editor: AtomCore.IEditor;
  bufferPosition: TextBuffer.IPoint;
  scopeDescriptor: AtomCore.ScopeDescriptor;
  prefix: string;
  activatedManually: boolean;
}

abstract class Provider {
  selector : string = ".source.perl"
  abstract getSuggestions(info : SuggestionInfo) : Promise<ISuggestion[]>
}
export {
  Provider,
  SuggestionInfo
 }

suggestion.ts

/// <reference path="../typings/bundle.d.ts" />

type SuggestionType = 'variable'|'constant'|'property'|'value'|'method'|'function'|'class'|'type'|'keyword'|'tag'|'snippet'|'import'|'require';

interface ISuggestion {
  displayText?: string
  replacementPrefix?: string
  type?: SuggestionType;
  leftLabel?: string
  leftLabelHTML?: string
  rightLabel?: string
  rightLabelHTML?: string
  className?: string
  iconHTML?: string
  description?: string
  descriptionMoreURL?: string
}

class TextSuggestion implements ISuggestion {
  constructor(public snippet: string, public type: SuggestionType) {
  }
}
class SnippetSuggestion implements ISuggestion {
  constructor(public text: string) {

  }
}

export {
  ISuggestion,
  TextSuggestion,
  SnippetSuggestion,
  SuggestionType
}

この定義のおかげで、簡単にProviderを増やすことができますね。

あとはひたすら getSuggestions に渡ってくる入力の情報から非同期に補完候補を生成してresolveするだけです。

とは言うものの、今の時点でまだこれは使っていません、なぜなら簡単なキーワードだけなら自動的に補完候補を生成してくれる便利APIがあるのです。

SymbolProvider Config API

簡単なキーワードなら、SymbolProvider Config API を使うことで補完候補を生成できます。

SymbolProvider Config API · atom/autocomplete-plus Wiki · GitHub

たとえばPerlのビルトイン関数は settings/language-perl.cson に以下のように書くだけです。

'.source.perl':
  autocomplete:
    symbols:
      builtin:
        suggestions: [
          'abs'
          'accept'
          'alarm'
          'atan2'
          'bind'
          'binmode'
          'bless'
          // ...
          'waitpid'
          'wantarray'
          'warn'
          'write'
          'y'
        ]

これだけであとは勝手に補完できるようになります。

f:id:takuji31:20161223093650p:plain

ビルトイン関数は最初Providerを作って補完できるようにしていましたが、こちらの設定に変えました。

atom-autocomplete-perl の今後について

このProviderは、今のところビルトイン関数の補完だけ動きます。

今後以下のような機能の提供を予定しています。

  • package名補完
  • クラスメソッド補完
  • (構想段階ですが) Smart::ArgsData::Validator でバリデーションした変数の型を雑に推測してそれっぽい補完候補を返す

ひとまずpackage名補完だけ追加したら、全国のPerl Mongerの皆様が利用できる状態にしたいなぁと思っています。

まとめ

AtomのPackageは簡単に作ることができました。node.jsを使うことができますので、npmにある大量の資産を活かしたり、他の言語のコードをシステム経由で実行することもできます。

あなたもこの機会にAtomをHackしてみませんか?

はてなではJavaScriptやAltJSが好きなエンジニアを募集しています!

hatenacorp.jp

明日の担当は id:tarao です、お楽しみに!

git merge --abort

gitでmergeした時にconflictして、やっぱやめたってなる時に今までは git reset --hard HEAD ってしてたんだけど、ふとmanを眺めたら --abort オプションがあるのを発見した。

rebase とか cherry-pick にあるのになんでないのって思ってたけど、どうも勘違いだったっぽい。

gitちゃんと使うならもっとオプション知らないといけないなぁと思った。

TODOコメントにGitのbranch名を入れたい続編

以前TODOコメントにGitのbranch名を入れるためにSnippet書いたという記事を書いた。

blog.takuji31.jp

この記事以降もチームは変わってないし使っているのだが、branch名そのままだとそのbranchのmergeまでに終わらせないといけないTODOに思えるという指摘があった。

たしかにそうだなって感じだったので、branch名のルールを少し見直して feature-name/part みたいな感じにすることにした。

それに合わせてsnippetのコードも少し改変。

snippet     todo
options     head
    # TODO(`substitute(substitute(substitute(system('git rev-parse --abbrev-ref HEAD'), '\n\+$', '', ''), '^\(feature|hotfix|release|design\)\/', '', ''), '\/.\+$', '', '')`): ${0}

git-flow で使いそうなprefixとか design/ とか を取っ払ってやることでとにかくそれっぽい名前を出すことに成功した。

最近PerlAtomで書けるように準備しているので、このsnippetもそのうち使わなくなりそうだけども、とりあえず書き残しておく。

Kotlinの演算子オーバーロードについて

これは Kotlin Advent Calendar 2016 9日目の記事です。

qiita.com

今日はKotlinの演算子オーバーロードについて紹介します。

演算子オーバーロードとは

演算を行うための記号が演算子です、だいたいのブログラミング言語にはありますよね?

+ - / * =&& || など、プログラミング言語にはたくさんの演算子があります。

Kotlinでは様々なクラスを演算子で演算した時の挙動をコントロールする演算子オーバーロードの機能があります。

これはJavaにはない機能ですね。

Kotlinの演算子オーバーロードの仕組み

Kotlinで演算子により演算を行う場合、左辺のクラスに存在する演算子に対応した名前で右辺の型にマッチするメソッドが存在した場合に行われます。

1 + 2 // 3

+plus メソッドを呼ぶので以下と同じ意味になります。

1.plus(2)

実際にKotlinで数値リテラルのメソッドを呼ぼうとすると、補完候補plus や他の演算子に対応したメソッドが出てきます。

ボウリングで学ぶ演算子オーバーロード

決まった名前でメソッドを作るだけです。これは通常のメソッド以外に拡張関数でも可能です。

私はボウリングが趣味なので、ボウリングのスコアを表現するクラスを作ります。

ボウリングは雑に説明すると、10本のピンに向かってボールを転がしていっぱい倒したらハッピーというスポーツです。

10本のピンは2回以内に倒しきらないといけなくて、1投目で倒しきったらストライク、2投目で倒しきったらスペア、倒しきれなかったらミス(エラー)となります。

ストライク、スペア、ミスのどれかで投げ終わる1つの流れをフレームといい、これを9回繰り返します。

9回繰り返した次の10フレーム目は最大3投でき、3投するかミスをした場合に終わります。

この10フレームが1ゲームになります。

クラスを定義

まずはスコアそのものを表現するクラスを作りましょう。

    object Strike : BaseScore(value = 10)
    object Spare : BaseScore(value = 0)
    class Score(value: Int) : BaseScore(value = value)
// ↓複雑になるので一旦は考慮しないことにします
//    object Foul : Score(value = 0)
//    object Miss : BaseScore(value = 0)
//    object Gutter : BaseScore(value = 0)
//    class Split(value: Int) : Score(value = value)
}

スペアは10-(1投目の投数)ですが、便宜的にオブジェクトで表現します。

次にフレームを作ります、9フレーム目までのフレームは1〜2つのスコアからなります

複雑度を下げるために、フレームは完了しないと計算できないようにします。

sealed class Frame(val scores: List<BaseScore>) {
    object StrikeFrame : Frame(listOf(Strike))
    class SpareFrame(firstScore: BaseScore) : Frame(listOf(firstScore, Spare))
    class MissFrame(firstScore: BaseScore, secondScore: BaseScore) : Frame(listOf(firstScore, secondScore))
}
class TenFrame private constructor(val scores:List<BaseScore>) {
    constructor(firstStrike: Strike, secondStrike: Strike, thirdScore: BaseScore) : this(listOf(firstStrike, secondStrike, thirdScore))
    constructor(strike: Strike, secondScore: Score, thirdScore: BaseScore) : this(listOf(strike, secondScore, thirdScore))
    constructor(firstScore: BaseScore, spare: Spare, thirdScore: BaseScore) : this(listOf(firstScore, spare, thirdScore))
    constructor(firstScore: BaseScore, missScore: Score) : this(listOf(firstScore, missScore))
}

10フレーム目だけ途中で投げ終わる可能性があるので、起こり得る全パターン(ただし3投目は何でもいいので固定)をコンストラクターで用意しました。

フレームが集まればゲームになります、ゲームを表現するクラスを作りましょう。

sealed class Game(val frames: List<Frame>) {
    class IncompletedGame(frames: List<Frame>) : Game(frames)
    class CompletedGame(frames: List<Frame>, tenFrame: TenFrame) : Game(frames)
}

本当は初期家事にフレーム数のバリデーションが必要ですが、今回は雑にこういう感じでいきます。

スコア + スコア = フレーム

さて、これを演算できるように実装していきましょう。

Score(1) + Score(8)  // ミス
Score(1) + Spare // スペア

まずは↑のパターンを作ります

open class Score(value: Int) : BaseScore(value = value) {
    open operator fun plus(b: BaseScore): MissFrame {
        return if (this.value + b.value < 10) {
            MissFrame(this, b)
        } else {
            error("Invalid score combination!")
        }
    }
    operator fun plus(spare: Spare): SpareFrame {
        return SpareFrame(this)
    }
}

この組み合わせで1つのフレームができあがります。

ストライク + スコア = フレーム2つ (完了していないゲーム)

ストライクだけは1つでフレームが終わってしまうので、ちょっと特殊です

Strike + Score(3) // ストライクと3ピン倒した状態、今回これは考慮しない
Strike + Strike // ダブル
Strike + (Score(3) + Spare) // ストライクとスペア

これは Strike オブジェクトに演算子オーバーロードをしましょう。

object Strike : BaseScore(value = 10) {
    operator fun plus(strike: Strike): IncompletedGame {
        return IncompletedGame(listOf(StrikeFrame, StrikeFrame))
    }
    operator fun plus(frame: Frame) : IncompletedGame {
        return IncompletedGame(listOf(StrikeFrame, frame))
    }
}

これだけで大丈夫です、Strikeは1つでフレームになるので、演算時にStrikeFrameに暗黙的に変換しているような扱いにしています。

フレーム + フレーム = 完了していないゲーム

フレームが2つ以上集まると完了していないゲームになりますね。ストライク単体もフレームと同じ扱いです。

sealed class Frame(val scores: List<BaseScore>) {
    operator fun plus(b: Frame): IncompletedGame {
        return IncompletedGame(listOf(this, b))
    }
    operator fun plus(strike: Strike): IncompletedGame {
        return IncompletedGame(listOf(this, StrikeFrame))
    }
}

単純ですね。

完了していないゲーム + フレーム = 完了していないゲーム

完了していないゲームにどんどんフレームを足していきます、最終的には9つまで許容します。

class IncompletedGame(frames: List<Frame>) : Game(frames) {
    operator fun plus(b: Frame): IncompletedGame {
        return if (this.frames.size == 9) {
            error("Next frame is TenFrame!")
        } else {
            IncompletedGame(frames + b)
        }
    }
    operator fun plus(strike: Strike): IncompletedGame {
        return this + StrikeFrame
    }
}

10個目を足そうとするとエラーになります。

完了していないゲーム + テンフレーム = 完了したゲーム

ここまでくるとほぼ完成ですね、10フレーム目を足しましょう。

class IncompletedGame(frames: List<Frame>) : Game(frames) {
    operator fun plus(b: TenFrame): CompletedGame {
        return if (this.frames.size != 9) {
            error("Next frame is not TenFrame!")
        } else {
            CompletedGame(frames, b)
        }
    }
}

使ってみる

ここまできたら、1ゲームを表現する演算ができるようになるはずです。

実際に先日投げたこの3ゲームをKotlinで表現してみましょう。

1ゲーム目

Strike + Strike + (Score(9) + Score(0)) + (Score(7) + Spare) + Strike + (Score(9) + Spare) + (Score(7) + Score(1)) + Strike + Strike + TenFrame(Strike, Score(8))

2ゲーム目

(Score(7) + Spare) + Strike + (Score(9) + Spare) + Strike + (Score(9) + Spare) + (Score(9) + Score(0)) + (Score(9) + Spare) + (Score(9) + Spare) + (Score(8) + Spare) + TenFrame(Score(9), Spare, Score(7))

3ゲーム目

Strike + (Score(9) + Spare) + Strike + (Score(7) + Score(0)) + (Score(9) + Score(0)) + (Score(8) + Score(0)) + Strike + Strike + (Score(7) + Score(1)) + TenFrame(Strike, Score(9), Spare)

全部エラーなしに書けました!

実際のところ、入力のバリデーションが必要だったりするので、これで完成ではないですが、演算子オーバーロードとしてはこれで完成かなと思います。

まとめ

いかがでしたでしょうか、当たり前ですが右辺の型で戻す型を変えたりなどできるので、結構柔軟な計算ができるようになります。

これ以外にも、中置関数を使うと組み込み演算子以外のカスタム演算子として使うことができるようになります。

詳しくは公式ドキュメントをご覧ください。

kotlinlang.org

最後に

計算まで終わらなかったのでそのうち実装して公開します。

以下が今回のコードの全容です。

sealed class BaseScore constructor(val value: Int) {
    object Strike : BaseScore(value = 10) {
        operator fun plus(strike: Strike): IncompletedGame {
            return IncompletedGame(listOf(StrikeFrame, StrikeFrame))
        }
        operator fun plus(frame: Frame) : IncompletedGame {
            return IncompletedGame(listOf(StrikeFrame, frame))
        }
    }

    object Spare : BaseScore(value = 0)

    class Score(value: Int) : BaseScore(value = value) {
        operator fun plus(b: Score): MissFrame {
            return if (this.value + b.value < 10) {
                MissFrame(this, b)
            } else {
                error("Invalid score combination!")
            }
        }

        operator fun plus(spare: Spare): SpareFrame {
            return SpareFrame(this)
        }
    }
//    object Foul : Score(value = 0)
//    object Miss : BaseScore(value = 0)
//    object Gutter : BaseScore(value = 0)
//    class Split(value: Int) : Score(value = value)
}

sealed class Frame(val scores: List<BaseScore>) {

    operator fun plus(b: Frame): IncompletedGame {
        return IncompletedGame(listOf(this, b))
    }

    operator fun plus(strike: Strike): IncompletedGame {
        return IncompletedGame(listOf(this, StrikeFrame))
    }

    object StrikeFrame : Frame(listOf(Strike))
    class SpareFrame(firstScore: BaseScore) : Frame(listOf(firstScore, Spare))
    class MissFrame(firstScore: BaseScore, secondScore: BaseScore) : Frame(listOf(firstScore, secondScore))
}
class TenFrame private constructor(val scores:List<BaseScore>) {
    constructor(firstStrike: Strike, secondStrike: Strike, thirdScore: BaseScore) : this(listOf(firstStrike, secondStrike, thirdScore))
    constructor(strike: Strike, secondScore: Score, thirdScore: BaseScore) : this(listOf(strike, secondScore, thirdScore))
    constructor(firstScore: BaseScore, spare: Spare, thirdScore: BaseScore) : this(listOf(firstScore, spare, thirdScore))
    constructor(firstScore: BaseScore, missScore: Score) : this(listOf(firstScore, missScore))
}

sealed class Game(val frames: List<Frame>) {
    class IncompletedGame(frames: List<Frame>) : Game(frames) {
        operator fun plus(b: Frame): IncompletedGame {
            return if (this.frames.size == 9) {
                error("Next frame is TenFrame!")
            } else {
                IncompletedGame(frames + b)
            }
        }
        operator fun plus(b: TenFrame): CompletedGame {
            return if (this.frames.size != 9) {
                error("Next frame is not TenFrame!")
            } else {
                CompletedGame(frames, b)
            }
        }
        operator fun plus(strike: Strike): IncompletedGame {
            return this + StrikeFrame
        }
    }
    class CompletedGame(frames: List<Frame>, val tenFrame: TenFrame) : Game(frames)
}

class Main {
    companion object {
        fun main(args: Array<String>) {
            Score(1) + Score(8)  // ミス
            Score(0) + Score(10) // スペア
            Score(2) + Score(8) // スペア

            Strike + Strike + (Score(9) + Score(0)) + (Score(7) + Spare) + Strike + (Score(9) + Spare) + (Score(7) + Score(1)) + Strike + Strike + TenFrame(Strike, Score(8))
            (Score(7) + Spare) + Strike + (Score(9) + Spare) + Strike + (Score(9) + Spare) + (Score(9) + Score(0)) + (Score(9) + Spare) + (Score(9) + Spare) + (Score(8) + Spare) + TenFrame(Score(9), Spare, Score(7))
            Strike + (Score(9) + Spare) + Strike + (Score(7) + Score(0)) + (Score(9) + Score(0)) + (Score(8) + Score(0)) + Strike + Strike + (Score(7) + Score(1)) + TenFrame(Strike, Score(9), Spare)
        }
    }
}

転職に対して思っていること色々

これは 転職(その2) Advent Calendar 2016 2日目の記事です。

こんにちは、株式会社はてなでアプリケーションエンジニアをやっているid:takuji31です。

最初はそろそろ転職して1年になるのでその辺の話を書こうかと思ったんですが、ちょっと思うところがあって内容を変えました、理由は以下の通りです。

はっきりこれといったテーマがない感じなので雑多にメモっぽいこと書きます。

自分の価値観なので、それは違うよってことあるかもしれませんし、行き詰まってる人の参考になるかもしれませんし、他の方の考え方を否定するものではありませんのであしからずご了承ください。

経歴的なもの

1987年3月1日生まれの29歳、大阪市生まれ大阪市育ちの大阪市民で2010年に4年制の専門学校を1年休んで5年で卒業して、東京のモバイル系のベンチャーに新卒入社。

1年半ほどソシャゲの開発と運用をやって、半年ほどスマートフォンの着メロサイトの開発と運用をやっていた。

同時に入社してすぐの頃からAndroidアプリの開発も並行してやっていて、その年の夏に(仕事では)自分にとっても自社にとっても初めてのAndroidアプリをリリースした。

ちょうど2年経った頃、元同僚の誘いで某アパレル系大企業の子会社に入社、日本でトップ3以内にいる某ファッションECサイトスマートフォンアプリを作ったりなどしていた、iOS開発をやり始めたのはこのタイミング。

色々あって1年ほどで大阪の実家に帰ることになって転職、前職に入社。スマートフォンアプリの受託開発を経験。

半年で辞めようと思ったけどそろそろ5年くらいはどこか1社で頑張った経歴が欲しいと思いつつも2年半ほどで身体の方にガタがきて転職活動を始めて、以前からとある方にお誘いいただいていた縁もあって、今年の1月からはてなに入社した。

転職理由を振り返ってみる

振り返ってみるとポジティブともネガティブな理由ともとれそうな感じの転職の仕方をしてきていて、実際そうだと思う。

会社への不満とか自分がここにいて本当に成長できるのかとか、目指しているものが違うとかそういう思いがありつつ、一方でステップアップしたいみたいなことは常に思っているし、すごい人達と仕事したいからそういう人達いるところを探したいみたいなのもある。

はてなに入ってみて11ヶ月経って思うけど、やっぱりすごい人達の集まりって感じだし、YAPCとかで登壇してて何となく雲の上とは言わなくても自分とは全然違う世界にいそうだと思っていた人達と今仕事をしているのがとても良い。

何をもって転職失敗とするのか

※たぶんフィクションです

  • 年収が2/3くらいになる
  • 残業時間激増する
  • 仕事が無駄にキツくなる
  • 入った会社のサービスに興味がない
  • その他労働環境の悪化
  • やりたかったことと違うことやってる
  • 年齢層が自分と合ってない(めっちゃ年寄りばかりとか、若すぎるとか)
  • 会社に通いづらい

実際はてなに入った時も「通勤時間長くて朝つらい(京阪経由で1時間40分、JRでも1時間20分ほど)」問題があったが、今は京都に引っ越して解決している。

この辺は独身だと解決しやすい問題という気がしているが、共働きとか子持ちだと大変そう。

転職失敗しそうな要素

全部自分の話

  • 次の会社より辞める日が先に決まっている
  • 辞める理由がはっきりしていない、とにかくつらいとかそういう気持ちになってる時
  • 他人(主にその時所属している会社の人事とか)に言われて転職を決めた時
  • Uターンで選択肢が少ない(分からない)時
  • ちゃんと色んな選択肢を見ずに言われたまま決めた時

転職を成功させる確率が上がるかもしれない要素

自分の話かもしれないし、想像かもしれない

  • 自分自身のスキルを常に磨く
  • 業界のトレンドを追う
  • 他社の人とつながっておく
  • 同僚にも自分の実力を分かってもらっておく
  • コネクションがないなら転職エージェントにも相談する

年収

多くの人が転職の理由の一つに加える要素の一つだと思う。

一応自分は2回目の転職以外は基本的に上がってるし、残業代やボーナス入った実績値なら一度も下がってないんだけど、オファー時の提示額が下がるの結構モチベに影響ある気がしている。

少なくとも同じような職種に転職する時は上がっておきたい。

その他転職に対して思うこと雑多に

  • 給与交渉で弱気になって下げるとだいたいロクなことない
  • 転職回数、あまり気にしない方がよいのではないか、回数気にして落とされるなら縁がなかったと思えばよさそう
  • 残業代実際見込みじゃないけど見込みの計算で提示される年収、実際に計算するとめちゃくちゃ安くなって面白い
  • プログラマーの給料、関東と関西で月額5万円くらいのギャップがあるらしいがソースは忘れた
  • 残業ほとんど0に近かった人間が残業時間40時間くらいの会社に行くと周りとの感覚のギャップに悩まされるのでオススメできない
  • 結局は縁だと思うので、いいと思ってた会社落ちたからといって自分はゴミクズなのでどこの会社からも必要とされていないとか思わない方がよさそう
  • 縁だけでなくタイミングもあるので、一度落ちた(と思う)会社を再度受けて入社できることはある、実は前回の転職の時にはてな受けて書類で落ちていた(エージェントから連絡が来なかったので実際はどうか曖昧だけど)
    • 一方で前回書類で落ちた別の会社にもう1回書類送ったけどまた落ちた、冷静に考えて経験と一致してないしまぁ普通にあるよねって感じ

まとめ

準備とかちゃんとして転職成功しようぜ!

RecyclerView.ItemDecorationについて #関モバ

こんばんは、最近Androidエンジニアを名乗っていいのか悩ましいid:takuji31です。

今日はRecyclerViewのItemDecorationについて簡単な使い方をまとめてみました。

なお、この記事の一部は本日開催の関西モバイルアプリ研究会#20で発表された内容です。

RecyclerView.ItemDecoration とは

RecyclerViewの各アイテムに対して何らかの装飾を施す仕組みです。

代表的なのは罫線を引いてくれる(Support Library25.0.0でようやく追加された)DividerItemDecorationやアイテムのDnDやスワイプが行えるようになるめっちゃ便利なItemTouchHelperがあります(というより公式で提供されている子クラスはこの2つだけ)。

色んなRecyclerViewのアイテムに対して簡単な装飾を施したいのなら、レイアウトで頑張るよりこちらでやる方がよいかと思います。

メソッド

getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)

指定したViewが本来レイアウトした位置からどれくらいズレるのかを指定するメソッドです。

最後の state がないメソッドがありますが、これは非推奨です。

view が対象のView、 outRect がアイテムのオフセットを返すための Rect です、 outRectleft|top|right|bottom に直接値をセットします。

onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)

装飾の描画時に呼ばれるメソッドです。呼ばれるタイミングは各アイテムの内容が描画される前です。

最後の state がないメソッドがありますが、これは非推奨です。

c はRecyclerView全体のCanvasです、各要素に対して呼ばれるのではなく、描画ごとに1回だけ呼ばれます。

このメソッドで装飾を描画することになります。

onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)

基本的には onDraw と同じですが、こちらは呼び出されるタイミングがアイテムの内容が描画された後です。

最後の state がないメソッドがありますが、これは非推奨です。

アイテムに重ねて描画したい場合に使うとよさそうです。

使い方

基本的には ItemDecoration を継承したクラスを作って、getItemOffsetonDraw もしくは onDrawOver (あるいは両方)を実装してやるとよいです。

使う時はRecyclerView.addItemDecoration(RecyclerView.ItemDecoration decor) を呼んで装飾を追加します。

今回はiOSUITableViewCell にある accessoryType のようなものを作ってみました。

AccessoryTypeを定義する

雑にenumを定義しました、とりあえず2タイプだけサポート。

enum class AccessoryType(@DrawableRes val resId: Int) {
    NONE(0),
    DISCLOSURE_INDICATOR(R.drawable.ic_chevron_right_black_24dp),
    CHECK_MARK(R.drawable.ic_done_black_24dp),
}

Adapterを作る

文字列と AccessoryType を持つ Pair のリストを持つだけの Adapter を作りました。

ポイントは ViewHolder.itemViewsetTagAccessoryType を渡していること。

ViewRecyclerView から ViewHolder を取り出すことはできますが、これだと ViewHolder の型に ItemDecoration が依存してしまうので、Tagで渡しています。

class Adapter(var items: List<Pair<String, AccessoryType>>) : RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflator = LayoutInflater.from(parent.context)
        return ViewHolder(view = inflator.inflate(R.layout.recyler_row_simple_textview, parent, false))
    }

    override fun getItemCount(): Int {
        return items.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val (text, accessoryType) = items[position]
        holder.textView.text = text
        holder.textView.setOnClickListener {
            Log.d(javaClass.simpleName, "clicked")
        }
        holder.itemView.setTag(R.id.tagAccessoryType, accessoryType)
    }
}

ItemDecorationを作る

今回は AccessoryType.NONE 以外の時に右側にいい感じに装飾を表示することにしました。

ItemDecoration 全体のコードは↓の通り

class AccessoryItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() {
    val horizontalMargin by lazy {
        context.resources.getDimensionPixelOffset(R.dimen.accessory_horizontal_margin)
    }

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val accessoryType = view.getTag(R.id.tagAccessoryType) as? AccessoryType
        if (accessoryType != null) {
            if (accessoryType != AccessoryType.NONE) {
                val drawable = ContextCompat.getDrawable(view.context, accessoryType.resId)
                outRect.right = drawable.intrinsicWidth + horizontalMargin  * 2
            }
        }
    }

    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {

        val childCount = parent.childCount
        for (i in 0..childCount - 1) {
            val child = parent.getChildAt(i)
            val accessoryType = child.getTag(R.id.tagAccessoryType) as? AccessoryType
            if (accessoryType != null && accessoryType != AccessoryType.NONE) {
                val drawable = ContextCompat.getDrawable(child.context, accessoryType.resId)
                val bounds = Rect()
                parent.getDecoratedBoundsWithMargins(child, bounds)
                val left = bounds.right - horizontalMargin - drawable.intrinsicWidth
                val right = left + drawable.intrinsicWidth
                val top = bounds.top + ((bounds.bottom - bounds.top - drawable.intrinsicHeight) / 2) + Math.round(ViewCompat.getTranslationY(child))
                val bottom = top + drawable.intrinsicHeight
                drawable.setBounds(left, top, right, bottom)
                drawable.draw(c)
            }
        }
    }
}

getItemOffsets

ViewHolder でセットした AccessoryType のTagを取得して、修飾に使うDrawableとmargin分だけ右側にoffsetを設定しています。

override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
    val accessoryType = view.getTag(R.id.tagAccessoryType) as? AccessoryType
    if (accessoryType != null) {
        if (accessoryType != AccessoryType.NONE) {
            val drawable = ContextCompat.getDrawable(view.context, accessoryType.resId)
            outRect.right = drawable.intrinsicWidth + horizontalMargin  * 2
        }
    }
}

onDrawOver

子の View を1つずつ取得して、それぞれ必要に応じて Drawable を描画しています。

parent.getDecoratedBoundsWithMargins(child, bounds) でそのViewの装飾込みの境界を表すRectが取得できます。

ViewCompat.getTranslationY(child) でViewのtranslationYが取得できるので、この分描画位置をずらす必要があります。

あとは DrawableCanvas に描画するだけです。

override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {

    val childCount = parent.childCount
    for (i in 0..childCount - 1) {
        val child = parent.getChildAt(i)
        val accessoryType = child.getTag(R.id.tagAccessoryType) as? AccessoryType
        if (accessoryType != null && accessoryType != AccessoryType.NONE) {
            val drawable = ContextCompat.getDrawable(child.context, accessoryType.resId)
            val bounds = Rect()
            parent.getDecoratedBoundsWithMargins(child, bounds)
            val left = bounds.right - horizontalMargin - drawable.intrinsicWidth
            val right = left + drawable.intrinsicWidth
            val top = bounds.top + ((bounds.bottom - bounds.top - drawable.intrinsicHeight) / 2) + Math.round(ViewCompat.getTranslationY(child))
            val bottom = top + drawable.intrinsicHeight
            drawable.setBounds(left, top, right, bottom)
            drawable.draw(c)
        }
    }
}

スクリーンショット

f:id:takuji31:20161128184829p:plain

このコードの問題点

  • Offsetを指定してる分選択した時の背景描画が小さくなる
  • もちろんdrawしているだけなので、その装飾をタップできたりはしない

今回は分かりやすい例を出すためにこんな感じにしましたが、こういうのはViewに直接レイアウトした方がいいかもしれません。

まとめ

  • getItemOffsets でoffsetを決める
  • onDrawonDrawOver で描画する
  • RecyclerView.addItemDecoration() して使う

今回のサンプルコードはこちら

github.com

Kotlin1.1の新機能についてざっと眺めてみた(M1-M3対応) #kansaikt

こんにちは、Kotlinエバンジェリストになりたいid:takuji31です。

先日EAPでM3が公開されたKotlin1.1の新機能について、あまりちゃんと把握していなかったのでM1から見直してみました。

なお、この内容の一部は Kansai.kt #2で発表されました。

各タイトルは公式ブログから

新機能

Coroutines

他の言語にある async/await とか generator/yield ですね。

非同期や反復処理をモダンな感じに書けるようになります。

Kotlin 1.1最大の新機能とも言えます。

使い方

coroutineは今はKotlin本体と分離されていて、別の依存を追加することで使えます。

compile 'org.jetbrains.kotlinx:kotlinx-coroutines-generate:0.1-alpha-2'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-async:0.1-alpha-2'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-rx:0.1-alpha-2'

それぞれ coroutines-asyncasync/awaitcoroutines-generategenerator/yieldcoroutines-rx が RxJavaのObservableをawaitできるライブラリーです。

async/await は誤解を恐れない書き方をすると、非同期な処理を実行したいメソッド内で async 関数を呼び、その中の非同期にやりたい処理をawait()で囲ってやります。 Observable の場合は async/await ではなく asyncRx/await(First|Last|Single) を使います。 await の引数は CompletableFuture なのでAndroidだと7.0以降(API24)、Javaだと8以降でしか使えません、残念。

めっちゃ雑なサンプル

fun asyncOperation(i: Int): CompletableFuture<Int> = CompletableFuture.supplyAsync {
    Thread.sleep(1000)
    i
}
val future = async<Int> {
    val i = await(asyncOperation(1))
    i
}
Log.d("MainActivity", "i:" + future.get())

雑に使ってみた感想だと、CompletableFutureをいい感じに使う機能でFutureあまり使わない勢からするとさほど利点はなさそうというイメージ、他言語のものほどはまだ洗練されてなさそうでした、まぁalpha2だしこんなもんでしょう。

Type aliases

型に別名を付ける機能です。 今まで(String, Int) -> Unitみたいな感じで書いていて面倒なのでわざわざinterface作っていたりしたコールバックとかをいい感じに名前つけられて便利。

typealias VeryUsefulAction = (String, Int) -> Unit

個人的に1.1の今のところ出ている新機能で一番ありがたいです。

Local delegated properties & Inline properties

関数とかメソッド内で使えるプロパティーらしい、関数内での初期化を簡単に書けたりするようになりそう?

いまいち使いどころがよく分からなかった。

Scripting

Kotlinでスクリプト書けるようになったらしいです、GradleがKotlinサポートを表明したのは記憶に新しいですが、これもその機能の1つのようです。

Generic Enum values access

Genericsを使っていい感じにEnumの値にアクセスできたりするらしいです、型引数と組み合わせると便利な処理が書けるようになったりしそう。 inline なのでコンパイル時にインライン展開してくれます。

inline fun <reified T : Enum<T>> enumValues(): Array<T>
inline fun <reified T : Enum<T>> enumValueOf(name: String): T

新たな文法

Bound callable references

いわゆるmethod referenceみたいなやつですね、今までは Class.method をサポートしていたけど、新たに variable.method もサポートするとのことです。

Destructuring in Lambdas (M2)

合成値(data classとかPairみたいにvariable.component1()...componentN()で分解できるやつ)の分解を、ラムダ式でもいい感じにやってくれるようになりました。

listOf(Pair(1, 2), Pair(2,3)).map{
    (a, b) -> a + b // 3, 5
};

今までは面倒だけど変数定義し直していたりしたので、簡単に書けるようになって便利ですね。

Underscore for unused symbols (M3)

使わない戻り値を_で無視することのできる機能

ラムダ式の引数を無視したり、合成値の分解をした時に途中の値だけ捨てるのに便利ですね。

data class User(val id: Long, val name: String)
// ...
val (_, name) = user;

Underscore in numeric literals (M3)

数値リテラルの途中に_を挟めるようになりました。10000000 -> 10_000_000 みたいな感じ。

Perlで見たことあるやつって感じで桁の多い数値を表現する時に便利ですね。

その他変更

Relaxed rules for sealed classes and data classes

sealed classやdata classの定義が柔軟になったとのこと。

data class は非data classを継承できるようになっています。 もちろんopenである必要があるし、プロパティーもopenじゃないと継承できません。

open class User(open val id: Long, open val name: String)

data class AdminUser(override val id:Long, override val name: String, val role: String) : User(id = id, name = name)

これは結構ありがたい。

sealed classは今までは必ず子クラスは親の中で定義する必要があった(sealed A を継承する場合は A.B)のですが、これが不要になり、同じファイル内ならどこに書いても良いことになりました。

sealed class Score(val number: Int)

class Strike() : Score(10)
class Gutter() : Score(0)
class Miss() : Score(0)

importする時に若干面倒だったのでありがたいですね。

Java 7/8 support

Kotlin1.1からJava7/8がサポートされました。

ちゃんとJavaの新しいクラスなんかもKotlinで使えます。

JavaScript

JSサポートが強化されているらしいですが、使ってるのを聞いたことがないのでよく分かりませんでした。

眺めてみて

結構色々便利にはなっていますが、どれもまだEAPなので変わる可能性はありますし、今後もっと増えるかもしれません。

余談ですが、IDEAの2016.3で1.1M3のプラグインがまともに動かなかったので、Android Studioを使ってAndroidアプリで試しました。