UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

EmacsLispでBrainfuckのMajor Modeとインタプリタを作る

こんにちは、最近 Ubuntu 20.04 から Manjaro 19.0 に乗り換えた @takeokunn です。

非常にサクサク動くようになって快適になりました。Linux詳しくなりたいです。

はじめに

brainfuckは実行できる命令が8種類しかないシンプルな言語です。brainfuckの記事は世の中に転がっているのでそちらを参照ください。

知り合いのエンジニアとガストで喋ってたときに「emacs詳しくなりたいならbrainfuckとか良いんじゃね?作ろうよー」と言われたので作ってみました。

できたもの

repo: https://github.com/takeokunn/brainfuck.el

f:id:bararararatty:20200324160118p:plain

  • syntax highlightできるようにした
  • tokenのdocをmodelineに出すようにした
  • interpreterを作ってbuffer内のstringを取得して結果を出すようにした

Major Mode

(defvar bf-syntax-table
  (let ((table (make-syntax-table)))
    (modify-syntax-entry ?\" "." table)
    table))

;;;###autoload
(define-derived-mode brainfuck-mode prog-mode "Brainfuck"
  :syntax-table bf-syntax-table
  (bf--add-keywords)
  (bf--help-doc-fun))

;;;###autoload
(add-to-list 'auto-mode-alist '("\\.bf" . brainfuck-mode))

(defvar brainfuck-mode-map nil "Keymap for brainfuck-mode.")

(defun bf--add-keywords ()
  (font-lock-add-keywords
   nil
   (list (cons (rx (any "[" "]")) font-lock-keyword-face)
         (cons (rx (any ">" "<" "+" "-" "." ",")) font-lock-function-name-face)
         (cons (rx (not (any "[" "]" ">" "<" "+" "-" "." ","))) font-lock-comment-face))))

今回は prog-mode から派生させてみました。

token 8種類をハイライトするだけなので、 keyword を正規表現でmatchさせて [ ] はkeyword, 他6種類のtokenは function, それ以外の文字列は comment として表示するように書いてみました。

DocをModeLineに表示

(defun bf--help-sym-called-at-point ()
  (unless (eobp)
    (buffer-substring-no-properties (point) (1+ (point)))))

(defun bf--help-lookup-doc (sym)
  "Return document string for SYM."
  (pcase sym
    (">" "Increment the pointer.")
    ("<" "Decrement the pointer.")
    ("+" "Increment the value indicated by the pointer.")
    ("-" "Decrement the value indicated by the pointer.")
    ("." "Print the value indicated by the pointer.")
    ("," "Read one byte from input and store it in the indicated value.")
    ("[" "Jump to the matching `]' if the indicated value is zero.")
    ("]" "Jump to the matching `[' if the indicated value is not zero.")))

(defun bf--help-summerize-doc (sym doc)
  (concat sym " : " (car (split-string doc "[\n\r]+"))))

(defun bf-help-minibuffer-help-string ()
  (interactive)
  (let* ((sym (bf--help-sym-called-at-point))
         (doc (when sym (bf--help-lookup-doc sym))))
    (when doc (bf--help-summerize-doc sym doc))))

(defun bf--help-doc-fun ()
  (make-local-variable 'eldoc-documentation-function)
  (setq eldoc-documentation-function
        'bf-help-minibuffer-help-string))

自分の現在のカーソルの文字を取得し、それにあたるdocを取得、 eldoc に流し込むように実装しました。

インタープリタと実行処理

(defun bf-interpreter (input)
  (interactive)
  (let* ((input-list (-map #'char-to-string (coerce input 'list)))
         (ptr 0)
         (mem (make-vector 30000 0))
         (braces (make-vector (length input-list) 0))
         (braces-stack '()))
    (dotimes (outer (length input-list))
      (if (string-equal (nth outer input-list) "[")
          (let ((cnt 0))
            (progn
              (do ((inner 0 (1+ inner)))
                  ((< (length (nthcdr outer input-list)) inner))
                (cond ((string-equal (nth (+ outer inner) input-list) "[") (push t braces-stack))
                      ((string-equal (nth (+ outer inner) input-list) "]") (pop braces-stack)))
                (if (zerop (length braces-stack))
                    (setq inner (length (nthcdr outer input-list)))
                    (incf cnt)))
              (aset braces outer (+ outer cnt))
              (aset braces (+ outer cnt) outer)))))
    (do ((index 0 (1+ index)))
        ((< (length input-list) index))
      (pcase (nth index input-list)
        (">" (incf ptr))
        ("<" (decf ptr))
        ("+" (aset mem ptr (incf (aref mem ptr))))
        ("-" (aset mem ptr (decf (aref mem ptr))))
        ("." (princ (char-to-string (aref mem ptr))))
        ("," (aset mem ptr (read-char)))
        ("[" (if (zerop (aref mem ptr)) (setq index (incf (aref braces index)))))
        ("]" (unless (zerop (aref mem ptr)) (setq index (aref braces index))))))))

(defun bf-exec ()
  (interactive)
  (let ((str (buffer-string)))
    (bf-interpreter str)))

Loopの処理(括弧の対応)が非常に大変だったが、 JavascriptでBrainfuckのインタプリタを実装してみた。 を参考にしたらいけた。

M-x bf-exec と叩くとバッファ内の文字列を取得し、インタープリタで処理をし、結果を吐き出すようにしました。

参考サイト

終わりに

次はEmacsLispでC compilerを作りたいです。

弊社ではemacsのpluginを作れるプログラマを募集しています。

www.wantedly.com

UUUM攻殻機動隊に入隊して

システムユニットの5n4wasa6です。

UUUM攻殻機動隊に入隊して1ヶ月立ちましたので、定例(予定)の1ヶ月経過blogを書きます。

UUUMは現在、中期IT計画というPJが進行しており、私はその開発にjoinしております。 開発で使用している言語は主に、Ruby on Rails,Vue.jsでインフラタスクにも関わっています。

エンジニアとしての経歴は社内で1番短いですが、やりたいと言えばやらせてもらえる環境で日々楽しみながら働いております。

今回は、UUUM攻殻機動隊について3本建てで書いていきます。

  1. UUUM攻殻機動隊に入隊して感じたこと
  2. サービスの紹介 (一部)
  3. 社内勉強会の様子

はじめに

先ほど記載した通り、私は2019年2月にUUUMにjoinしました。

前職では、マイクロサービスの開発とインフラ周りの構築や移行などやっておりました。

その前は、通信会社で法人営業をやっておりましたので、エンジニアとしてはかなり後発であり気合いを入れて日々精進しております。

1. UUUM攻殻機動隊に入隊して感じたこと

現在、隊員は約30名おり、日々サービス開発に勤しんでおります。

UUUM攻殻機動隊に入隊して感じたことは、めっちゃ働きやすい環境であること。

技術やサービスベースで気兼ねなく議論しており、スピード感が非常に早い点が大きな要因だと思います。

また、後述しますが、勉強会が非常に充実しており、技術への追求が素晴らしいです。

2週間に1,2度程のペースで開催されており、私の1番楽しみにしている時間です。

ジャンル問わず開催されておりますが、最近はvim熱が非常に高いです。

詳しくはこちらをご参照ください。 system.blog.uuum.jp system.blog.uuum.jp

2. サービスの紹介 (一部)

UUUMはYoutuberのマネジメント会社であることは、ご存知の方もいるかと思うのですが、 そこで働くエンジニアがどんなことをしているかを想像することは難しいと思います。

私も、UUUMに興味を持った際に、実際にエンジニアがどんな業務をしているのか容易には想像できませんでした。 ※攻殻機動隊blogの存在を知らなかった...というただの情弱なのですが。。

そこで、サービスの一部紹介したいと思います。 Youtuberさんのマネジメント会社であるため、Youtuberさん向けやそれを管理する社内システムが多いです。

[クリエイター(youtuberさん等)向け各種サービス]

・クリエイターのサポートツール ・クリエイターの情報管理ツール ・各種データ分析ツール

[中期IT計画]

・社内管理システム こちらは私がJOINしているPJで、上記のサービスやデータを一元管理できる社内管理システムになります。

[MUUU]

UUUM公式オンラインストア

Youtuberさんのグッズなどを販売するオンインストアサービス

[レモネード]

Instagram特化型インフルエンサーマーケティングツール

インフルエンサーの選定、依頼、関係性構築、パフォーマンス分析、コンテンツ管理など 全ての作業を1つの自動ツールで簡単に実施できるサービスになります。

レモネードの技術詳細につきましては、こちらをご参照頂ければと思います。

社内,youtuberさん向けサービスが主で、説明し切れていない部分もございますが、 是非UUUM攻殻機動隊に入隊いただき、実態を解明頂ければと思います。

3. 社内勉強会の様子

前述の通り、UUUM攻殻機動隊は非常に勉強会が充実している。

勉強会では、業務効率化のための設定やネットワークの基礎、暗号化/復号化など様々なジャンルで開催されている。

今回は、勉強会の派生で展開した、ターミナル操作効率化のための各種設定について紹介します。 恥ずかしながら、dotfilesも作成していなかった私ですが、諸先輩方に教えていただきながら支給されたPCを進化させたので記します。

dotfilesについては、何度となく紹介されております 汎用性・拡張性の高い dotfiles 環境を作る を参考にした諸先輩のdotfilesを参考に自前で作成(ほぼ~パクリ~)しました。 今回はその中のいくつかの設定をご紹介します。

tmux

tmuxとは、端末多重化ソフトウェアです。 これまで、ターミナルでタブを複数表示したり、複数ターミナルを立ち上げており非常に不便でしたが、tmuxを導入することでこのような煩わしさは解消。 要するに、1つのターミナル上で複数のターミナルを立ち上げて同時に作業を可能にしてくれる便利なやつです。

tmuxの導入は非常に簡単

brew install tmux

あとは、~/.tmux.confこちらを参考に設定すれば便利なターミナル操作が可能になります。 mouse操作をonにすることをお勧めします。

# マウス操作を有効にする
set-option -g mouse on
bind -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e'"

tmuxの操作方法もチートシート集がございますので安心。

かく言う私は、優しい優しい先輩方にあれこれ聞いて操作を習得しました。笑

peco

pecoとは、一覧結果のインクリメンタルサーチ(入力に応じて候補を絞り込む検索方法)を実現してくれるツールです。

pecoの導入も非常に簡単です。

brew install peco

あとは、バインドキーを利用したコマンドの履歴検索にpecoを使う設定をしましょう(bash,zshユーザー)。 カスタマイズして自分に合ったpecoちゃんにすることが可能です。 「履歴降順」「重複削除」がポピュラーでしょうか。

ghq

ghqとは、リポジトリを一元管理してくれるツールです。

ghqの導入も簡単

brew install ghq

ghq rootでghqのルートディレクトリを返してくれ、 ghq listでghqルートディレクトリ以下のGitリポジトリ一覧を表示できます。 上記のpecoと組み合わせると非常に便利になります。

hub

hubとは、CLIで作業中リポジトリのURLをコマンド一発で開けるようになるツール。

hubの導入も簡単

brew install hub

peco, ghq, (hub)を組み合わせることで、リポジトリ一覧をインクリメンタルサーチして、 選択したリポジトリに移動することができるため、非常に便利で業務効率化につながります。

ghq list | peco
cd $(ghq root)/$(ghq list | peco)

参考サイトのようにエイリアスを貼ってより便利に使用できます。

alias g='cd (ghq root)/(ghq list | peco)'
alias gh='hub browse (ghq list | peco | cut -d "/" -f 2,3)'

fish

fishとは、インタラクティブシェルの1つで補完機能が便利なやつです。 ユーザフレンドリーであり、エラーメッセージが親切でコマンド入力時にコマンドに色を付けてくれる親切なやつです。

fishの導入

brew install fish

bash or zshからfishに変更が必要なためこちらを参考に。

fishに乗り換えて良い点は、補完機能が優秀で操作スピードが格段に上がっていることです。

終わりに

弊社では、自作dotfilesの自慢のできるプログラマを大募集しています。 www.wantedly.com

vimサークル活動報告 #2

システムユニットのxxuxa_kです。ついにUUUMでの生活も3年目に入りました。今日もvim活動報告です。普段使っているneovimの設定について書きたいと思います。たぶん5億番煎じくらいの内容になると思います。前回分もぜひご覧ください。

system.blog.uuum.jp

プラグインと設定

プラグイン管理にはvim-plugを使っています。インストールやアップデートが並列で走るので早いのとコマンドがシンプルなのがとてもいいです。使っているプラグインと設定を一部紹介していきます。

GitHub - junegunn/vim-plug: Minimalist Vim Plugin Manager

Denite

github.com

Deniteはありとあらゆる検索をやってくれるプラグインでDark poweredシリーズの一つです。前回の活動報告にもありますがこれがなくてはもはやvim生活は成り立ちません。まさにDark powerです。denite本体に合わせてgit関連のdenite sourceを追加してくれるプラグインも合わせて入れています。

Plug 'Shougo/denite.nvim', { 'do': ':UpdateRemotePlugins' }
Plug 'chemzqm/denite-git'

Deniteを起動するkeymapをいくつか設定していますが実際に使うのはやはりfile/recとlineが圧倒的に多いです。Deniteはsourceを複数与えることができるのでfile/recとlineを一度に指定するkeymapもつけていますが、あんまり使わないですね。ここに挙げているsource以外を見たいときはDeniteでsourceを検索してそのまま見つけた別のsourceで検索しています。(実はこれだけでいいんじゃね?と思ったりします。)ちなみにLeaderにはスペースキーを割り当てているのですが大きくて打ちやすいのが好みです。

nnoremap <Leader>f :Denite file/rec<CR>
nnoremap <Leader>l :Denite line<CR>
nnoremap <Leader>e :Denite file/rec line<CR>
nnoremap <Leader>h :Denite defx/history<CR>
nnoremap <Leader>g :Denite grep<CR>
nnoremap <Leader>b :Denite buffer<CR>
nnoremap <Leader>m :Denite menu<CR>
nnoremap <Leader>o :Denite outline<CR>
nnoremap <Leader>u :Denite source<CR>
nnoremap <Leader>c :Denite gitstatus<CR>

次に自動コマンドの設定です。DeniteはdeniteというFileTypeをもっているので、Deniteのバッファにいるときはこのkeymapが有効になります。iキーを押すとfilterが開始されますが、filterを終了したいときにCtrl-oが使えるというようになっています。

autocmd FileType denite call s:denite_my_settings()
function! s:denite_my_settings() abort
  nnoremap <silent><buffer><expr> <CR> denite#do_map('do_action')
  nnoremap <silent><buffer><expr> d denite#do_map('do_action', 'delete')
  nnoremap <silent><buffer><expr> p denite#do_map('do_action', 'preview')
  nnoremap <silent><buffer><expr> q denite#do_map('quit')
  nnoremap <silent><buffer><expr> i denite#do_map('open_filter_buffer')
  nnoremap <silent><buffer><expr> <Space> denite#do_map('toggle_select').'j'
endfunction

autocmd FileType denite-filter call s:denite_filter_my_settings()
function! s:denite_filter_my_settings() abort
  imap <silent><buffer><C-o> <Plug>(denite_filter_quit)
endfunction

1つおすすめの設定を書いておきます。↑のようにkeymapを設定すればDeniteのバッファからはiキーで検索を開始できますが、start_filter: v:trueを指定することでDeniiteを開くと同時にfilterが開始されます。filter時のプロンプトも好きなものに変えられるようになっています。

call denite#custom#option('default', {
      \ 'split': 'horizontal',
      \ 'highlight_filter_background': 'DeniteFilter',
      \ 'prompt': '> ',
      \ 'start_filter': v:true
      \})

最後にDeniteのmenuに関する設定です。Deniteのmenuについてはがんばってhelp読んではいるんですがまだ理解できておらず、使い道を探っているところです。とりあえず今できるとわかっていることは

  • 事前に用意したコマンドを実行する
  • 特定のファイルを開く

の2つです。bundle exec ...系のコマンド追加と、dotfileが開けるようにしました。追加はしたものの普段はtmux起動してpaneとかwindowを移動しながらやっているので今後もあまり使わない気がします。そもそもこういうのはDeniteに求める機能ではないですね。

let s:menus = {}
let s:menus.rails = {
      \ 'description': 'Commands in Rails project',
      \ 'command_candidates': [
        \ ['install gems', '!bundle install'],
        \ ['execute tests', '!bundle exec rails test'],
        \ ['rubocop', '!bundle exec rubocop'],
        \ ['rubocop auto-correct', '!bundle exec rubocop --auto-correct'],
        \ ['slim-lint', '!bundle exec slim-lint app/**/*.slim'],
      \ ]
      \ }
let s:menus.dotfiles = {
      \ 'description': 'Edit dotfiles',
      \ 'file_candidates': [
        \ ['neovim', '~/.config/nvim/init.vim'],
        \ ['vim', '~/.vimrc'],
        \ ['tmux', '~/.tmux.conf'],
        \ ['zshrc', '~/.zsh/.zshrc'],
      \ ]
      \ }
call denite#custom#var('menu', 'menus', s:menus)

Defx

github.com

またしてもDark poweredシリーズです。Defxはファイルブラウザです。Deniteと同様にsourceを対象に起動するという作りになっていて、Deniteと同じように設定ファイルがかけます。(ただしDefxはDeniteに依存しておらず単体で動きます)

Plug 'Shougo/defx.nvim', { 'do': ':UpdateRemotePlugins' }

keymapはこれだけです。defx導入前はNERDTreeを使っていた(今も入ってはいる)んですが全く使わなくなりました。

nnoremap <silent>,d :Defx<CR>

私は普段ファイルを指定してneovimを起動するということをしないので、プロジェクトのルートに移動したらすぐnvimとかnvとかを引数なしで叩いてneovimを起動します。(nnvnviを全部nvimのaliasにしています。)その際にdefxが自動で起動するようにしているという設定です。

autocmd VimEnter * if argc() == 0 && !exists("s:std_in") | Defx | endif

DefxもDeniteと同様にdefxのバッファにdefxという独自のFileTypeがあるので、自動コマンドで専用のkeymapを割り当てることができます。ほぼDefxのhelpの内容そのままです。スペースキーで複数のファイルを選択して一気に開けるのはたまに嬉しいときがあります。Defxも便利なんですがほとんどの場合Deniteで済むのでめちゃくちゃ活用できているという感じではないです。

autocmd FileType defx call s:defx_my_settings()
function! s:defx_my_settings() abort
  nnoremap <silent><buffer><expr><CR> defx#do_action('open')
  nnoremap <silent><buffer><expr> c defx#do_action('copy')
  nnoremap <silent><buffer><expr> m defx#do_action('move')
  nnoremap <silent><buffer><expr> p defx#do_action('paste')
  nnoremap <silent><buffer><expr> v defx#do_action('open', 'vsplit')
  nnoremap <silent><buffer><expr> i defx#do_action('open', 'split')
  nnoremap <silent><buffer><expr> o defx#do_action('open_or_close_tree')
  nnoremap <silent><buffer><expr> O defx#do_action('open_tree_recursive')
  nnoremap <silent><buffer><expr> x defx#do_action('close_tree')
  nnoremap <silent><buffer><expr> P defx#do_action('open', 'pedit')
  nnoremap <silent><buffer><expr> K defx#do_action('new_directory')
  nnoremap <silent><buffer><expr> N defx#do_action('new_file')
  nnoremap <silent><buffer><expr> M defx#do_action('new_multiple_files')
  nnoremap <silent><buffer><expr> C defx#do_action('toggle_columns', 'mark:indent:icon:filename:type:size:time')
  nnoremap <silent><buffer><expr> S defx#do_action('toggle_sort', 'time')
  nnoremap <silent><buffer><expr> d defx#do_action('remove')
  nnoremap <silent><buffer><expr> r defx#do_action('rename')
  nnoremap <silent><buffer><expr> ! defx#do_action('execute_command')
  nnoremap <silent><buffer><expr> ee defx#do_action('execute_system')
  nnoremap <silent><buffer><expr> yy defx#do_action('yank_path')
  nnoremap <silent><buffer><expr> . defx#do_action('toggle_ignored_files')
  nnoremap <silent><buffer><expr> ; defx#do_action('repeat')
  nnoremap <silent><buffer><expr> b defx#do_action('cd', ['..'])
  nnoremap <silent><buffer><expr> ~ defx#do_action('cd')
  nnoremap <silent><buffer><expr> q defx#do_action('quit')
  nnoremap <silent><buffer><expr> <Space> defx#do_action('toggle_select') . 'j'
  nnoremap <silent><buffer><expr> * defx#do_action('toggle_select_all')
  nnoremap <silent><buffer><expr> j line('.') == line('$') ? 'gg' : 'j'
  nnoremap <silent><buffer><expr> k line('.') == 1 ? 'G' : 'k'
  nnoremap <silent><buffer><expr> <C-l> defx#do_action('redraw')
  nnoremap <silent><buffer><expr> <C-g> defx#do_action('print')
  nnoremap <silent><buffer><expr> cd defx#do_action('change_vim_cwd')
endfunction

vimdoc-ja

github.com

vimだったりneovimだったりvimscriptだったりを理解するスピードが最近上がってきたなと思うんですが、完全にこれのおかげです。おそらくvimでもneovimでも特定の内容についてhelpを引くと英語版の内容がヒットすると思いますが、これを日本語版で出せるようになるというものです。インストールしてset helplang=jaするだけで日本語のhelpがある内容であれば日本語で出してくれます。一度参加したゴリラ.vimで教えていただきました。翻訳をされた皆様には本当に感謝しかありません。 普段vimのhelp引いてないっていう方はぜひ使ってください。Deniteのline検索と一緒に使えばスラスラ読めます。

gorillavim.connpass.com

vim-lsp

github.com

さて最後にLsp周りです。私は特に理由はないのですがvim-lspを使っています。(というか他のものを使ったことがありません。)非同期補完にはまたしてもDark poweredシリーズdeoplete.nvimを使っています。 mattnさんのvim-lsp-settingsも入れているのですが:LspInstallServerしたあとの挙動がどうにも?となることが多かったので最近はあまり使っておらずvim-lspのwikiを見て1つずつLanguage Serverをセットするようにしています。

Plug 'prabirshrestha/async.vim'
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
Plug 'lighttiger2505/deoplete-vim-lsp'
Plug 'mattn/vim-lsp-icons'
Plug 'ryanolsonx/vim-lsp-typescript'

Language Server設定の例です。すべてに共通して実行できるLanguage Serverがあれば自動コマンドを実行してregister_serverするという形式になっています。これはvim-lspのwikiに書いてある内容をそのまま実行すればすぐに使えるようになります。とはいえwikiだけでは設定がうまくいかないこともあってGroovy用のLanguage Serverがその例です。設定方法をQiitaに書いたのでこちらも良ければ見てください。

qiita.com

if executable('solargraph')
  augroup LspRuby
    autocmd!
    autocmd User lsp_setup call lsp#register_server({
          \ 'name': 'solargraph',
          \ 'cmd': {server_info->[&shell, &shellcmdflag, 'solargraph stdio']},
          \ 'initialization_options': {"diagnostics": "true"},
          \ 'whitelist': ['ruby'],
          \ })
  augroup END
endif

if executable('vim-language-server')
  augroup LspVim
    autocmd!
    autocmd User lsp_setup call lsp#register_server({
          \ 'name': 'vim-language-server',
          \ 'cmd': {server_info->['vim-language-server', '--stdio']},
          \ 'whitelist': ['vim'],
          \ 'initialization_options': {
          \   'iskeyword': '@,48-57,_,192-255,-#',
          \   'vimruntime': expand($VIMRUNTIME),
          \   'suggest': { 'fromVimruntime': v:true },
          \ }})
  augroup END
endif

if executable('java') && filereadable(expand('$HOME/.lsp/groovy-language-server/build/libs/groovy-language-server.jar'))
  autocmd User lsp_setup call lsp#register_server({
        \'name': 'groovy-language-server',
        \'cmd': {server_info->[
        \  'java',
        \  '-jar',
        \  expand('$HOME/.lsp/groovy-language-server/build/libs/groovy-language-server.jar')
        \]},
        \'whitelist': ['groovy']
        \})
endif

まとめ

こうしてみると私のneovim設定はDark power無しにしては成り立たないものになっているなと思います。Shougoさんの作るプラグインは思想が一貫していて1つ何かがわかると他にも応用できるのがとても美しい作りになっているなと思います。またLspについてですが、もうちょっとなんとかできるよね?という感想を日々持っています。Lspを使ってneovimによりIDE感を出す活動をvimサークルではやっていきたいです。

最後に

我々システムユニットではvimmerもneovimmerも募集しています。興味のある方はぜひ採用サイトを見てみてください。

recruit.uuum.co.jp

Swagger OpenAPIでAPI Referenceを書く

こんにちは、@takeokunn です。

yamlyml の違いってなんだろーと調べたところ Please use “.yaml” when possible. って公式のfaqに書いてあったのでなるべく .yaml を使っていこうと思った今日このごろです。

今回はOpenAPIの実装例について書いていこうと思います。

Dockerで構築

docker-compose.yml はこんな感じ。

version: "3.3"
services:
  swagger-editor:
    image: swaggerapi/swagger-editor
    container_name: "swagger-editor"
    ports:
      - "19881:8080"
  swagger-ui:
    image: swaggerapi/swagger-ui
    container_name: "swagger-ui"
    ports:
      - "19882:8080"
    volumes:
      - ./openapi.yaml:/usr/share/nginx/html/openapi.yaml
    environment:
      API_URL: openapi.yaml
  swagger-api:
    image: stoplight/prism:3
    container_name: "swagger-api"
    ports:
      - "19883:4010"
    command: mock -h 0.0.0.0 /openapi.yaml
    volumes:
      - ./openapi.yaml:/openapi.yaml

swagger-uiswagger-editor は転がってるコードをそのままペタっと貼り付けました。

以前は danielgtaylor/apisprout を使っていたのですが、どうもOpenAPIの最新の仕様に追いついていなかった(要出典)ので今回は stoplight/prism を使いました。

下のサンプルの get: /colors を試しに叩いてみるとこんな感じ。簡単にmockが作れて良いですね。

~/.g/g/t/.emacs.d (*´ω`*) < curl "http://localhost:19883/colors" -H "Authorization: Bearer xxx"
{"message":200,"colors":[{"id":1,"name":"赤","created_at":"2002/05/12 20:30:15","updated_at":"2002/05/12 20:30:15"}]}

実際に openapi.yaml を書いてみる

Specifitcation とにらめっこして書けば大体できます。

試しに2つのendpointを作ってみました。

  • get: /colors: 色を取得する
  • post: /colors: 色を作成する

Bearer token を必要とする場合でも簡単に記述てきてとても良いです。

openapi: 3.0.5
info:
  title: OpenAPIテスト
  version: 1.0.0
  description: OpenAPIテスト
servers:
  - url: http://localhost:3000/api/v1
    description: Local server
  - url: https://staging.test.tokyo/api/v1
    description: Staging server
security:
  - bearerAuth: []
paths:
  /colors:
    get:
      summary: Get colors
      description: 色一覧を取得
      responses:
        200:
          description: 色一覧を返す
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: 200
                  colors:
                    type: array
                    items:
                      $ref: '#/components/schemas/ColorModel'
    post:
      summary: Create color
      description: 色を作成
      requestBody:
        content:
          application/json:
            schema:
              required:
                - name
              properties:
                name:
                  type: string
                  example:responses:
        201:
          description: 作成した色を返す
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Success
                  color:
                    type: object
                    $ref: '#/components/schemas/ColorModel'
        400:
          description: 作成エラー
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Failure
components:
  schemas:
    ColorModel:
      type: object
      properties:
        id:
          type: integer
          example: 1
          description: primary id
        name:
          type: string
          example:description: 名前
        created_at:
          type: string
          example: 2002/05/12 20:30:15
          description: 作成日
        updated_at:
          type: string
          example: 2002/05/12 20:30:15
          description: 作成日
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API Key

emacsで快適に openapi.yaml を書く

emacsで快適にyamlを書くためにいくつかpluginを入れました。

openapi-yaml-mode

url: magoyette/openapi-yaml-mode

MELPAで公開されていないが、顧客が欲しかった感じのEmacsLispが書かれているので導入した。completionや簡易的なhighlightなどが入っている。

f:id:bararararatty:20200225222634p:plain
openapi

;; cask
(depends-on "openapi-yaml-mode" :git "https://github.com/magoyette/openapi-yaml-mode.git")

;; config
(use-package openapi-yaml-mode
  :mode (("\\openapi.yaml$" . openapi-yaml-mode))
  :config
  (setq openapi-yaml-use-yaml-mode-syntax-highlight t))

DarthFennec/highlight-indent-guides

url: DarthFennec/highlight-indent-guides

highlight-indent-guides

yaml のインデントを可視化してくれるplugin。以下のようにconfigを書いた。

(use-package highlight-indent-guides
  :diminish
  :hook
  ((prog-mode yaml-mode) . highlight-indent-guides-mode)
  :custom
  (highlight-indent-guides-auto-enabled t)
  (highlight-indent-guides-responsive t)
  (highlight-indent-guides-method 'character))

終わりに

openapi.yaml 職人の方は是非弊社で一緒にyamlをかきましょう!!!

www.wantedly.com

vimサークル活動報告 #1

こんにちは、弊社唯一のemacsユーザの @takeokunn です。

最近ブログを書いていなかったのでそろそろやらねば...ということで筆を取りました。

はじめに

UUUMには 会社公認サークル #circle-vim というものがあります。

サークルメンバーはvim歴が浅い人が多く約10人(幽霊含み)いて、日々 BTO会長 を中心に素vim力を高めるべく精進をしていました。

が、BTO会長の会社卒業を期にneovimで闇の力を得ようと様々なpluginを突っ込んで新たな力を得ようという方針になってきているので、今回はその活動報告をしていこうと思います。

neovimを使えるようにする

homebrewでサクッと入ります。

$ brew install --HEAD neovim

ちなみに、僕はubuntuユーザなので自前でbuildをしました。

$ ghq get https://github.com/neovim/neovim
$ cd ~/.ghq/github.com/neovim/neovim
$ make -j
$ sudo make install

dotfilesを整える

我らがvimサー会長BTOさんの 汎用性・拡張性の高い dotfiles 環境を作る という記事を参考に皆各々dotfilesを作っています。

僕はvimとnvimを以下のように使い分けて管理しています

  • vim: サーバに入って設定ファイルを弄る用、ほぼ素vim
  • neovim: ガッツリカスタマイズして日常使いを目指す

plugin紹介

swoop

url: pelodelfuego/vim-swoop

言わずと知れた検索/置換ライブラリ。emacsでは愛用していたのでnvimでも是非入れたかった逸品。

swoop公式画像

vim-swoopでは検索し終わった後にbufferを消してくれなかったのでしょうがなくkeybindにdeleteする処理を追加しました。

また、現在のカーソルの文字列を取得して検索する自前の関数を作りました。

function! MySwoop()
  let word = expand('<cword>')
  if len(word) > 0
    call SwoopPattern(word)
  else
    call Swoop()
  end
endfunction

nnoremap <silent> ,s :call MySwoop()<CR>
nnoremap <silent> ,q :bdelete! swoopBuf<CR>

EmacsLispだとこんな感じ

(use-package swoop
  :config
  (setq swoop-minibuffer-input-dilay 0.4)
  (defun my/swoop-from-isearch ()
    (interactive)
    (let* ((symbol (thing-at-point 'symbol 'no-properties)))
      (swoop symbol))))

(define-key ivy-mode-map (kbd "C-o") 'my/swoop-from-isearch)

vim-multiple-cursors

url: terryma/vim-multiple-cursors

terryma/vim-multiple-cursors

みんな大好きmultiple cursor。良いですね大好きです。

emacsだと smartrepmultiple-cursors を組み合わせてやるしかなくて結構たいへんです。

(use-package multiple-cursors
  :init
  (require 'smartrep)
  (declare-function smartrep-define-key "smartrep")
  (bind-key "C-M-c" 'mc/edit-lines)
  (bind-key "C-M-r" 'mc/mark-all-in-region)
  (global-unset-key (kbd "C-t"))
  (smartrep-define-key global-map "C-t"
    '(("C-t" . 'mc/mark-next-like-this)
      ("n"   . 'mc/mark-next-like-this)
      ("p"   . 'mc/mark-previous-like-this)
      ("m"   . 'mc/mark-more-like-this-extended)
      ("u"   . 'mc/unmark-next-like-this)
      ("U"   . 'mc/unmark-previous-like-this)
      ("s"   . 'mc/skip-to-next-like-this)
      ("S"   . 'mc/skip-to-previous-like-this)
      ("*"   . 'mc/mark-all-like-this)
      ("d"   . 'mc/mark-all-like-this-dwim)
      ("i"   . 'mc/insert-numbers)
      ("o"   . 'mc/sort-regions)
      ("O"   . 'mc/reverse-regions))))

Shougo/denite.nvim

url: Shougo/denite.nvim

denite.nvim

最高のPlugin、とにかく最高のplugin。顧客が求めていた全てが詰まっている最高のPluginです。

bufferの検索、project内grep、file検索、outline検索など欲しかったものを全て提供してくれています。

「これがあればfzf要らないんじゃね?」と思ったが、共存の道を探っている部員もいます。

" config
autocmd FileType denite call s:denite_my_settings()
function! s:denite_my_settings() abort
  nnoremap <silent><buffer><expr> <CR>
        \ denite#do_map('do_action')
  nnoremap <silent><buffer><expr> d
        \ denite#do_map('do_action', 'delete')
  nnoremap <silent><buffer><expr> p
        \ denite#do_map('do_action', 'preview')
  nnoremap <silent><buffer><expr> q
        \ denite#do_map('quit')
  nnoremap <silent><buffer><expr> <Esc>
        \ denite#do_map('quit')
  nnoremap <silent><buffer><expr> i
        \ denite#do_map('open_filter_buffer')
  nnoremap <silent><buffer><expr> <Space>
        \ denite#do_map('toggle_select').'j'
endfunction

" keybind
nnoremap <silent> ,k :Denite file/rec<CR>
nnoremap <silent> ,b :Denite buffer<CR>
nnoremap <silent> ,o :Denite outline<CR>
nnoremap <silent> ,r :Denite file/old<CR>
nnoremap <silent> ,h :Denite command_history<CR>
nnoremap <silent> ,g :Denite grep<CR>

emacsだと abo-abo/swiper がこれに当たるのですが、こういうインターフェースで提供できたら最高だなって思わされました。

終わりに

「emacsのこのpluignや機能、nvimでもほしいな」「nvimのこのpluignや機能、emacsでもほしいな」といった感じでお互いを高めあえれば最高だと思いました。

dark poweredシリーズやLSPなどの設定をもっともっとやって快適なvimライフを送りたいです。

弊社ではvim/neovimを完全に理解しているプログラマを募集しています。

www.wantedly.com

UUUM System Meetup #2

f:id:chibiProgrammer:20191224133138j:plain こんにちは、エンジニアの中村です。

12月13日にUUUM System Meet Up#2を開催したので、その様子を書いてみました!

今回も前回に引き続き沢山の人がきてくださいました。

前回よりもかなり盛り上がってとてもいい会になりました。

今回のイベントは、社内外の技術者たちと交流を深めたいということと、 社外の人に自社のシステムを紹介しようという目的の元開催いたしました!

今回のイベントの発表内容はこちらです。 今回は元UUUMエンジニアのnazoさんをお呼びして、発表していただきました!

発表者 役職 タイトル
obara_t エンジニア UUUMエンジニアの日常
ishihara_k エンジニア UUUM攻殻機動隊に入隊しました
fujisawa_y エンジニア CREAS
nazo エンジニア レガシープロダクトを改善していくための戦い方

最初の会社説明の後に、 obara_tさんの発表「UUUMエンジニアの日常」

f:id:chibiProgrammer:20191224134605j:plain
UUUMエンジニアの日常

obara_tさんは、社内システムについてと、UUUMエンジニアの日常を紹介してくださいました。 技術選定の話や、技術的なチャレンジは責任持ってやってくれればOKなど、自由なシステムの雰囲気を紹介してくださいました。

社内外で勉強会もよくしていて、 直近だと「お前らはDockerを1mmも理解していない」という勉強会が開かれ、 ピザパや焼肉をみんなで食べに行ったりなど、とても楽しそうな雰囲気が参加者に伝わったように思います。

次は、 ishihara_kさんの「UUUM攻殻機動隊に入隊しました」

f:id:chibiProgrammer:20191224141316j:plain
UUUM攻殻機動隊に入隊しました

ishihara_kさんはUUUMに入ってまだ2ヶ月目ですが、入ってすぐの目線で見たシステムについて話してくださいました。

ishihara_kさんの1つ前の会社がとてもブラックで、1年で300日オフィスで寝泊まりで作業していたという話を聞いて恐ろしいなぁと思いました。

UUUMはめちゃめちゃホワイトなので安心してくださいね!

次に、fujisawa_yさんの「UUUMと開発の話」

f:id:chibiProgrammer:20191224160429j:plain
UUUMと開発の話

fujisawa_yさんはCREASという、UUUMクリエイター限定サイトの紹介と、エンジニア×YouTuberについて発表してくださいました。

CREASを支える技術や、実際の作業内容を話してくださいました。CREASについて詳しく知りたい方は、「UUUM限定サイト」と検索してみてください!

また、エンジニア×YouTuberの話では、Githubのフォロワー数7位はエンジニアであったり、海外ではエンジニアでYouTuberの人のチャンネルがとても伸びていることを初めて知りました。

最後は、nazoさんの「レガシープロダクトを改善していくための戦い方」

f:id:chibiProgrammer:20191224145532j:plain
レガシープロダクトを改善していくための戦い方

nazoさんは元UUUMエンジニアの方で、初期からずっとシステムを支えてくださった方です(とてもすごい人です)。

今回は、レガシーコードとの向き合い方・心構えについて発表してくださいました。

レガシープロダクトを悪い状態を続けないために、CI/CDの設備、Lintの導入、環境構築をしやすい状態にしておくなど、 気をつけておかないといけない部分を紹介してくださり、自分が見えていない部分を色々と学ばさせていただきました。

とてもためになりました!登壇してくださり、本当にありがとうございました!

懇談会では、ケータリングを囲んでわいわい技術の話で盛り上がりました。 今回のイベントでUUUMに興味を持ってくださった方も沢山いて、とても楽しかったです。

f:id:chibiProgrammer:20191224151744j:plain
懇談会での様子

f:id:chibiProgrammer:20191224152651j:plain
ケータリング

f:id:chibiProgrammer:20191224153107j:plain
ケータリング

f:id:chibiProgrammer:20191224152404j:plain
全体写真

イベント参加者の皆さま本当にありがとうございました。1回目よりもとてもいいイベントになりました。 第3回も行いますので、その際はぜひ遊びにきてください!

プロダクトマネージャーカンファレンス2019

こんにちは。PMチームのしだです。
会社のカンファレンス参加精度を利用して、2019年11月12、13日に行われたプロダクトマネージャーカンファレンス2019に参加してきました。

2019.pmconf.jp

参加した9つのセッションの中から、
「PMが学ぶべき、最低限のデータ活用スキルとは」
についてレポートします。

レポート

セッション概要

『PMが学ぶべき、最低限のデータ活用スキルとは』
株式会社Hakali代表取締役
小川 晋一郎

データは分析するだけでは意味がなく、ユーザーのために活用されてはじめて価値が生まれます。ですのでユーザーのことを日々真剣に考え、新たな価値提供を生みたいと常に思っているPMの人たちこそデータ活用スキルを身につけるべきです。しかし、かといって統計学を一から学んで、、、とか、機械学習の講座を受けて、、、では遠すぎます。必要最低限のデータ活用スキルを身につけて、ユーザーへの価値提供力を高めましょう。

https://2019.pmconf.jp/sessions/2019/11/13/S2-003/

はじめに

  • よくある事例

    • 数字を今すぐ見たいPM VS 忙しいエンジニア
    • 説明をしたいPM(ユーザーに嫌がられるのでユーザーへのメール数を減らしたい) VS 話の通じない上司(減らして売上あがるの?)
    • 自分でデータを扱えればこんな事例に対応できる
  • 実際に対応した事例

    • Before:機能ベースのKPI設定で現場が疲弊
    • After:意味ベースのKPIを設定したことで、中長期的な目線での戦略が打てるように
  • =受注の背景には何があるのかを見つけて意味ある指標を設定する

  • ふまえて今回のテーマは

    • 意思決定のためのデータ活用手順
    • 具体的なスキルとその身につけ方

意思決定のためのデータ活用手順

f:id:shidm00:20191122114032j:plain

  • データ活用に最も大切なテーマ
    • データというコミュニケーション言語をつかって、解くべき「課題」をチームで合意する
      • 事業インパクト
      • 解決コスト
      • 戦略の方向性
      • 部門ごとの視点
      • 組織のcapability

指標が相手や営業に刺さらない場合はだいたい「課題の設定」がずれている
そのためにPMとしては
解くべき「課題」を決めるための合意形成プロセスに責任をもち、
議論が進むための素材を準備する

  • おすすめのデータ活用モデル
    • OODAループ
      • Observe 見る
      • Orient わかる
      • Decide きめる
      • Act うごく
    • PDCAだとPlanが先にくるが、Planを立てるということは難しいこと
    • なので、まずは観察することから始める
    • 先の読めない、リーンな開発には特に合う

f:id:shidm00:20191122114102j:plain

Observe(見る)
  • 現場感覚(インタビュー) と 生データ
  • ユーザー(顧客)と接点ある人達から課題感を聞いて整理
  • 現場のモヤモヤはだいたい正しい
  • モヤモヤ会議で課題を集めてみる

代案のない提案をするなとか代案出さずに不満だけ言うななどと言われがちだが、モヤモヤにある課題が何なのかの見極めも、その解決も難易度はとても高い
代案が出せない場合ずっとももやもやを抱えている状態が続くことになる
なのでまずは現場のモヤモヤを吸い上げて整理するのが大事
モヤモヤ集めで課題認識が擦り合うだけで、現場の協力が得られるようになり、その後の議論がスムーズになるケースもある

  • 生データを見てみる
    • 分析手法をつかってかっこよく鮮やかな正解を出す…とかいうよりも、とにかく生データを見ること
    • エクセルでの数値管理はつまりサマリなので、生データからあたってみるだけでもかなり違う
Orient(分かる)
  • モデル化
    • 現実の見方や捉え方を言語化、可視化してKPIツリーに紐付けること

KPIツリーに出てきにくいが「スピード」が重要な指標となることは多い(例えばマッチング後の面接設定メールなど)
スピードを加味したモデル作りをする

  • 総数問題のモデル化
    • 一般的に「総数」が増えると「次のファネルへの移行期待値」が上がるが、閾値がある
      • 増やせば増える、には限界がある
    • 適切なところを指標に設定する必要がある

f:id:shidm00:20191122114121j:plain

  • 総数問題に関連して…
  • 冒頭ちらっと出したユーザーへのメール爆打ち問題のモデル化

    • それが起きやすいのは、送れば送るほどKPIツリーの数字が増えるモデルにしているから
    • そういう状態にしないために、KPIの中にネガティブ要素を含める必要がある
    • 例えば、数打ちすぎると退会率があがる、開封率が下がるなどが考えられるので、退会率や開封率も組み合わせるなど
    • トータルの売上を意識している人と同じ言語でデータで語る必要がある
  • 細分化の罠

    • 細かいパターン分類に走っても、打ち手を細かく打つコストが高いことが多い
    • KPIツリーのマクロデータのみで思考すると起こりがち
Decide(きめる)
  • マクロで見たときの施策インパクトは常に確認する
    • PMが「やりたい」ことが、案外マクロでみたらインパクトが小さいことは少なくない
    • 率改善が売上にどうインパクトするのかという視点を常に持つ
  • 比較

    • 比較がないと良い悪いの判断ができないが、比較するものによって見える結論は変わることを自覚する必要がある
  • WEBサービスの外のモデルが大切なこともあって見落としやすい

    • WEBサービスのことばかり見ていて、一連の作業の流れなど大きなところをみることを忘れると見落としが発生しがち
    • ユーザーの行動全体をみる
  • その他気をつけること

    • 普段会議にいないキーマンの観点を考慮できていないと意思決定できないこともあるので注意
    • 常に視座を高く持っておくことが大切
まとめ
  • チームでデータを扱いながらOODAループを回すことで、事業クリティカルな課題を1つずつクリアしていく
    • OODAループ
      • Observe 見る
      • Orient わかる
      • Decide きめる
      • Act うごく

具体的なスキルとその身につけ方

たくさん覚えるのではなく、必要最低限で生き延びること
=サバイバル

データ活用のために押さえること

WebサービスのPMであればまずはSQLを覚えるべし

  • 実践がないと頑張れないので、準備より本番
    • データ定例を作って関係者を週次であつめる
    • もやもやを話してもらって宿題化する
    • 宿題を解くことで力をつけていく
  • 試行錯誤しやすい環境を構築することも必要

  • 概念的に理解すべきポイント

    • データをDatabaseとして「テーブル」の概念で扱えるかどうかは大きな分岐点
    • 人間が読むことではなく、システムが読める形で扱う
  • おすすめのSQL修業ステップ

    • ちゃんとした人(エンジニア、データサイエンティスト)が書いたクエリを読み込み、一部だけいじって出してみる
    • 自分で簡単な文法で書いて抽出したものを、BIツール等外で加工して利用
    • WINDOW関数やサブクエリ等、ある程度複雑なものもSQLで表現
  • DBのテーブルにどんなデータが入っているのかを読みとけるようになると自走力は上がる
分析手法として 何をしっておくと良いのか

分析はrawdataの意味を取りやすくしたもの

  • rawdata

    • 量が多い
    • 全体の意味が読み取りづらい
    • 予算のモデルにしづらい
  • 統計処理

    • 全体の意味を読みやすく集計しているにすぎない
    • データ全体を一言で表現;代表値(平均値、中央値)
    • まとまったパターンに集約:クラスタリング
    • 可視化:各種グラフ
  • 分析手法:分布

    • 平均値より中央値
    • でも意味を考えるならまずは「分布」を眺めたほうがいい
  • クラスタリング

    • タイプごとの中央値みたいなイメージ
    • 共有するときには、データの裏付けを元に「意味」ベースでラベリングすると議論しやすい

まとめ

とにかくやり続けられる環境を作ることが大事
やり続ければいつかスキルは上がる

データ抽出スキルがあることで世界は広がる
Web業界であればまずはSQLを学ぶこと

生情報(インタビュー、rawdata)を元に事象をモデル化して、チームでOODAループを回す

カンファレンス全体通しての感想

  • 会社の状況や文化、事業の内容、一緒に仕事をする人やチームの体制によってPMがやらなければならないことにはかなり差がある
  • 共通しているのは『担当プロダクトの価値を上げること』がミッションで、そのためにできることは何でもするという点(解像度低め)
  • 一般的に定義されているPMの役割について知っておくのは、考える糸口として必要
  • ただ、それにむりやり当てはめるとうまく行かない場合が多そう
  • たくさんの事例を知っておくのは臨機応変な対応をする上で重要

初参加でしたが、普段知ることができないたくさんの企業の様々な事例とその対応を知ることができ、大変有意義な勉強をさせて頂きました。