Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions preprocessed-site/posts/2019/haskell-in-vrchat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
title: VRのためにHaskellを使った話
headingBackgroundImage: ../../img/background.png
headingDivClass: post-heading
author: Haruka Nakajima
postedBy: <a href="https://twitter.com/haru2036">Haruka Nakajima(@haru2036)</a>
date: December 03, 2019
tags: Spock, VRChat
...
---

# はじめに
はじめまして。趣味でHaskellしているはる(haru2036)と申します。まったり進行で開発しているのでGHCのバージョンアップの速さについていけてない感があります……
さて、今回はあんまり深い話はありません。どちらかと言うとこんなニッチなところにHaskell使ったよというネタで書きます。

# VR空間でLTがしたい
突然ですが、私は[VRChat](https://vrchat.com/)(以下VRC)というソーシャルVRサービス(Second LifeのVR版みたいなものです)にハマっています。
友人との雑談の中でVRCの中でLT会ができればプログラミングなどの話題で盛り上がれる人が集まってワイワイ楽しくできるのではないかと話して、その場のノリでとりあえず実装してみることにしました。

![完成したワールド](/img/2019/haskell-in-vrchat/vrc-lt-room.png "完成したワールド")

今回作りたかったのはスライドを表示するためのスクリーンと、ページ送りに使うボタンを実装したワールドです。

![発表者用コントロールパネル](/img/2019/haskell-in-vrchat/vrc-lt-control-panel.png "発表者用コントロールパネル")

VRCではアバターやワールドを自由に作ることができるのですが、VRCが提供するコンポーネント以外のスクリプトは利用できません。Haskellユーザとしては得意なことを活用しづらい土壌です。
幸いスライドを表示する手段はゲーム内でURLから画像を取得し表示するVRC_Panoramaというコンポーネントを利用することにより確保できましたが、VRC_Panoramaが取得できる画像はワールド作成時に決め打ちで指定されたURLのリストに含まれるもののみです。
そのため、スライド画像へのURLのリストを直接VRC_Panoramaに渡していると、イベントを開催する際よくある飛び入り参加やスライドの用意が遅れた参加者に対応できなくなってしまいます。

その問題を解決するために、イベント開始時からのページ数とスライドの画像URLをマップするWebAPIを用意しました。
具体的には、`/slides/{pageCount} ` のような形のエンドポイントを持ったAPIを用意し、そこから実際の画像へリダイレクトをかけるという方法を取りました。

![実装イメージ図](/img/2019/haskell-in-vrchat/vrc-lt-image.png "実装イメージ図")

# Webフレームワーク
今回は自分で使うだけだしということでさらっとやってみたかったのでSpockを利用しました。もう少し誰でも使えるサービスにしたいと考えているのでServantに載せ替えてかっちり作り直そうかと思い移植しているところです。

# はじめてのHaskellペアプロ
じつはLT会をやろうと思いついた友人の[BOXP](https://twitter.com/b0xp2)はClojureユーザで、せっかくだからとAPIの開発を手伝ってくれました。
あまりHaskellに馴染みはなかったものの、いわゆる関数型プログラミング的な概念はバッチリなのでスススっと書いてくれました。
書いてくれる上での障壁になったのは、型関連の要素(`data`や`type`や`newtype`がぱっと見わからなかった、型コンストラクタ、値コンストラクタの概念)に馴染みが薄かったことでした。
Discordで画面共有しながら説明を行ったのですが、やはり同じ画面を見ながら説明するのはとてもやりやすいと感じました。
本人からのメッセージはこちら。

> プログラミングHaskellを昔読んでかじったことがある程度で素人もいいところでしたが、当人のサポートもあり思いついた数日後には実装が終わっていました。
> はるくんの話にもある通りDiscordで画面共有しながらペアプロし、Haskellでのテストコードの書き方も一から教えてもらいながら書きました。これは願ってもない体験だったので根気よく教えてくれたことに非常に感謝しています。
>
> また、個人的には実装以外でのブレストや実際の会場でのデバッグをVRChat上でできた事もとてもよかったなと思っています。
> 単純に実装を確認するためには二人以上でVRChatに入る必要があるというのもありましたが、完全リモートでも身振り手振りありでブレストができたことや、アバターのおかげで環境に囚われないコミュニケーションができていたこともGoodでした。

# デプロイ
今回は自分で使うだけな上に常時稼働している必要もなく、コストを最小限に抑えたかったのでHerokuにデプロイしました。
Dockerfileを書き、スタティックリンク周りで悩みながらもイメージを生成してHerokuのレジストリにPushし、後はいつものHerokuという感じでうまくいきました。

余談ですが、最近参加したGotanda.hsというイベントで`cabal build --enable-executable-static`でいい感じにシングルバイナリが生成できるというお話を聞いたので、最近stackばっかり使ってたのを改めて適材適所で使い分けていきたいなーと思っています。

# 実際に開催してみて
ここはHaskellほぼ全く関係ないですが……

![](/img/2019/haskell-in-vrchat/vrc-lt.png)
VRC-LTという名前で6回ほど開催しているのですが、場所の制約を受けずに勉強会ができ、その後の懇親会も会場の撤収時刻や終電を気にせず話したい人はとことん話し続ける事ができるというところが非常に良かったです。
ホワイトボードはまだ未実装ですが、空間に書けるペンも配布されているのでその手のアイテムも取り入れれば懇親会での話も更にはずむのではないでしょうか。

VRChatはPCのみでも利用することができます。
VRC-LTはほぼ月イチペースで不定期開催ですので、もしよろしければ参加していただけると嬉しいです。
開催時のアーカイブ等も以下のWebサイトにて公開中です。合わせてご覧ください。
[https://vrc-lt.github.io](https://vrc-lt.github.io)

# リポジトリ
そんなこんなで開発中のリポジトリはこちらになります。
拙いところもいっぱいですがIssueやPRなどで気になった点を教えていただければ幸いです!
[https://github.com/vrc-lt/VRC-Slide-Server](https://github.com/vrc-lt/VRC-Slide-Server)