LoginSignup
15
14

More than 5 years have passed since last update.

Ntmux を用いたマルチリンガル開発

Last updated at Posted at 2014-03-07

百聞は一見に如かず.「能書きはいいから」という向きは, 中程の動画をまずご覧いただければ分かり易いと思う.
一応記事は順を追って説明する.

インタラクティブ開発

Ntmux は端末上で動作するプログラムで, ポート番号の後に続けてコマンドと引数を与えて実行すると, コマンドを端末上で実行するが, 実は指定したポートで接続を待ち受ける TCP サーバがバックグランドで起動しており, このポートに TCP 接続したクライアントが送信してきたバイト列は, 全て端末からの入力のようにコマンドに入力される.

この ntmux を使うと端末上のスクリーンエディタやシェルも操作することができる (詳しくはキッチンシンクの外へ出ようを参照) のであるが, ここでは「テキストエディタで編集中のコード片を ntmux 上のインタプリタに送信して評価結果を確認しながら, コードを編集する」という代表的な使い方を紹介したい.

マルチリンガル開発

その言語のインタープリタサーバが存在し, 使っているエディタ用のインタプリタサーバと通信するプラグインが存在すれば, 上述したインタラクティブ開発は可能であるし, もちろん ntmux を用いた場合よりも各言語の機能を十分に引き出せる. LISP 系の開発者は大抵このようなインタラクティブ開発をおこなっているだろう.

しかし ntmux を使えば, 簡易的なインタラクティブ開発環境を, 言語に依らない統一した方法で準備することができるため

  • 複数言語を頻繁に利用する場合
  • 常用していない言語を試しに使ってみる場合など, あまり環境設定に労力を避けない場合

に非常に有利だ.

vim を用いた例

ntmux はインストールしてあるとする.
以下のファイル ntmux.vim を準備する.

ntmux.vim
function! Send(host, port, msg)
  python <<EOF
import vim
import socket
cs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cs.connect((vim.eval('a:host'), int(vim.eval('a:port'))))
cs.sendall(vim.eval('a:msg'))
cs.close()
EOF
endfunction

function! GetSelected()
  let tmp = @@
  silent normal gvy
  let selected = @@
  let @@ = tmp
  return selected
endfunction

command! -nargs=1 PSend call Send('localhost', <args>, GetSelected())
vnoremap cp <Esc>:PSend port<CR>

このファイルを :source などなんらかの方法で vim にロードしておく.
Python 拡張が入った vim でないと動かない.
拙いコードだが, あくまで例として容赦いただきたい.

:let port = port番号 などで, port を定義しておくと, ヴィジュアルモードで範囲選択して cp とタイプした時に, 指定しておいたポートの TCP サーバに接続し, 選択範囲の文字列を送信する. 別の端末で ntmux port番号 上でインタプリタが動作していれば, それはすなわちそのインタプリタに送った文字列を評価させることができるということになる.

例えばある端末で

% ntmux 5000 irb
irb(main):001:0> 

などのように Ruby インタプリタを起動しておく.

vim の方で適当にテキストを編集し, コード片, 例えば (1..10).map{|x| x * x} のようなコード片を書き, コードの先頭で v を押してビジュアルモードに入ってコード片の末尾に移動, cp とタイプすると, 先の端末の方にコードが送られる.
この時, 送信コード片に改行が含まれるようにすると, すぐに評価させらて,

irb(main):001:0> (1..10).map{|x| x * x}
=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

のように先の端末に評価結果が表示されるだろう.

もちろん他のエディタでも, 機能拡張に使える言語 (vim なら VimScript, Emacs なら Emacs Lisp) に TCP 接続の機能があって, 指定範囲の文字列を送信できさえすれば, 同じことができる.

デモ

デモとして, プロジェクト・オイラー 問題 6 :

10以下の自然数について, 二乗の和と和の二乗の差は
    (1 + 2 + ... 10)2 - (12 + 22 + ... + 102)
      = 552 - 385
      = 3025 - 385
      = 2640
である.
100 以下の自然数について, 二乗の和と和の二乗の差を求めよ.

に対していくつかのプログラミング言語の解法を解説する文書を, それぞれインタープリタで動作確認しながら vim で執筆するという (わざとらしい) デモ動画を撮ってみた.
プログラミング言語の選択が異なる以外, 三つの動画はほとんど同じ内容である. あまり言語が多すぎても冗長なので, 三つずつなるべく似通っていない言語を混ぜた動画を用意した. 興味のある言語が含まれる動画を参照いただきたい.

以下のデモで, 各言語のインタープリタはインストールした後, 一切設定を行っておらず, vim 側でも言語毎の設定は一切行っていない.
Clojure は clojure-1.5.1.jar に CLASSPATH を通しただけ.
ただし, インタープリタがコマンドライン編集機能を提供しない場合, rlwrap を用いている.

Clojure, Haskell and JavaScript (node.js) 版のデモ動画

2014-03-07 3-07 16.44.05.png

OCaml, Common Lisp (sbcl) and Python 版のデモ動画

2014-03-08 3-08 01.59.00.png

Scala, Scheme (Gauche) and Ruby 版のデモ動画

2014-03-10 3-10 00.11.00.png

コード

デモに登場する各言語でのプロジェクト・オイラー問題 6 への解法の例を以下に掲載しておく.
インタプリタに評価させるコード片であり, 単体の実行可能ファイルになるようには考慮されていない.
あくまで例示なので, map や reduce に関数リテラルを渡す練習といった書き方にしている. 書き方は, どの言語でもー議論成り立つだろう.

Clojure

pep6.clj
(- (#(* % %) (apply + (range 1 101)))
   (apply + (map #(* % %) (range 1 101))))

Haskell

pep6.hs
((\x -> x * x) $ foldl1 (+) [1..100])
  - (foldl1 (+) $ map (\x -> x * x) [1..100])

JavaScript

pep6.js
function range(s, n) {
  var r = [], i;
  for (i = s; i < n; i++)
    r.push(i);
  return r;
}

((function(x){return x * x;})(
   range(1, 101).reduce(function(a, b){return a + b;}))
 -
 range(1, 101).map(function(x){return x * x;}
   ).reduce(function(a, b){return a + b;}))

OCaml

pep6.ml
let range s e =
  let rec f r s e = if s = e then r else f (s::r) (s + 1) e
  in List.rev @@ f [] s e;;

let foldl1 f xs = match xs with x::xs -> List.fold_left (+) x xs;;

((fun x -> x * x) @@ foldl1 (+) @@ range 1 101)
  - (foldl1 (+) @@ List.map (fun x -> x * x) @@ range 1 101);;

Common Lisp

pep6.cl
(defun range (s e)
  (loop for i from s to (1- e) collect i))

(- (funcall (lambda (x) (* x x)) (apply #'+ (range 1 101)))
   (apply #'+ (mapcar #'(lambda (x) (* x x)) (range 1 101))))

Python

pep6.py
((lambda x: x * x)(reduce(lambda a, b: a + b, range(1, 101)))
  - reduce(lambda a, b: a + b, map(lambda x: x * x, range(1, 101))))

Scala

pep6.scala
(((x:Int) => x * x)(List.range(1, 101).reduceLeft(_ + _))
  - List.range(1, 101).map(x => x * x).reduceLeft(_ + _));

Scheme

pep6.scm
(use srfi-1)
(define (fold1 f s) (fold f (car s) (cdr s)))
(- ((lambda (x) (* x x)) (fold1 + (iota 100 1)))
   (fold1 + (map (lambda (x) (* x x)) (iota 100 1))))

Ruby

pep6.rb
(1..100).inject(:+) ** 2 \
  - (1..100).map{|x| x * x}.inject(:+)

Conclusion

Ntmux を用いると, 複数のプログラミング言語での開発において,

  • 端末で動作する各言語のインタプリタがあれば, 同一のエディタや IDE のインスタンス内部で動作していなくても,
  • それぞれの言語のインタプリタサーバを使わず ntmux のみを用い,
  • 開発環境上に, それぞれの言語のプラグインではなく, 一つのプラグインのみを導入して

インタラクティブな開発が行える.

課題

  • ntmux
    • ntmux から TCP クライアントへのレスポンスのルール決めと実装
    • IPv6 対応
    • MacOS X 以外のプラットフォームへの対応や動作確認
  • vim プラグイン
  • emacs プラグイン

など.

求むご意見・ご協力.

15
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14