HaskellとScalaの型クラスの違いについて社内勉強会で発表しました
orehathiya's blog
2019年10月25日金曜日
2017年12月27日水曜日
servant + persistent + puxを使ったWebアプリの作成
servant + persistent + puxを使ったWebアプリのサンプルを作成したので紹介します。
WebAPIの定義にservant 、データ永続化ライブラリに persistent 、フロントエンドフレームワークにPureScript製のpuxを使っています。
全てのソースは https://github.com/orehathiya/example-servant-persistent にあります。
servant + persistent を使ったWebAPIの紹介は以下のように既に記事がいくつか存在するのでpux との連携部分について主に紹介します。
servantは実装するAPIの仕様を型によって定義することができ、自動でHTTPリクエストパラメータやレスポンスのシリアライズ/デシリアライズを行ったり、自動でドキュメントを生成したり、APIにアクセスするクライアント関数を生成したりすることができます。
これによってAPIの実際の実装とドキュメントやクライアント関数との間に乖離が発生することを防ぐことができます。
サンプルでは servant-purescript を使ってservantで定義したHaskellのAPIの型とデータ型からPureScriptのAPIクエリ関数とデータ型を生成しています。
自分で定義したUserとReport型は何も問題なく生成できますが、PersistentのEntity aとKey a型はそのままGenericのインスタンスにできない(GADTs等はできないらしい)ので構造が似たようなMyEntityとMyKeyを定義してそのRepで上書きしています(このあたりもっと綺麗な書き方があれば教えて欲しいです)。
生成される型は以下のようになります。
APIクエリ関数は以下のようになります(多いのでgetUserGetByName関数だけ抜粋)。
APIの定義通りnameパラメータを受け取ってEntity User型を返す関数になっています。
生成されたAPIクエリ関数はimportして以下のように使えます。
puxはいわゆるElm ArchitectureのPureScript製のフロントエンドフレームワーク です。foldpはElmのupdate、Reduxのreducerに相当する状態更新のための関数です。このfoldp関数では状態(state)の更新と副作用のあるeffectfulな処理の両方を行うことができます(詳しくは公式ドキュメント参照)。
effect内で自動生成された先ほどのgetUserGetByName関数を実行してReceiveUserイベントを発火し、state内のuserに取得したユーザーデータをセットしています。
WebAPIを作成する時のよくある悩みとしてドキュメントやクライアントと実装の乖離が起こる事や、サーバーサイドとクライアントサイドで同じようなモデルを2回定義する必要がある事などがありますが、servantを使うとこれらの悩みを解消することができます。
皆様もぜひservantを使って楽々WebAPI開発を楽しみましょう!
WebAPIの定義にservant 、データ永続化ライブラリに persistent 、フロントエンドフレームワークにPureScript製のpuxを使っています。
全てのソースは https://github.com/orehathiya/example-servant-persistent にあります。
servant + persistent を使ったWebAPIの紹介は以下のように既に記事がいくつか存在するのでpux との連携部分について主に紹介します。
- https://qiita.com/jabaraster/items/e8ebbe6d25b535947aba
- https://qiita.com/cyclone_t/items/52ad44cfbb4603e123f3
servantの型定義からPureScriptの型とAPIクエリ関数を生成する
servantは実装するAPIの仕様を型によって定義することができ、自動でHTTPリクエストパラメータやレスポンスのシリアライズ/デシリアライズを行ったり、自動でドキュメントを生成したり、APIにアクセスするクライアント関数を生成したりすることができます。
これによってAPIの実際の実装とドキュメントやクライアント関数との間に乖離が発生することを防ぐことができます。
サンプルでは servant-purescript を使ってservantで定義したHaskellのAPIの型とデータ型からPureScriptのAPIクエリ関数とデータ型を生成しています。
自分で定義したUserとReport型は何も問題なく生成できますが、PersistentのEntity aとKey a型はそのままGenericのインスタンスにできない(GADTs等はできないらしい)ので構造が似たようなMyEntityとMyKeyを定義してそのRepで上書きしています(このあたりもっと綺麗な書き方があれば教えて欲しいです)。
data MyEntity record = Entity { key :: Key record , value :: record } deriving (Generic) newtype MyKey a = Key Int deriving (Generic) instance Generic (Entity a) where type Rep (Entity a) = Rep (MyEntity a) from = from to = to instance Generic (Key a) where type Rep (Key a) = Rep (MyKey a) from = from to = to myTypes :: [SumType 'Haskell] myTypes = [ mkSumType (Proxy :: Proxy (Entity A)) , mkSumType (Proxy :: Proxy (Key A)) , mkSumType (Proxy :: Proxy User) , mkSumType (Proxy :: Proxy Report) ]
生成される型とAPIクエリ関数
生成される型は以下のようになります。
newtype User = User { name :: String , age :: Int } derive instance genericUser :: Generic User derive instance newtypeUser :: Newtype User _ newtype Report = Report { imp :: Int , click :: Int , ctr :: Int } derive instance genericReport :: Generic Report derive instance newtypeReport :: Newtype Report _Haskell側の型とほぼおなじですね。Generic型クラスのインスタンスにもなっているのでpurescript-argonaut-generic を使ってJSONのシリアライズ/デシリアライズができます。
APIクエリ関数は以下のようになります(多いのでgetUserGetByName関数だけ抜粋)。
getUserGetByName :: forall eff m. MonadAsk (SPSettings_ SPParams_) m => MonadError AjaxError m => MonadAff ( ajax :: AJAX | eff) m => String -> m (Entity User) getUserGetByName name = do spOpts_' <- -="" ask="" let="" o="" of="" spopts_="" spsettings_=""> o let spParams_ = case spOpts_.params of SPParams_ ps_ -> ps_ let baseURL = spParams_.baseURL let httpMethod = "GET" let queryString = "" let reqUrl = baseURL <> "user" <> "/" <> "get" <> "/" <> encodeURLPiece spOpts_' name <> queryString let reqHeaders = [] let affReq = defaultRequest { method = httpMethod , url = reqUrl , headers = defaultRequest.headers <> reqHeaders } affResp <- -="" affjax="" affreq="" d="" decodejson="case" let="" of="" spopts_.decodejson="" spsettingsdecodejson_=""> d getResult affReq decodeJson affResp ->->
APIの定義通りnameパラメータを受け取ってEntity User型を返す関数になっています。
PUXから使う
生成されたAPIクエリ関数はimportして以下のように使えます。
foldp (ReceiveUser (Entity user)) (State st) = noEffects $ State st { user = Just (Entity user) , status = "User" } foldp (RequestUser) state = runEffectActions state [ReceiveUser <$> getUserGetByName "Alice"]
puxはいわゆるElm ArchitectureのPureScript製のフロントエンドフレームワーク です。foldpはElmのupdate、Reduxのreducerに相当する状態更新のための関数です。このfoldp関数では状態(state)の更新と副作用のあるeffectfulな処理の両方を行うことができます(詳しくは公式ドキュメント参照)。
effect内で自動生成された先ほどのgetUserGetByName関数を実行してReceiveUserイベントを発火し、state内のuserに取得したユーザーデータをセットしています。
まとめ
WebAPIを作成する時のよくある悩みとしてドキュメントやクライアントと実装の乖離が起こる事や、サーバーサイドとクライアントサイドで同じようなモデルを2回定義する必要がある事などがありますが、servantを使うとこれらの悩みを解消することができます。
皆様もぜひservantを使って楽々WebAPI開発を楽しみましょう!
参考サイト
- https://github.com/eskimor/purescript-bridge
- https://github.com/haskell-servant/example-servant-persistent
- https://github.com/alexmingoia/pux-starter-app
2017年12月13日水曜日
Haskellの気に入っている所
最近Haskellの良いところを聞かれる機会が増えてきたので言葉にして整理しておく。
Paul Graham氏はエッセイでプログラミング言語が長期間生き残るために必要なことは根源的なオペレーターを少なくする事だと述べている。Hasekllは根源的なオペレーターは関数1つのみである。Lispは50年以上生き残っているプログラミング言語だが同じぐらいシンプルなHaskellも同じように50年以上生き残るプログラミング言語になるだろう。
また同じ静的型付け言語としてJavaが存在するがHaskellにはMaybe型が存在するのでNull pointer exceptionが発生することがない。 さらに型推論も存在するので全ての関数に手で型を書く必要がないし、必要ならコンパイラに生成させることもできる。
Haskellの型クラスと似たような事を例えばRubyで行おうとするとダックタイピングを行うことになるだろう。ダックタイピングではあるメソッドが存在することを前提にメソッド呼び出しを行うが、もしその想定するメソッドが受け取ったオブジェクトに存在しない場合ランタイムエラーになる。しかしHaskellでは型制約という仕組みによりその型を呼び出すことの出来る関数が定義されていることをコンパイル時に保証することができるのでより安全に抽象的な関数を記述することができる。
この仕組みのおかげでその関数が何を行っているか(副作用のない関数は必ず値を受け取って値を返す)わかりやすくなるし、テストも書きやすくなる。
まずビルドツールであるstackがある。今までYesodのような依存パッケージが多い巨大なフレームワークをビルドしようとするとdependency hellになることが多かった。stackでは必ずビルドが通るパッケージの集合のバージョンを指定することでそのバージョンであれば必ずビルドが通るようになりdependency hellから開放された。
さらにエディタサポートツールであるinteroがある。パワフルな型システムのHaskellはエディタサポートで可能な事が多い言語であるにもかかわらず今までなかなかこれといったエディタサポートツールがなかった。しかし、このinteroが出てきたことで型表示・定義元ジャンプ・入力補完等基本的なエディタサポート機能が使えるようになった。
例えばYesodは他の言語やフレームワークより早いというベンチマークもある。
C++ より速くて、Perl より簡潔で、Python よりきちんとしていて、Ruby より柔軟で、C# より型が充実していて、Java より頑強で、PHP とは何の共通点もないもの。
文法がシンプル
究極的に言ってしまえばHaskellに存在する概念は関数のみである。値も値コンストラクタが値を受け取って値を返すという形で生成されるので実質関数と考えられなくもない。オブジェクト指向の要素を持つプログラミング言語にあるようなクラスやメソッドや継承等オブジェクト指向を実現するための概念が一切存在しない。このために覚えなければいけないことは非常に少なく学習コストも低い。さらに、コードはシンプルになり一貫性が上がる。この特徴は同じく関数型言語であるClojureやクラスが存在しないGoとある意味似ているだろう。Paul Graham氏はエッセイでプログラミング言語が長期間生き残るために必要なことは根源的なオペレーターを少なくする事だと述べている。Hasekllは根源的なオペレーターは関数1つのみである。Lispは50年以上生き残っているプログラミング言語だが同じぐらいシンプルなHaskellも同じように50年以上生き残るプログラミング言語になるだろう。
静的型付け
Haskellは静的型付け言語である。型の解決がコンパイル時に行われ、コード中に型が不整合な部分があるとコンパイラがエラーを出してくれる。このためにコードを動かす段階になったらまず間違いなく動くだろうという安心感が大きい。また静的型付け言語なので当たり前だが存在しない関数を呼び出したり、関数の引数の数が違っていたりするとエラーになる(カリー化は除く)。静的型付けというと前者のメリットがよく言われるが個人的には後者もコードのリファクタリング時などにあるとうれしい実用的に大きなメリットだと感じている。また同じ静的型付け言語としてJavaが存在するがHaskellにはMaybe型が存在するのでNull pointer exceptionが発生することがない。 さらに型推論も存在するので全ての関数に手で型を書く必要がないし、必要ならコンパイラに生成させることもできる。
安全できれいに抽象化できる
Haskellは数学的構造を抽象的に扱うための理論である圏論にある概念をコードで記述できるほど抽象的なコードが書きやすい。これはインターフェースに似た概念である型クラスが存在する事が大きい。この型クラスのおかげで同じ構造を持つ物であれば同じように操作することができる。例えばMaybe型とList型を考えるとGoではfor文とif文&タプルを使う必要がある一方Hsakellではどちらも>>=(bind)関数で扱うことが可能である。Haskellの型クラスと似たような事を例えばRubyで行おうとするとダックタイピングを行うことになるだろう。ダックタイピングではあるメソッドが存在することを前提にメソッド呼び出しを行うが、もしその想定するメソッドが受け取ったオブジェクトに存在しない場合ランタイムエラーになる。しかしHaskellでは型制約という仕組みによりその型を呼び出すことの出来る関数が定義されていることをコンパイル時に保証することができるのでより安全に抽象的な関数を記述することができる。
純粋性
Haskellでは副作用のある関数とない関数に明確に分けることができる。副作用のある関数はIO型やState型(かもしくはそれを合成したモナド)が戻り値となる。モナドの仕組みのおかげで副作用のある関数から副作用のない関数を呼ぶことはできるがその逆はできないようになっている。この仕組みのおかげでその関数が何を行っているか(副作用のない関数は必ず値を受け取って値を返す)わかりやすくなるし、テストも書きやすくなる。
おまけ
周辺ツールの使いやすさ
近年Haskellを取り巻く便利な周辺ツールがいくつか出てきている。まずビルドツールであるstackがある。今までYesodのような依存パッケージが多い巨大なフレームワークをビルドしようとするとdependency hellになることが多かった。stackでは必ずビルドが通るパッケージの集合のバージョンを指定することでそのバージョンであれば必ずビルドが通るようになりdependency hellから開放された。
さらにエディタサポートツールであるinteroがある。パワフルな型システムのHaskellはエディタサポートで可能な事が多い言語であるにもかかわらず今までなかなかこれといったエディタサポートツールがなかった。しかし、このinteroが出てきたことで型表示・定義元ジャンプ・入力補完等基本的なエディタサポート機能が使えるようになった。
効率性
Haskellはインタプリタ言語ではなくコンパイラ言語であり、実行速度はそれなりに早い。\例えばYesodは他の言語やフレームワークより早いというベンチマークもある。
まとめ
これらをまとめてHaskellを端的に表現すると以下の有名な言葉に集約されるだろう。C++ より速くて、Perl より簡潔で、Python よりきちんとしていて、Ruby より柔軟で、C# より型が充実していて、Java より頑強で、PHP とは何の共通点もないもの。
参考
登録:
投稿 (Atom)
HaskellとScalaの型クラスの違い
HaskellとScalaの型クラスの違いについて社内勉強会で発表しました 型クラス from hakukotsu
-
最近Haskellの良いところを聞かれる機会が増えてきたので言葉にして整理しておく。 文法がシンプル 究極的に言ってしまえばHaskellに存在する概念は関数のみである。値も値コンストラクタが値を受け取って値を返すという形で生成されるので実質関数と考えられなくもない。オブ...
-
servant + persistent + puxを使ったWebアプリのサンプルを作成したので紹介します。 WebAPIの定義に servant 、データ永続化ライブラリに persistent 、フロントエンドフレームワークにPureScript製の pux を使って...
-
HaskellとScalaの型クラスの違いについて社内勉強会で発表しました 型クラス from hakukotsu