Edited at
ClojureDay 8

ペアプログラミングの可能性を広げる Party REPL

この記事は Clojure Advent Calendar 2018 8日目の記事です。

前日の記事は @lagenorhynque さんのClojureサーバサイドフレームワークDuctガイド でした。

もう年末ですが皆さんパーティしていますか?え、予定がない?パーティって何すればいいか分からない?

では今年の年末はペアプログラミングならぬパーティプログラミングなんていかがでしょうか。


Party REPL

いつも最善とは言えませんが、ペアプログラミングの効能は広く浸透しています。現在、モダンなエディタはリモートでの共同編集機能をサポートしており一台の端末の前に二人座って行う必要もなく、ペアプログラミングの環境は満足のいくものとなりつつあります。しかし、それを一段押し進めて「動作しているアプリケーション」を直接、ペアプログラミングで開発できるとしたらいかがでしょうか。

出来るんです Clojure なら!clojure/conj で紹介された Party REPL を使えば実現できます。

Party REPL — A multi-player REPL built for pair-programming

Paty REPL は ClojureScript と shadow-cljs で開発される Atom のプラグインで、TeleType と組み合わせることで「動作しているアプリケーション」のペアプログラミング開発を実現します。現状(v1.1.0)Linux/Mac に対応しており、leiningen で起動した nrepl, unrepl (, socket REPL) が使えます。この記事では Paty REPL の導入と使い方についてご紹介します。


TeleType

TeleType は Atom のペアプログラミング用プラグインで、リアルタイムにファイルの共同編集を行なえます。使用するには Github による認証が必要ですが、編集時は WebRTC により PC 同士が直接接続されます。CRDT: Conflict-free Replicated Data Types という編集方式を取っており、編集内容の衝突も起こらない(らしい)です。


Clojure REPL について

Clojure は Code as Data という特性を持ち、コードが全て構造化・記述・操作可能なデータです。また、コードを入力として受け取り、プログラムを実行し、その結果をデータとして出力する REPL(Read Eval Print Loop) という機能を持ちます。REPL に入力するコードは文字列で表現されたデータであるため、入出力を TCP 経由にすることも出来ます。

REPL はいくつかの階層に分かれます。(Clojure 1.9 以降の場合)


  • STD/IO REPL ... 標準入出力による REPL。

  • Socket REPL ... 上記に TCP 入出力をかぶせた REPL。

  • nrepl ... 上記の上に構築された REPL サーバ・クライアントプロトコル。ミドルウェア(piggieback など)により拡張可能。

nrepl は現状最も使われている REPL ですが nrepl サーバをミドルウェアで拡張するとクライアント REPL 側にも対応したツールが必要となってしまいます。これはローカル開発では問題ではありませんが、リモートの nrepl サーバに接続する場合は問題が起こる可能性があります。unrepl はそのために開発された新しい REPL で、接続時にサーバから必要なツールをクライアント側に読み込ませることでこの問題を解決しようとしています。また、Clojure 1.10 からはよりシンプルな prepl というストリームベースの REPL が標準に追加されるようです。Party REPL では unrepl を使ったほうがより多くの機能を使えるようなので、この記事では unrepl を使います。


セットアップ

ペアプログラミングに参加するホスト、ゲスト PC をそれぞれセットアップします。そもそも Atom を使ってなかったので、Clojure 開発環境のセットアップからやります。OS は macOS Mojave 10.14.1、Atom のバージョンは 1.33.0 です。


Atom インストール

公式サイトから Atom をインストールします。

https://atom.io/


Clojure 関連プラグインのインストール

必要に応じて Linter や Formatter などを導入します。私は下記の記事・動画を参考にセットアップしました。REPL 関係は紛らわしいのでインストールしないでおいて下さい。

Slick Clojure Editor Setup with Atom


Party REPL のインストール

下記のプラグインをインストールします。バージョンは執筆時点の最新です。


  • teletype v0.13.3

  • clojure-party-repl v1.1.0


TeleType サインイン

TeleType を使うためには Github にサインインする必要があります。TeleType メニューからサインインリンクに飛び、取得したトークンを入力して下さい。

Screen Shot 2018-12-06 at 7.28.10 PM.png

以上でセットアップは完了です。


使ってみる

私には conj デモのように Clojurian な奥さんはいないので一人で PC 並べてパーティします。Dark Theme がホスト、Light Theme がゲストです。


プロジェクト用意

開発するプロジェクトは Leiningen プロジェクトであればよいですが、今回は開発中にリセットが可能な duct を使うことにします。

lein new duct lets-party +site +example +ataraxy +cljs

cd lets-party
lein duct setup

unrepl を使うためには Socket REPL サーバを公開する必要があるため、project.clj に下記を追加してください。デフォルトではループバックアドレスで起動してリモートから接続出来ないため、:address \"0.0.0.0\" も忘れずにつけて下さい。

:jvm-opts ["-Dclojure.server.repl={:address \"0.0.0.0\" :port 9999 :accept clojure.core.server/repl}"]

後はお好みですが REPL を落とさずに開発を進めたいので、起動中のアプリに依存を読み込ませることの出来る alembic を開発プロファイルに追加しときます。

[alembic "0.3.2"]

以上でプロジェクトの準備は完了です。プロジェクトは Github に push して、参加者全員がチェックアウトして下さい。

https://github.com/223kazuki/lets-party


Local REPL 起動

まずはホスト側の準備です。ターミナルから repl を起動して下さい。

cd lets-party

lein repl

Party REPL から REPL を立ち上げることも可能ですが、nrepl クライントが開いて unrepl クライアントが開けなかったため、別途立ち上げた REPL サーバに接続する方法を取ります。


Local REPL 接続

Clojure Party Repl: ConnectToRemoteRepl コマンドを実行すると下記のフォームが開くので入力し接続して下さい。

Screen Shot 2018-12-07 at 10.57.18 AM.png

下記のようなパネル構成となります。

Screen Shot 2018-12-07 at 10.58.48 AM.png

右上がこれまでの実行結果が表示される REPL History パネル、右下が REPL にコードを送る REPL Entry パネルです。コマンドを実行するときは REPL Entry パネルに入力し、Clojure Party Repl: SendToRepl を実行して下さい。SendToRepl は Cmd-Enter にキーバインドされていますが、私は他のホットキーとぶつかるので Ctrl-Enter に割り当てました。実行結果は REEPL History パネルに反映されます。

Screen Shot 2018-12-07 at 11.01.45 AM.png

また、ソースファイル上でも実行可能です。Eval したい範囲を選択、もしくは括弧の後ろにカーソルを置いて SendToRepl でコードが評価されます。REPL 上の名前空間を移動するために ns フォームを評価することもお忘れなく。

Screen Shot 2018-12-07 at 11.03.46 AM.png

更に遅延シーケンスやサイズの大きいシーケンスに関しては折りたたみ表示をサポートしており、...more をクリックすることで展開していくことが可能です。

Screen Shot 2018-12-07 at 11.04.26 AM.png


TeleType でファイル共有

では、ペアプログラミングに移るために TeleType でソースファイルを共有します。ホストは共有を許可してリンクを取得しゲストに知らせます。TeleType アイコンをクリックし、"Share" でリンクを取得し、Slack などでゲストに通知します。

Screen Shot 2018-12-07 at 11.50.26 AM.png

ゲストは TeleType メニューの "Join A Portal" にリンクを入力することでホストの開いているファイルを開けるようになります。

Screen Shot 2018-12-07 at 11.07.01 AM.png

青と緑のカーソルがそれぞれホスト、ゲストのものです。

Screen Shot 2018-12-07 at 11.07.57 AM.png

ゲストがファイルを編集するとリアルタイムにホスト側にも反映されます。


Remote REPL 接続

では、いよいよゲストからホスト REPL に接続します。ホストがローカル REPL に接続したのと同様に Clojure Party Repl: ConnectToRemoteRepl でホスト REPL にリモート接続します。Host だけホストのものに変えてください。接続すると Entry, History パネルが開きますが、これは REPL サーバには接続していますが自分のローカルで起動した REPL クライアントです。ホスト側の REPL クライアントを共有して使いたいので、下記の2バッファーを開きます。


  • @223kazuki: Clojure Party REPL Entry lets-party

  • @223kazuki: Clojure Party REPL History lets-party

Screen Shot 2018-12-07 at 11.14.20 AM.png

これで REPL クライアントも共有しながら開発が出来るようになりました。ゲストも Entry パネルやソースファイルから SendToRepl でコードを実行することが出来ます。また、ホスト側ではゲストの操作をリアルタイムに見ながら、かつ Entry では実行履歴を補完(Cmd-up)することも可能です。


Let's Paty!

では、ペアプログラミング、いやパーティプログラミングを始めましょう。duct にハンドラを追加します。


1. アプリケーション起動

REPL Entry から (dev), (go) を実行し、アプリケーションを起動します。


2. ハンドラ追加

ファイルの追加はホスト側でしか出来ないので、ホスト側はファイルを追加します。


  • src/lets_party/handler/party.clj

実装はゲスト側で担当します。それぞれファイルを編集し、party エンドポイントを追加します。


  • src/lets_party/handler/party.clj

  • resources/lets_party/config.edn

Screen Shot 2018-12-07 at 11.25.34 AM.png

unrepl はデフォルトで標準出力の表示をサポートしてないようです。


3. alembic で依存追加

ここでゲストはエンドポイント開発に必要なライブラリ(hiccup)が足りないことに気付きます。ここで alembic を使います。

project.clj を編集し、


clojure

+ [hiccup "1.0.5"]


Entry パネルから alembic でプロジェクトを再読込します。

(require 'alembic.still)

(alembic.still/load-project)

これで REPL を切らずに必要なライブラリを使えるようになりました。エンドポイントに依存を追加して SendToRepl して下さい。


party.clj

(ns lets-party.handler.party

(:require [ataraxy.core :as ataraxy]
[ataraxy.response :as response]
[clojure.java.io :as io]
[integrant.core :as ig]
[hiccup.page :refer [html5 include-css include-js]])) ;; 追加


4. ハンドラ実装

必要に応じてホストが関数を実装したりもできます。関数の動作確認もソースファイルから行えます。エクストリーム・プログラミング的な使い方で役割分担しての開発も行えます。ns の切り替えでぶつからないように注意する必要はありますが。(それぞれ別の REPL クライアントを使うという方法もあります)


party.clj

(defn- party [n]

(case n
1 "Party1"
2 "Party2"
3 "Party3")) ;; こちらはホストが実装。

(comment
(party 1)
(party 2)
(party 3)) ;; 動作確認用。範囲選択して `SendToRepl` で試せる。

(defn- party-page [context]
(let [body
(html5
[:head
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
(include-css "//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/semantic.min.css")
[:title "Let's Party!"]]
[:body
[:h1 (party 1)]
(include-js "/js/main.js")])]
{:status 200 :headers {"Content-Type" "text/html"} :body body})) ;; ここはゲストが実装。

(defmethod ig/init-key :lets-party.handler/party [_ _]
(fn [{[_] :ataraxy/result}]
(party-page nil)))



5. アプリケーションリセット

実装後は (in-ns 'dev), (reset) を実行することでハンドラをサーバに追加し、ブラウザでチェックします。

http://[host address]:3000/party

注意ですが、あくまでもアプリケーションはホスト側で起動しており、そこから何でも出来てしまうので共有には細心の注意を払って下さい。

Screen Shot 2018-12-07 at 11.34.21 AM.png

この手順を繰り返すことで、REPL を停止することなくソースファイルに編集した内容をコードとしてアプリケーションに反映しながら開発することが出来ます。勿論 REPL を切ったら(切らざるを得ない状況に追い込んだら)負けです。


まとめ

Party REPL によるパーティプログラミングを紹介をしました。ペアプログラミングはこれだけ広まっていますが、起動しているアプリケーションの状態を共有しながら開発を行うという発想は Clojure + REPL のパワーを持ってしないとなかなか生まれないものだと思います。Atom というのが emacs ユーザには若干ネックで、cider などに比べるとまだ機能は少ないですが、新しいペアプログラミングの道を開く素晴らしいツールなので是非お試し下さい。


参考