bigpadノート

プログラミング寄りの内容を書いてます。

1章 WebSocketを使ったチャットアプリケーション(シンプルなウェブサーバー)

(Mac OS X El Capitanを基本に書いています)

最初のプログラムとしてブラウザを使ったチャットアプリケーションを作ってみよう。 ブラウザとサーバーはWebSocketを使って通信することにします。 WebSocketっていうのはウェブサーバー/ブラウザ間の双方向通信を行う技術です。 通信がつながると、ブラウザ起点の通信だけでなく、サーバー起点の通信も可能になります。 詳しくはWikipediaを見てね。

これは書籍「Go言語によるWebアプリケーション開発」を使って Haskellの勉強をしようというシリーズです。 Haskellは書籍「関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)」を読んで勉強中。

表記について

以降の説明では次の表記を使います。

コマンド

$ echo これはコマンド実行です
これはコマンド実行です

$始まりの行が入力したコマンド行で、それ以外は出力された行か補足です。 出力が多い時は:で省略します。 なおrootユーザーでのコマンドは#始まりで表記します。

ソースコード

-- 入門プログラム
main = putStrLn "Hello, World!"

使用パッケージの選定

Haskellには多くのウェブフレームワークが用意されています。

今回はウェブフレームワークのひとつYesodで使われているWAIWARPWAI WebSocketsと、WebSocketsHTTP types、あとGingerを使うことにしました。

WAI

WAIHaskellのウェブサーバー/フレームワークの共通インタフェースを提供します。 CGIWSGIみたいなものでしょうか。

Warp

WarpはWAIを使ったHaskellのHTTPサーバーライブラリです。 RubyWEBrickみたいなものでしょうか。

WAI WebSockets

WAI WebSocketsはWAI上でWebSocketsを使うためのライブラリです。

WebSockets

WebSocketsHaskellでWebSocketサーバーを実装するためのライブラリです。

Ginger

GingerHaskellのテンプレートエンジンです。 PythonのJinja2をHaskellで実装したもののようです。

シンプルなウェブサーバー

まずはパッケージの準備を行ってシンプルなウェブサーバーを作ってみましょう。

プロジェクト用ディレクトリ作成

プロジェクトを作りたいディレクトリに移動し、stack newでプロジェクトディレクトリを作ります。 今回は~/proj/にchatプロジェクトを作ってそこで作業をします。

$ cd ~/proj
$ stack new chat
Downloading template "new-template" to create project "chat" in chat/ ...
The following parameters were needed by the template but not provided: author-email, author-name, authorName, category, copyright, github-username
You can provide them in /Users/big/.stack/config.yaml, like this:
templates:
  params:
    author-email: value
    author-name: value
 :
Or you can pass each one as parameters like this:
stack new chat new-template -p "author-email:value" -p "author-name:value" -p "authorName:value" -p "category:value" -p "copyright:value" -p "github-username:value"

Using cabal packages:
- chat/chat.cabal
 :
Writing configuration to file: chat/stack.yaml
All done.
$cd chat

この出力ではパラメータ指定がされていないと出ています。 今回はサンプルですので省略しましたが、実際のプロジェクト時には作者やメールアドレスなどを指定します。(いつか調べたい)

Cabalファイル編集

まず作られたchatディレクトリにあるchat.cabalファイルを編集します。 Libraryセクションのbuild-depends:に使用するパッケージを追加します。

 :
library
  hs-source-dirs:      src
  exposed-modules:     Lib
  build-depends:       base >= 4.7 && < 5
                      -- ここに使用パッケージ追記
                      , http-types
                      , wai
                      , warp
  default-language:    Haskell2010
 :

ソースファイル編集

次にchat/src/Lib.hsファイルを編集しましょう。 とりあえずsomeFunc関数を書き換えていきます。 今回は短いので全て引用しておきます。 参考ページ

{-# LANGUAGE OverloadedStrings #-}

module Lib
    ( someFunc
    ) where

import           Network.HTTP.Types        (status200)
import           Network.HTTP.Types.Header (hContentType)
import           Network.Wai               (Application, responseLBS)
import           Network.Wai.Handler.Warp  (run)

someFunc :: IO()
someFunc = do
  let port = 3000
  putStrLn $ "Listening on port " ++ show port
  run port app

app :: Application
app req f =
  f $ responseLBS status200 [(hContentType, "text/plain")] "Hello world!"

1行目 {-# LANGUAGE OverloadedStrings #-}

文字列リテラルである"〇〇"の扱いを変更します。 今回使用するパッケージが[Char]ではなく、ByteStringを要求するためです。

3〜5行目 module

外部に提供するものを書きます。 雛形ではLibモジュールとしてsomeFuncを提供しています。

7〜10行目 import

使用するパッケージを書きます。 今回は何がどのパッケージかわかるように使う要素も(〇〇)で指定しています。

13〜16行目 run port app

Warpを利用してポート番号とApplicationを渡してサーバーを起動しています。

18〜20行目 app :: Application

WAIのApplication型を定義しています。 リクエストに対してレスポンスとしてstatus:200context-type:text/plainと内容Hello world!を返すように定義しています。

アプリビルド

chatディレクトリでstack buildしてみましょう。 初回はCabalファイルに追加したパッケージのインストールが行われるため、かなり時間がかかります。

$ stack build
 :
chat-0.1.0.0: build
Preprocessing library chat-0.1.0.0...
 :

アプリ実行

ビルドが成功したら、実行してみましょう。

$ stack exec chat-exe
Listening on port 3000

ブラウザでhttp://localhost:3000にアクセスすると、Hello world!メッセージが表示されれば成功です。 パチパチパチ〜

使われている基礎構文の説明

Haskell 2010 言語報告書
ちゃんと言語報告書を読んだわけではなく、実際のコードから推測したりググって調べたものを書いています。まだHaskellに慣れていないので勘違いがあったらごめんなさい。

コメント

--から改行まではコメントとして扱われます。 ただし、他の構文に使われる字句の一部の場合はそっちが優先されます。 例えば-->|--はコメントとしては扱われないです。 なので慣習的に前後に空白を置いて書かれることが多いようです。

コメントが複数行になる場合は{--}で囲むと改行を含めてコメントとして扱われます。 この形式のコメントは入れ子にできるので、ネストコメントと呼びます。

以下がコメント例です。

-- これは1行コメント
{- これは複数行コメント
   改行もOK
   {- ネストできます
   -}
   ここもコメントです
-}
-- {- ここは1行コメントです
this_is_NOT_comment = "コメントアウトの切り替え"
-- -}

最後のコメントがネストコメントのようにも見えますが、コメントアウトのイディオムです。 ネストコメントの終了を-- -}としておくと、開始部分を1行コメントにするかどうかで、コメントアウトの切り替えができます。

また、ネストコメントはコンパイラへのプラグマ指定としても使われます。 今回のコードでは{-# LANGUAGE OverloadedStrings #-}が使われています。

次回予定の記事で扱いますが、ソースコードとその中のコメントからドキュメントを自動生成するhaddockというツールもあります。 -- |{- |-- ^{- ^始まりのコメントがドキュメント化されます。

識別子

  • 変数識別子小文字アルファベット始まりで、英字・数字・シングルクォートが0個以上続くもの
  • 構成子識別子大文字アルファベット始まりで、英字・数字・シングルクォートが0個以上続くもの

識別子は小文字、大文字を区別し、アンダースコア_は小文字とみなされます。 また、_始まりの識別子はコンパイラの未使用警告を抑制するようです。

型推論

Haskellには型推論機能があります。 わざわざ書かなくても自明なものはコンパイラが自動で推測してくれます。 ただ、自明じゃないものや人に向けたドキュメントとして型を指定することもできます。

::で型を指定します。 someFunc :: IO()app :: Applicationが型指定です。 左側の小文字始まりのものが変数名(関数名)で、右側の大文字始まりのものが型になります。 IO()()が何を指しているかは勉強不足です。。。

let式 / where節

letで式中でのみ有効な一時的な定義を行うことができます。 似たような構文としてwhere節があります。 whereは式中ではなく、宣言全体で有効な一時的な定義を行うことができるようです。 きっとmodulewhereもそういうものなんでしょう。

演算子

Haskellでは演算子を自分で定義することができます。 用意されている演算子+,-,*,/などよくあるものや、$++など見かけないものがあります。 (私は普段C++を使っています)

  • $は関数適用を行います。通常は左結合の所を右結合にできます。関数の引数として別の関数の結果を使いたい場合に()を省略できて便利です。

  • ++はリストの結合です。文字列リテラルは文字のリストなので、++を使って結合します。