Shoken Startup Blog

KitchHike Founder/CTO

よちよち.rb 第25回を見学して勉強会というコミュニティについて考えたこと

よちよち.rb 第25回に主催者のゆかおさんにお願いして見学させてもらいました。

よちよち.rb 第25回 - よちよち.rb(初心者限定 Ruby On Rails 4 とか勉強会) | Doorkeeper

僕は2年ほど前に丸の内MongoDB勉強会を始めました。(しばらく海外に行っていたので今は別の人が主催をしてくれています。)

勉強会という一つのコミュニティを始めるにあたって、意識したことが2つありました。

  1. 自分がいなくなっても継続するコミュニティを目指す
  2. 参加条件を用意して参加者のモチベーションを一定以上に保つ

よちよち.rbというミートアップに参加して感じたことは、上の2つとは全く逆のことでした。 まずは、この2つを意識するようになった経緯を書きたいと思います。

自分がいなくなっても継続するコミュニティを目指す

これは新卒で入社して間もない頃、日本の著名なDebianコミッターの方の講演を聞く機会があり、その中での質疑応答がもろに影響を受けています。 僕の質問「理想の(オープンソース)コミュニティとはどんなコミュニティですか?」に、「自分がいなくなっても継続していくコミュニティが理想なんじゃないかな。」と答えてもらいました。クラウドなんてほとんど知られてなかった当時ですが、DNSなどに代表されるWWWの分散型システムと重なり、とてもとても影響を受けました。

これについて丸の内MongoDB勉強会ではある程度達成できたと思っています。初回の勉強会から約2年。周りの方々の協力のおかげで、僕が海外に行った後でも丸の内MongoDB勉強会は継続的に開催されています。次回は7/17に開催予定です。 丸の内MongoDB勉強会 #18 - 丸の内MongoDB勉強会 | Doorkeeper

参加条件を用意して参加者のモチベーションを一定以上に保つ

こちらはDebian勉強会に影響を受けました。学生時代に初めて参加した勉強会です。とてもドキドキしながら、まだ渋谷にあったGoogleオフィスで開催された東京エリアDebian勉強会に行ったのを覚えています。 当時も今も、Debian勉強会は参加登録と同時に、事前課題を提出することを参加の条件としています。事前課題と言っても、勉強会に参加しようとする人なら時間を少々かければできるものです。当時の事前課題はたしか「自分の好きなオープンソースライセンスとその理由」だったと記憶しています。最新の事前課題を見ると、「本日の東京エリアDebian勉強会でDebianに関して何の作業をするかを宣言してください。」でした。 この事前課題によって、

  • 勉強会参加者の共通知識ができる
  • せっかく事前課題をやったのだから、という気持ちがドタキャンを抑止する
  • まったく事前学習無しのセミナーノリでの参加を抑止し、モチベーション高く参加する

という効果が期待されます。 当時も事前課題のおかげで、初参加の学生ながら会話に参加できたことを覚えています。

丸の内MongoDB勉強会では事前課題では無く、毎回ハンズオン勉強会という形式にして参加条件に「ノートPCにMongoDBをインストールしてくること」としました。こちらもある程度効果があったと思います。全員のノートPCにMongoDBがあることを前提に、レプリカセットやシャーディングなどを参加者同士で実践することができました。

でも、よちよち.rbでは違った

よちよち.rbに参加してまず感じたことは、主催者であるゆかおさんのあたたかさと、ゆかおさんを中心としたコミュニティの一体感。ゆかおさん主催だから参加している人も多いんじゃないかと思います。コミュニティを作る情熱とモチベーションがなければ、週1で勉強会を25回も続ける事はできません。ゆかおさんには感服です。この人じゃないとできない勉強会、これも理想の一つなんだなと考えが変わりました。

そしてもう一つのほう。よちよち.rbでは特に事前課題は無いようです。参加条件も初心者向けミートアップという意味での、「プログラミング歴2年以内程度を想定」というもののみ。にも関わらず、参加者みんなモチベーションが高い。セミナー気分で参加している人はゼロでした。 その理由の一つをWeb上のコメントで見つけました。「前回行けなかったけど、参加者コメントを見てモチベーションが上がった。がんばろう。」という趣旨の書き込みがありました。このいっしょに勉強して、いっしょに成長することが最大のモチベーションなんですね。ミートアップのREADME.mdに書いてありました。

この勉強会のいちばんの目的は、Railsを習得することではありません。目線の高さが同じ人といっしょにひとつの活動を根気よく続けることで自信をつけていくこと、これが最大の目標です。

講師と生徒、のような形式はとらないつもりです。みんなで転んで、みんなで進んでいきます。 効率よりも経過を重視します。2時間で1ページも進まなくても、その時間で考え実行したことが宝です。

参加/不参加はお気軽にどうぞ。無理のない範囲で、たのしく技術をみがいていきましょう。

yochiyochirb/meetups

他にも工夫がいっぱい

  • ミートアップの最初は一人一人自己紹介。1時間程度使っている。

  • 自己紹介は各自Gistを用意。

  • 自己紹介時に自己診断でRailsとGitの経験(ステータス)を共有。

  • Railsチュートリアルをみんなで進めるので継続参加を推奨している。

  • 参加者は名札をつける。前回参加者は持参する。

  • 参加するとシールがもらえるラジオ体操方式。

  • 4,5人のグループにわかれて、Railsチュートリアルを音読する。

  • グループで話題に上がった事をGithubのissueで管理。後ほど全体で共有。

よちよち.rbのみなさまに感謝

よちよち.rbに参加して、コミュニティへの考え方が大きく変わりました。ゆかおさんが中心となって、参加者全員で作っているミートアップなんだな、と感じました。最後にもう一度引用します。

講師と生徒、のような形式はとらないつもりです。

みんなで転んで、みんなで進んでいきます。

効率よりも経過を重視します。2時間で1ページも進まなくても、その時間で考え実行したことが宝です。

yochiyochirb/meetups

Swiftのfuncの引数に出てくるアンダースコアやシャープの意味について調べた

Swiftではfuncでメソッドを定義する。 funcの引数に出てくるアンダースコアやシャープはExternal Parameter Namesという。 これはメソッドの引数をラベル化する場合に使う。Rubyのキーワード引数と似ている。

Swiftでは第二引数以降はラベルを付けて呼び出す必要がある

func join(s1: String, s2: String, joiner: String) -> String {
    return s1 + joiner + s2
}
join("hello", "world", ":") // => コンパイルエラー

join("hello", s2: "world", joiner: ":") // => OK

https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Functions.html

アンダースコア ( _ ) は、引数が自明でラベルが必要ない場合に使う

// s2の前に_を書くと、
func join(s1: String, _ s2: String, joiner: String = " ") -> String {
    return s1 + joiner + s2
}

join("hello", "world", joiner: ":") // s2はラベル無しでOK

明示的にラベルで指定して欲しい時は、ラベルとして変数の前に外部から参照する文字列を置く

func join(first s1: String, second s2: String, joiner joiner: String) -> String {
    return s1 + joiner + s2
}

join(first: "hello", second: "world", joiner: ":")

シャープ ( # )は上記の外部から見えるラベルと、内部で使うパラメータが同じ名前の場合に簡略化できる

func join(s1: String, #s2: String, #joiner: String) -> String {
    return s1 + joiner + s2
}

join("hello", s2: "world", joiner: ":")

Shorthand External Parameter Names in Swift - Qiita

イニシャライザではデフォルト値が無くてもラベルが必要

class Example {
  init( counter: Int) { }
}

var example = Example(counter: 0) //ラベル必須
class Example {
  init( _ counter: Int) { }
}

var example = Example(0) //アンダースコアがあるとラベルはいらない

Swiftでpublic/privateなどのアクセスコントロールについて他

今日得たSwiftの知識

public/privateなどのアクセスコントロールはまだ実装されてない

Apple Developer Forumsのスレッドで、もうすぐ実装されると書いてある。

Does Swift have access modifiers? - Stack Overflow

Rangeの仕様がRubyと逆

for i in 1..3 {
  print( i )
}
// => 12

for i in 1...3 {
  print( i )
}
// => 123

Rubyと逆。

プロパティの監視ができる

willset, didSetというものがあって、それぞれプロパティに"値を入れる前"、"値を入れた後"の処理を記述できる。 また、didSetの中でoldValueという変数で直前の値を参照できる。

class ExampleClass {
    var counter: Int {
        willSet {
            println("willSet")
        }
        didSet {
            println("didSet")
            println("old counter = \(oldValue)")
            println("current counter = \(counter)")
        }
    }
    init(counter: Int) {
        // 初期化時は呼ばれない
       self.counter = 0
    }
}

Computed Propertiesではプロパティ宣言と同時にgetter, setterが定義できる

class ExampleClass {
    var base = 0
    var counter: Int {
        get {
            return base + 1
        }
        set {
            base += 1
        }
    }
}

getterのみの場合、returnだけ書けば良い。

class ExampleClass {
    var base = 0
    var counter: Int {
            return base + 1
    }
}

すべてのプロパティに初期値が設定されている場合はイニシャライザを省略可能

The Swift Programming Language: Initialization

Swiftでカリー化した関数を書くことができる

Swift - Trailing Closures and Curried function - Qiita

Swiftをターミナルで実行してWebAPIからjsonを取得してみる

Qiitaに投稿した。

Swiftをコマンドラインで実行してWebAPIからjsonを取得する - Qiita

コンパイル型言語なのにRubyirbのようなREPLモードができて楽しい。

f:id:sfujisak:20140613143148g:plain

ターミナルでSwiftを起動

$ sudo xcode-select -switch /Applications/Xcode6-Beta.app/Contents/Developer
$ xcrun swift 

実行したコード

ターミナルにコピペすれば動く。

import Foundation
// Thanks to tiqav api! ( http://dev.tiqav.com/ )
var request = NSURLRequest(URL: NSURL(string: "http://api.tiqav.com/search/random.json"))
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
var json: NSArray = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.AllowFragments, error: nil) as NSArray
for item: NSDictionary! in json {
  println(item.objectForKey("source_url"))
}

SwiftでAVFoundationを使ったカメラアプリ

Qiitaに投稿した。

SwiftでAVFoundationを使ったカメラアプリのサンプル - Qiita

ソースコード

Swift Camera Sample

ViewController.swift

//
//  ViewController.swift
//  swift-camera-sample
//
//  Created by Shoken Fujisaki on 6/8/14.
//  Copyright (c) 2014 Shoken Fujisaki. All rights reserved.
//

import UIKit
import AVFoundation

class ViewController: UIViewController {
    
    var stillImageOutput: AVCaptureStillImageOutput!
    var session: AVCaptureSession!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        // Start Camera
        self.configureCamera()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func configureCamera() -> Bool {
        // init camera device
        var captureDevice: AVCaptureDevice?
        var devices: NSArray = AVCaptureDevice.devices()
        
        // find back camera
        for device: AnyObject in devices {
            if device.position == AVCaptureDevicePosition.Back {
                captureDevice = device as? AVCaptureDevice
            }
        }

        if captureDevice {
            // Debug
            println(captureDevice!.localizedName)
            println(captureDevice!.modelID)
        } else {
            println("Missing Camera")
            return false
        }
        
        // init device input
        var error: NSErrorPointer!
        var deviceInput: AVCaptureInput = AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error: error) as AVCaptureInput
        
        self.stillImageOutput = AVCaptureStillImageOutput()
        
        // init session
        self.session = AVCaptureSession()
        self.session.sessionPreset = AVCaptureSessionPresetPhoto
        self.session.addInput(deviceInput as AVCaptureInput)
        self.session.addOutput(self.stillImageOutput)
        
        // layer for preview
        var previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer.layerWithSession(self.session) as AVCaptureVideoPreviewLayer
        previewLayer.frame = self.view.bounds
        self.view.layer.addSublayer(previewLayer)
        
        self.session.startRunning()
        
        return true
    }

}