ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

ストレスを溜めやすいエンジニアの体調管理

今週のお題「体調管理」

どうも、NEWGAME!では八神コウちゃんが一番好きなペンギン村の住人@tobi462です。

今回は今週のお題である体調管理について書いてみたいと思います。

事前に書いておきますが、技術的な要素は一切ありません。(Tech Blogなのにね・・・)

体調を大いに崩した去年末

毎日のような頭痛、朝起きるのはものすごく辛い、仕事もプライベートも楽しくない、なんだかずっと疲れている。

というのが私の去年のピークでした。はい、有り体に言って軽いうつ状態というやつですね。

仕事は続けられていたものの大した成果も出せず、わりと辛い日々を過ごしていました。

しかし、社内カウンセリングに行ったきっかけから、少しずつ回復しつつある現状の体調管理について書いてみたいと思います。

IT業界はストレスを溜めやすいですし、同じように苦しんでいる方に私の経験を共有して、何かしらのプラスにつながれば良いと思い記事にしてみました。

寝る前に思考内容を書き出す

前述したようにわりと限界が近い状態で、社内のカウセリングに行きました。(カウンセリングを勧めてくれた同僚にはとても感謝してます)

その時の私は、いつも仕事のことが頭から離れないというのがストレスというか悩みでした。

その時カウンセラーにオススメされたのが、金曜日の夜などに思考内容を書き出して整理するというものでした。

そのメソッド自体は知っていたものの、その時の私にそれを実践しようという発想はもちろん出てくるものではありませんでした。

それから不定期ではあるものの寝る前に気持ちのダンプっぽいものをやるようになりました。

手書きの方が良いのかもしれませんが、とりあえずテキストエディタ(これは余談ですがVSCodeを好んで使っています)を立ち上げ、思うがままに考えをタイプして、最後は保存せずに破棄するというやり方をしてみました。

そうしたところ、やはり休みの日でも仕事のことを考えてしまうことはあるものの、少しだけ回数が減ったように感じ、前よりはストレスレベルも落ちたような気がしました。

運動駆動開発

次に運動の回数を増やしました。

私は以前からスポーツジムに通って、軽いランニングはしていたのですが、その頃は運動の回数もめっきり減ってしまっていました。

そこで、最初は3日に1回を目標にし、次に2日に1回のペースで、30分ほどのランニングをするようになりました。

これの効果は抜群で、久しぶりに自分の思考回路が戻ったというか、頭の中に渦巻いている悪いものが抜けた気がしました。

運動駆動と表現したのは、忙しくなるとあっという間に運動しなくなるという事実からです。

しかし、運動しないことが最大のリスクという認識があったので、運動から駆動させるという考えを採用しました。

調子にのらない

さて人間不思議なもので、体調が良くなってくると妙に強気になってきます。

全快にはほど遠い状況でありながら、これはもう大丈夫だろうと夜遅くまでコードを書いたり、あるいはアニメを見たりすることもありました。

結果はもちろん悲惨なもので、翌日の朝は体調が悪く、仕事もあまり調子が乗らないこともしばしばです。

体調が少し良くなったからといって油断しないこと、これは今でもハマりやすいポイントなので気をつけるようにしています。

オープンソースであれ

前述したように、去年、社内のカウセリングに行きました。

あまりそういったことは誰かに話そうとも思わなかったのですが、あえて部署内の日報に書いてみました。

その後も体調悪いとか、今日は運動するとか、回復傾向かもとか書くようにしました。

それの効果かは分かりませんが、体調が悪くて生産性が悪い自分を少し許せるようになったような気がします。

体調が悪ければ、それを隠さずにオープンにしてしまう。そうすると少し客観的に慣れて、自分自身でも無理をしなくなるような気がしました。

がんばらない勇気

というのが大切だと改めて思いました。

とくに脳が不調の時は、がんばろうと考えたって、ぶっちゃけわりとどうしようもない気がします。

であれば、勇気をもってがんばらないほうが最終的には生産的であるような気もします。

まとめ

といった形で、私の最近の体調管理、あるいは体調を回復するためにしていることを記事にしてみました。(これちゃんとお題のテーマに沿ってますかね・・・?)

今回の記事で挙げたのは以下です。

  1. 寝る前に思考内容をダンプする
  2. 運動駆動開発
  3. 少し体調が良くなっても過信しない
  4. オープンソースに振る舞う
  5. 頑張らない勇気

何かしら参考になるものがあれば幸いです。

【iOS】【Swift】「RootViewController + Wireframe」で画面遷移での消耗を回避する

自己紹介

はじめまして、ペンギン村で一番やかましい住人のナガクラ(@nagakuta)です!
Slackだけでなく、ブログもやかましく更新していきます!!(宣言)

TL;DR

  • RootViewControllerAppDelegate.window.rootViewControllerに指定してから画面遷移するようにすると色々ラクだよ!
  • Wireframeも一緒に使うとテスト書くときラクだよ!!

RootViewControllerによる画面遷移

RootViewController #とは

自分がRootViewControllerについて知ったのは、ペンギン村に貼られた以下の記事でした。

medium.com

その記事のリンクにRootViewControllerについての詳細記事がありました。

medium.com

その記事中にて説明されているRootViewControllerについて簡潔にザザッと説明すると、

  • 概念として、Container ViewControllerの延長である
  • AppDelegate.window.rootViewControllerに設定するViewControllerである
  • このViewControllerを起点に画面遷移を行うようにする

となります。なにこれ便利そう😲😲😲

そして、RootViewControllerを実装するメリットをサクッと説明すると、

  • 1つのナビゲーションスタックだけで新しいViewControllerを表示したり、インタフェースなしで戻したりすることができる
  • なので、どんなにViewが重なっていようと、最下層のViewControllerをカンタンに取得、あるいは差し替えることができる
  • 実は、AppDelegate.window.rootViewControllerを直接差し替えても、差し替え前のViewControllerがメモリ解放されないらしく、差し替えるたびにメモリ領域を圧迫する😨
  • AppDelegate.window.rootViewControllerRootViewControllerに固定することで、メモリ領域の不必要な圧迫を防ぐ

の3点でしょうか。なにそれすごく便利そう😂😂😂

RootViewControllerを実装してみる

では、実際にRootViewControllerを実装してみます( ´∀`)(一部端折っている箇所があります🙇)

RootViewController.swift

final internal class RootViewController: UIViewController {

    // MARK: - Life Cycle Methods

    init() {
        super.init(nibName: nil, bundle: nil)
    }

    override viewDidLoad()
        super.viewDidLoad()

        // 起動直後に遷移させる画面を宣言
        let launchViewController: UIViewController = LaunchViewController()

        // 子ViewのViewControllerを指定
        self.addChildViewContrlller(launchViewController)

        // 子ViewをSubViewとして追加
        launchViewController.view.frame = UIScreen.main.bounds
        self.view.addSubView(launchViewController.view)

        // 子Viewの所有権を譲渡
        launchViewController.didMove(toParentViewController: self)
    }

    /// NextViewControllerに遷移
    func transitToNextViewController() {
        let nextViewController: UIViewController

        // 子ビューのChildViewControllerを削除
        let childViewController: UIViewController = self.childViewControllers.first!
        childViewController.willMove(toParentViewController: nil)
        childViewController.view.removeFromSuperView()
        childViewController.removeFromParentViewController()

        // 新しいViewControllerを子ビューとして追加
        self.addChildViewController(nextViewController)
        nextViewController.view.frame = UIScreen.main.bounds
        self.view.addSubView(nextViewController.view)
        nextViewController.didMove(toParentViewController: self)
    }

}

LaunchViewController.swift

final internal class LaunchViewController: UIViewController {

    // MARK: - Life Cycle Methods

    (中略)

    override viewDidAppear(_ animated: Bool) {
        let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
        let rootViewController: UIViewController = appDelegate.window!.rootViewController as! RootViewController

        // NextViewControllerに遷移
        rootViewController.transitToNextViewController()
    }

    (以下略)

}

AppDelegate.swift

@UIApplicationMain
internal class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // 起動直後に遷移する画面をRootViewControllerに指定する
        self.window = UIWindow(frame: UIScreen.main.bounds)
        self.window.rootViewController = RootViewController()
        self.window.makeKeyAndVisible()

        return true
    }

    (以下略)

}

これで、起動 → RootViewControllerに遷移 → (RootViewControllerの子ビューをLaunchViewControllerに) → LaunchViewControllerに遷移 → (RootViewControllerの子ビューをNextViewControllerに置き換え) → NextViewControllerに遷移が実現できました( ´∀`)bグッ!

ここからNextViewControllerをログイン画面とするなり、メインコンテンツを表示させるなりするのが一般的なiOSアプリケーションの起動後の動きになると思います。

Wireframeで画面遷移

Wireframe #とは

Wireframeというのをざっくりと説明すると、「画面遷移をViewControllerの代わりに行うための仕組み」です。

通常、画面遷移を実装する際はUIViewControllerのメソッドを利用して行うと思います。(先程のRootViewControllerでも、Viewの上に重ねるという形で画面遷移を実装しています)

しかし、画面遷移をUIViewControllerクラスで行うと、少しViewControllerが膨らんでしまいますし、ViewControllerごとに同様の処理を何回も何回も実装するのはアホくs…スマートじゃないですよね😅

そんな時に使ってみたいのがWireframeです。Wireframeで画面遷移用のメソッドを実装し、ViewControllerクラスからそのメソッドを呼び出すようにすれば、いちいち画面遷移についてあーだこーだと考えずにViewControllerは画面表示のことに専念できます👍

また、遷移先の画面を指定できるようになれば、Unitテストの際にモックのViewControllerを指定することができるようになるため、テストを書くのが格段に楽チンになります😆😆😆

Wireframeによる画面遷移を実装してみる

ではでは、Wireframeを実装してみましょう(`・ω・´)ガンバル

RootWireframe.swift

internal protocol Wireframe {
    /// 指定した画面に遷移
    func transition(to viewController: UIViewController)
}

internal struct RootViewWireframe: Wireframe {

    func transition(to viewController: UIViewController) {
        let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
        let rootViewController: RootViewController = appDelegate.window!.rootViewController as! RootViewController

        // RootViewControllerの子ビューを削除
        if !rootViewController.childViewControllers.isEmpty {
            rootViewController.childViewControllers.forEach { (childViewController: UIViewController) in
                childViewController.willMove(toParentViewController: nil)
                childViewController.view.removeFromSuperView()
                childViewController.removeFromParentViewController()
        }

        // 以下はRootViewController.viewDidLoadメソッドの内容をそのまま移植
        rootViewController.addChildViewController(viewController)
        viewController.view.frame = UIScreen.main.bounds
        rootViewController.view.addSubView(viewController.view)
        viewController.didMove(toParentViewController: rootView)
    }

}

これでWireframeの実装ができました👍

では、RootViewControllerの画面遷移をWireframeに移譲します(`・ω・´)シャキーン

RootViewController.swift

final internal class RootViewController: UIViewController {

    let wireframe: RootViewWireframe = RootViewWireframe()

    (中略)

    override viewDidLoad() {
        super.viewDidLoad()

        // Wireframeによる画面遷移
        let childViewController: UIViewController = UIViewController()
        self.wireframe.transition(to childViewController)
    }

    (以下略)
}

LaunchViewController.swift

final internal class LaunchViewController: UIViewController {

    let wireframe: RootViewWireframe = RootViewWireframe()

    (中略)

    override func viewDidAppear(_ animated: Bool) {
        let nextViewController: UIViewController = UIViewController()
        self.wireframe.transition(to nextViewController)
    }

    (以下略)

}

これで、Wireframeによる画面遷移が実装できました!(∩´∀`)∩ワーイ

これで、AppDelegate.window.rootViewControllerの差し替えも容易になり、重なりまくった子ビューから最下層のViewを取得するのもRootViewController.childViewControllers.firstで取得できるようになります💪💪💪

あとがき

RootViewController + Wireframe による画面遷移」、いかがだったでしょうか😃😃😃

これからも、「楽に、最小効率で」を目的に勉強したTipsをこのブログで公開していきたいなと思います(`・ω・´)
次回更新もお楽しみに!!!

シェルスクリプトで現在のブランチ名を取得する(git rev-parse)

ペンギン村の住人、tobi462です。

今回はシェルスクリプトなどのプログラムからgitの情報を取得する際に、rev-parseという便利なコマンドがあったので、その紹介と教訓について書いてみます。

Tl;Dr

$ git rev-parse --abbrev-ref HEAD
master

冴えないやり方

実のところあとからググったらいくらでも記事があったのですが、最初は以下のように書いていました。

$ git branch | grep '*' | sed -e 's/* //'

まぁ、結果は同じになると思いますが、明らかに rev-parse を使ったやり方のほうがスッキリしています。

rev-parse って?

与えられたものが何かを調べるコマンドらしく、なんだかいろいろ出来るようです。 Git - git-rev-parse Documentation

以下では HEAD が指すコミットハッシュ値を取得しています。

$ git rev-parse HEAD
154cc76c53b6236247f0143272870447d336f4cf

$ git rev-parse --short HEAD
154cc76

どうやら普段のGit操作というよりは、Gitの情報を取得したい時につかえるコマンドという印象です。

--abbrev-ref って?

オンラインマニュアルによれば、

A non-ambiguous short name of the objects name. The option core.warnAmbiguousRefs is used to select the strict abbreviation mode.

とのことで、 どうやら曖昧でない短い名前を返すようです。言い換えると取得された名前を使用して参照すれば、確実に1つのハッシュ値に限定できるということでしょう。

まとめ(あるいは教訓)

普段利用しているようなGitコマンド(e.g. branchstatus)は人間向けのUIとしての側面を持っているので、プログラム的に何か扱いたい時はrev-parseを使うのが良さそう、というのが今回の教訓でした。

Swiftと比較しながら見る、KotlinのDSLを支える技術

自己紹介

はじめまして、けものフレンズではサーバルちゃんが一番好きなペンギン村の住人@tobi462です。

自分の技術ブログ(My Favorite Things - Coding or die.)も持っているのですが、楽しそうな記事はこっちで書きたいなって気分です。

という感じで、一発目の記事なので自己紹介でした。

さて今回はKotlinのDSLを支える技術について、Swiftと比較しながら機能を見ていきたいと思います。

DSLとは

DSLは、ドメイン特化言語(Domain Specific Language)と呼ばれます。

あれこれ説明するよりも、実際のコード例を見るのが早いかもしれません。以下はkotlinx.htmlパッケージのAPIを使ってHTMLを組み立てる例です。

val html = createHTML().
        table {
            tr {
                td { +"Hello" }
            }
        }
println(html)

これは多くの方の予想どおり以下のような出力結果が得られます。

<table>
  <tr>
    <td>Hello</td>
  </tr>
</table>

これは間違いなくKotlinのコードであり、実際にコンパイルも出来ますが、一見すると他の言語のようにも見えます。

このようにプログラミング言語に備わった機能を利用し、APIなどを工夫し、特定のタスクを解きやすくするための専用の構文が用意されているかの様に見えるのがDSLの特徴です。

”ドメイン特化”と言われる理由が分かるかと思います。

ちなみにDSLとしては、SQLなど他の言語として書かれる「外部DSL」と、今回のようにその言語内で表現される「内部DSL」とがあります。

静的型付け言語における内部DSLは、コンパイル時に意図した構造かをチェックできるのに加え、IDEなどの補完を活用できるというのがメリットとして挙げられるかと思います。

KotlinのDSL

KotlinのDSLを支える言語仕様について、Kotlin in Actionでは以下が列挙されています。

Kotlinの機能 Swiftにおける機能
拡張関数 extensions
演算子オーバーロード 同様
メソッド規約 subscript
括弧の外側のラムダ 接尾クロージャ
中置呼び出し なし
レシーバ付きラムダ なし

見てのとおり、多くはSwiftでもサポートしていますが、一部はサポートされていません。

順に見ていきたいと思います。

拡張関数(extension function)

既存のクラスに対して、新しいメソッドやプロパティを定義できる機能です。

以下ではstrong()という、新たなメソッドを既存のStringクラスに追加しています。

fun String.strong(): String {
    return this + "!!"
}

"Hello".strong() // => "Hello!!"

Swiftでは以下のようになります。

extension String {
    func strong() -> String {
        return self + "!!"
    }
}

"Hello".strong() // => "Hello!!"

定義方法は異なりますが、呼び出し側からみるとKotlinもSwiftも同等です。

演算子オーバーロード

演算子が利用された時の処理を、独自にカスタマイズできる仕組みです。

Swiftは新たな演算子が自分で定義できるのに対し、Kotlinでは言語に用意された演算子以外をオーバーロードすることは出来ません

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

val p1 = Point(10, 20)
val p2 = Point(30, 40)
p1 + p2 // => Point(x=40, y=60)

// 通常の呼び方
p1.plus(p2)

Swiftでは以下のように独自の演算子を定義できます。

infix operator ++

func ++ (a: Point, b: Point) -> Point {
    return Point(x: a.x + b.x, y: a.y + b.y)
}

let p1 = Point(x: 10, y: 20)
let p2 = Point(x: 30, y: 40)
p1 ++ p2 // => Point(x: 40, y: 60)

一見すると、自由に演算子を定義できるSwiftの方が優れているように見えますが、Kotlinはあえて制限することでコードをシンプルに保つという言語思想のようです。

私見ですが、独自の演算子は関数型ライブラリを作成する時などによく使われる印象があるので、そうした際にはSwiftの方がより可読性の高いAPIを提供できるかもしれません。

メソッド規約

x = array[0]array[0] = xといったように、[]など特定の書き方をした際の挙動をカスタマイズする仕組みです。

Kotlinではoperatorというキーワードに加えて、規約で定められたシグネチャで実装することで実現できます。

以下では独自に定義したPointクラスに対して[]が利用できるようにしています。

operator fun Point.get(index: Int): Int {
    return when(index) {
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException()
    }
}

val p = Point(10, 20)
p[0] // => 10
p[1] // => 20

// 通常の呼び方
p.get(0) // => 10
p.get(1) // => 20

以下のように、代入時の挙動もカスタマイズ出来ます。

data class MutablePoint(var x: Int, var y: Int)

operator fun MutablePoint.set(index: Int, value: Int) {
    when (index) {
        0 -> x = value
        1 -> y = value
    }
}

val point = MutablePoint(10, 20)
point[0] = 30
point[1] = 40
println(point) // => MutablePoint(30, 40)

// 通常の呼び方
point.set(0, 30)
point.set(1, 40)

Swiftでは同様の機能はSubscriptと呼ばれるもので実現します。

extension MutablePoint {
    subscript(index: Int) -> Int {
        get {
            switch index {
            case 0:  return x
            case 1:  return y
            default: assert(false)
            }
        }
        set {
            switch index {
            case 0:  x = newValue
            case 1:  y = newValue
            default: assert(false)
            }
        }
    }
}

var mp = MutablePoint(x: 10, y: 20)
mp[0] = 30
mp[1] = 40
mp[0] // => 30
mp[1] // => 40

両言語とも引数の数を変更できる点は同じですが、Kotlinは他にもいくつか規約をサポートしています。

以下にKotlinでサポートされている規約をいくつか挙げてみます。

シンタックス 関数呼び出し
x[a, b] x.get(a, b)
x[a, b] = c x.set(a, b, c)
a in c c.contains(a)
start..end start.rangeTo(end)
val (a, b) = p val a = p.component1(); val b = p.component2()
a("hello") a.invoke("hello")

中でも最後のinvoke規約はDSLを支える上で便利な機能なので、詳しく見ていきたいと思います。

invoke規約

invoke規約とは、オブジェクトそれ自体をメソッドのように呼び出せるようにする仕組みです。

以下では、Pointオブジェクトそれ自体をp("Hello")という形で呼び出せています。

operator fun Point.invoke(prefix: String) {
    println("$prefix $this")
}

val p = Point(10, 20)
p("Hello") // => "Hello Point(10, 20)"

それ自体を呼び出せるという表現から、関数オブジェクトを思い浮かべる方も多いかもしれません。

実際、関数オブジェクトはinvoke規約を用いた仕組みで実現されています。見比べると先程のコードとの共通性を見つけられると思います。

val succ: (Int) -> Int = { it + 1 }
succ(1) // => 2

Kotlintestの例

一見すると、これはコードの意図が分かりづらくなるようにも見えますが、DSLを構築する上では便利です。

以下は、サードパーティ製のKotlintestを用いたテストコードの例です。

class PlusSpec : ShouldSpec({
    "1 + 1" {
        should("return 2") {
            (1 + 1) shouldEqual 3
        }
    }
})

詳細は割愛しますが、ここで注目したいのは"1 + 1" { ... }という、一見するとKotlinには見えないコードです。

コードを読むと、以下のようにString型に対してinvokeメソッドを追加することで実現されています。

operator fun String.invoke(init: () -> Unit): Unit {
    ...
}

引数として関数型() -> Unitを受け取るようになっており、呼び出し時にはラムダ式を利用しているのがポイントです。

先程のKotlintestのコードを省略せずに記述すると以下のようになります。

class PlusSpec : ShouldSpec({
    "1 + 1".invoke({
        should("return 2") {
            (1 + 1) shouldEqual 2
        }
    })
})

これは仕組みが分かりやすいという点では優れていますが、DSLの可読性という面ではノイズが多く、invoke規約によって可読性の高いDSLを提供することができる良い例になっていると思います。

括弧の外側のラムダ

最後の引数がラムダ式の場合、ラムダ式を引数の()の外に出すことができる機能です。

先程のinvoke規約で挙げたKotlintestのコードでも利用されています。

以下は標準APIであるfilter関数をただラップした、selectという拡張メソッドを定義する例です。(わかりやすさのためジェネリクスは使用していません)

fun <Int> List<Int>.select(predicate: (Int) -> Boolean): List<Int> {
    return this.filter(predicate)
}

val xs = listOf(1, 2, 3, 4)
xs.select { it % 2 == 0 } // [2, 4]

// 通常の呼び方
xs.select({ it % 2 == 0 })

Swiftでは以下のようになります。

extension Array {
    func select(_ predicate: (Element) -> Bool) -> [Element] {
        return self.filter(predicate)
    }
}

let xs = [1, 2, 3, 4]
xs.select { $0 % 2 == 0 } // => [2, 4]

// 通常の呼び方
xs.select({ it % 2 == 0 })

Kotlinの方が制約が多いですが、呼び出し側は両言語とも同じシンタックスになるのが分かります。

中置呼び出し

さて、ここからはSwiftには完全にない機能です。

中置呼び出しは、メソッド呼び出し時に.を利用せず、代わりにスペースを利用することが出来る機能です。infixキーワードをつけた関数が対象になります。

infix fun Int.add(x: Int): Int {
    return this + x
}

// 中置呼び出し
1 add 1  // => 2

// 通常
1.add(1) // => 2

中置呼び出しは、Mapを生成するための標準APIとしても利用されています。以下ではtoが中置呼び出しとして利用されています。

val dict = mapOf(1 to "one", 2 to "two")
dict[1] // => "one"
dict[2] // => "two"

他には先程invoke規約のところで上げた、KotlintestのAssertionコードも良い例です。

(1 + 1) shouldEqual 2

レシーバ付きラムダ

レシーバ付きラムダは、ラムダ式に暗黙のレシーバを渡し、ラムダ式の中でthisとして参照できる機能です。ポイントはthisを省略できるという点です。

標準APIのwithを見てみたいと思います。

val point = MutablePoint(10, 20)
with(point) {
    x = 50
    y = 60
}
println(point) // => MutablePoint(50, 60)

一見するとwithは言語に組み込まれた構文のように見えます。しかし、実体は単なるトップレベルの関数であり、withに渡した引数mpがラムダ式にレシーバとして渡されている、という仕組みです。

レシーバはthisとして参照できるので、省略しない場合は以下のようになります。

with(point) {
    this.x = 50
    this.y = 60
}

kotlinx.htmlの例

冒頭で紹介した、HTML生成のコードでもレシーバ付きラムダが利用されており、thisを省略しない場合は以下のようなコードになります。

val html = createHTML().
        table {
            this.tr {
                this.td { +"Hello" }
            }
        }

このようにレシーバ付きラムダは、構造化された宣言的なDSLを作るのに便利です。

Swiftでは

ちなみにレシーバ付きラムダがないSwiftでも、一見するとそのような機能が使われているように見えるコードが書かれたりすることがあります。以下は、BDDフレームワークであるQuick/Nimbleのコード例です。

class SampleTest: QuickSpec {
    override func spec() {
        describe("plus") {
            context("1 + 1") {
                it("2") {
                    expect(1 + 1) == 2
                }
            }
        }
    }
}

先程から挙げているKotlinのDSLコードに非常に似ていますし、状態を保持しながらコードが実行されるようにみえるため、暗黙的なレシーバが利用されているようにも見えます。

しかし、describecontextなどは単なるトップレベル関数であり、共通のシングルトンインスタンスに状態を追加しているだけです。

言い換えるとSwiftでは、先程のwithのような関数をスマートな形で実装できません。 qiita.com

機能のまとめ

さて、ここまでKotlinのDSLを支える機能を、Swiftと比較しながら見てきました。

それぞれの機能に細かい差はありましたが、大局で見ると以下のようになりました。

Swiftにもある機能

Kotlinの機能名 通常の書き方 DSLライクな書き方
拡張関数 String.strong(x) x.strong()
演算子オーバーロード 1.plus(2) 1 + 2
getメソッド規約 point.set(0, 10) point[0] = 10
括弧の外側のラムダ filter({ ... }) filter { ... }

Kotlinにのみの機能

Kotlinの機能名 通常の書き方 DSLライクな書き方
中置呼び出し 1.to("one") 1 to "one"
レシーバ付きラムダ sb.append("1"); sb.append("2") with(sb) { append("1"); append("2") }

最後に

シンタックスの面では結構似ている両言語ですが、このようにDSLを支える機能という観点から見てみると、それぞれの言語の思想が見えてきて面白いのではないかと思います。

といった形で、今回は機能紹介に特化した感じになってしまいましたが、本記事を締めくくりたいと思います。(機会があればもう少しDSLに立ち入った記事を書くかも?)

そんな感じで、今年も(?)よろしくお願いします。

ふわっと出てくるPopup処理をやってみる。

f:id:kamui_project_tony:20180107004816j:plain:w50

ペンギン村の住民のカムイです。

Qiitaに上げた記事や、僕が開発している日課メーターについて、こちらで何か書ければいいなぁって思ってます。

今回はこの間投稿したQiitaの記事について。 qiita.com

アニメーション処理をAutoLayoutを使って表現しています。 高さをいじるとWarningがアニメーション処理時に起きてしまうのですが、そこはPriority値をいじって対応しています。 まだまだ不恰好なため、目標は個人的に大好きなObjCのOSSSweetAlert-iOSのような動きができるようにしていきたいと思っています。

ツッコミがあればQiita記事内のコメントか、直接PRとかくれると泣いて喜びます。 よろしくどうぞ。

Welcome to ようこそ ペンギン村!

きみは技術に興味があるフレンズなんだね!

当ブログは、ペンギン村に住むエンジニア達が得た技術情報をシェアしていくブログです。

ペンギン村?

それはブログの運営が進むにつれ明らかにになっていくでしょう・・・(たぶんならない)

ともあれ、技術情報を世界に発信していきたいな、とペンギン村で立案され採択されたのがこのブログです。

これからどういう形になっていくか分かりませんが、面白い技術記事などを継続的に公開していけたらと思ってます。

つまりはこれからもどうかよろしくね!

ということで、ブログ開設の挨拶でした。