Servantでの例外処理
ファイルの読み込み(基本形で、ここではまだ例外処理を入れていない)
ファイル読み込みに失敗した時の対処 (catch)
*> :t catch catch :: Exception e => IO a -> (e -> IO a) -> IO a
catch
は、catchした時の対処はできるが、戻りの型はIO a
なので、aで正常と異常を区別しなくてはいけない。下の例ではIO String
なので、String
で正常と異常を区別することになるが、異常の状態をなにか特別な文字列表現にしなくてはいけない。これは、実質的に、例外の情報を、呼び出し側に教えることができない。
Exception handing by catch in Servant
ファイル読み込みに失敗した時の対処 (try)
try
はIO (Either a b)
なので、例外の情報をLeft
で呼び出し元に教えることができる。
Exception handling by try in Servant
パスより読み込むファイルを指定する
明示的に型を指定するVisible Type Application
GHC 8.0からVisible Type Application
が導入されました。
今までは::
で型を明示的に指定していました。
Prelude> Prelude> let x = return 1 Prelude> :t x x :: (Num a, Monad m) => m a Prelude> let x = return 1 :: Maybe Int Prelude> :t x x :: Maybe Int Prelude> let x = return 1 :: Maybe Float Prelude> :t x x :: Maybe Float Prelude>
TypeApplications
を使うと@Type
で型を指定することができるようになります。
Prelude> Prelude> :set -XTypeApplications Prelude> let x = return @Maybe 1 Prelude> :t x x :: Num a => Maybe a Prelude>
使い方としては、ポリモーフィックな関数があったとして、それに対して型を限定した使い方をさせたい時に使うのかもしれません。
Prelude> :t map map :: (a -> b) -> [a] -> [b] Prelude> :t map @Int @Char map @Int @Char :: (Int -> Char) -> [Int] -> [Char]
IntとIntegerの違い
Int
とInteger
との違い
インスタンスの確認
Prelude > :i Int data Int = GHC.Types.I# GHC.Prim.Int# -- Defined in ‘GHC.Types’ instance Bounded Int -- Defined in ‘GHC.Enum’ instance Enum Int -- Defined in ‘GHC.Enum’ instance Eq Int -- Defined in ‘GHC.Classes’ instance Integral Int -- Defined in ‘GHC.Real’ instance Num Int -- Defined in ‘GHC.Num’ instance Ord Int -- Defined in ‘GHC.Classes’ instance Read Int -- Defined in ‘GHC.Read’ instance Real Int -- Defined in ‘GHC.Real’ instance Show Int -- Defined in ‘GHC.Show’
Prelude > :i Integer data Integer = integer-gmp-1.0.0.1:GHC.Integer.Type.S# !GHC.Prim.Int# | integer-gmp-1.0.0.1:GHC.Integer.Type.Jp# {-# UNPACK #-}integer-gmp-1.0.0.1:GHC.Integer.Type.BigNat | integer-gmp-1.0.0.1:GHC.Integer.Type.Jn# {-# UNPACK #-}integer-gmp-1.0.0.1:GHC.Integer.Type.BigNat -- Defined in ‘integer-gmp-1.0.0.1:GHC.Integer.Type’ instance Enum Integer -- Defined in ‘GHC.Enum’ instance Eq Integer -- Defined in ‘integer-gmp-1.0.0.1:GHC.Integer.Type’ instance Integral Integer -- Defined in ‘GHC.Real’ instance Num Integer -- Defined in ‘GHC.Num’ instance Ord Integer -- Defined in ‘integer-gmp-1.0.0.1:GHC.Integer.Type’ instance Read Integer -- Defined in ‘GHC.Read’ instance Real Integer -- Defined in ‘GHC.Real’ instance Show Integer -- Defined in ‘GHC.Show’
Bounded
クラスのインスタンスになっているのがInt
で、一方、IntegerはBoundedクラスのインスタンスではない。
Bounded
を確認してみると。Integer
には最大値(maxBound
)がないことがわかる。
Prelude > maxBound :: Int 9223372036854775807 Prelude > maxBound :: Integer <interactive>:11:1: error: • No instance for (Bounded Integer) arising from a use of ‘maxBound’ • In the expression: maxBound :: Integer In an equation for ‘it’: it = maxBound :: Integer
Intの最大値(9223372036854775807)の型を確認する。
Prelude > 9223372036854775807 :: Integer 9223372036854775807 Prelude > 9223372036854775807 :: Int 9223372036854775807
Intの最大値から+1の(9223372036854775807+1)の型を確認する。
Prelude > 9223372036854775808 :: Integer 9223372036854775808 Prelude > 9223372036854775808 :: Int <interactive>:15:1: warning: [-Woverflowed-literals] Literal 9223372036854775808 is out of the Int range -9223372036854775808..9223372036854775807 If you are trying to write a large negative literal, use NegativeLiterals -9223372036854775808
Haskellで複素数
複素数のためのData.Complex
パッケージがあります。
複素数は:+
コンストラクタを使います。
> :i :+ data Complex a = !a :+ !a -- Defined in ‘Data.Complex’ infix 6 :+
1+2i
は次のよう:+
を使って次のようになります。
> 1 :+ 2 1 :+ 2 > :t (1 :+ 2) (1 :+ 2) :: Num a => Complex a
共役はconjugateを使います。
> Data.Complex.conjugate (1 :+ 2) 1 :+ (-2)
通常の加減乗除の演算子が使えます。
> let c1 = 1 :+ 2 > let c2 = 1 :+ (-2) > c1 + c2 2.0 :+ 0.0 > c1 * c2 5.0 :+ 0.0 > c1 - c2 0.0 :+ 4.0 > c1 / c2 (-0.6) :+ 0.8
極形式も使えます。mkPolar
絶対値
偏角
で指定します。
> mkPolar 1 pi (-1.0) :+ 1.2246467991473532e-16
複素数から絶対値
偏角
を取り出すことができます。
> polar (1 :+ 1) (1.4142135623730951,0.7853981633974483) > polar ((-1) :+ 0) (1.0,3.141592653589793)
絶対値
> abs (1 :+ 1) 1.4142135623730951 :+ 0.0
Servantのexampleのcabalファイルを作成する
下記にあるexampleのgreet.hs
用のcabalファイルを作ります。
cabal init
にて対話形式で必要な項目を入力していきます。
foo$ cabal init Package name? [default: servant02] greet Package version? [default: 0.1.0.0] Please choose a license: 1) GPL-2 2) GPL-3 3) LGPL-2.1 4) LGPL-3 5) AGPL-3 6) BSD2 * 7) BSD3 8) MIT 9) ISC 10) MPL-2.0 11) Apache-2.0 12) PublicDomain 13) AllRightsReserved 14) Other (specify) Your choice? [default: BSD3] 8 Author name? [default: bar] foo Maintainer email? [default: aaa@gmail.com] foo@gmail.com Project homepage URL? Project synopsis? greet Project category: * 1) (none) 2) Codec 3) Concurrency 4) Control 5) Data 6) Database 7) Development 8) Distribution 9) Game 10) Graphics 11) Language 12) Math 13) Network 14) Sound 15) System 16) Testing 17) Text 18) Web 19) Other (specify) Your choice? [default: (none)] 18 What does the package build: 1) Library 2) Executable Your choice? 2 What is the main module of the executable: * 1) Main.hs 2) Other (specify) Your choice? [default: Main.hs] 2 Please specify? greet.hs Source directory: * 1) (none) 2) src 3) Other (specify) Your choice? [default: (none)] What base language is the package written in: * 1) Haskell2010 2) Haskell98 3) Other (specify) Your choice? [default: Haskell2010] Add informative comments to each field in the cabal file (y/n)? [default: n] y Guessing dependencies... Generating LICENSE... Warning: LICENSE already exists, backing up old version in LICENSE.save0 Generating Setup.hs... Warning: Setup.hs already exists, backing up old version in Setup.hs.save0 Generating ChangeLog.md... Warning: ChangeLog.md already exists, backing up old version in ChangeLog.md.save0 Generating greet.cabal... You may want to edit the .cabal file and add a Description field.
そのまま実行しようとすると、依存関係のライブラリの指定がなくてエラーになります。Guessing dependencies...
とありますが、完全には推測してくれないようです。
foo$ cabal run greet.cabal ./greet.cabal has been changed. Re-configuring with most recently used options. If this fails, please run configure manually. Resolving dependencies... Configuring greet-0.1.0.0... Preprocessing executable 'greet' for greet-0.1.0.0... [1 of 1] Compiling Main ( greet.hs, dist/build/greet/greet-tmp/Main.o ) [Servant changed] greet.hs:8:1: error: Failed to load interface for ‘Data.Aeson’ It is a member of the hidden package ‘aeson-1.0.2.1’. Perhaps you need to add ‘aeson’ to the build-depends in your .cabal file. Use -v to see a list of the files searched for. greet.hs:11:1: error: Failed to load interface for ‘Data.Text’ It is a member of the hidden package ‘text-1.2.2.1’. Perhaps you need to add ‘text’ to the build-depends in your .cabal file. Use -v to see a list of the files searched for. greet.hs:13:1: error: Failed to load interface for ‘Network.Wai’ It is a member of the hidden package ‘wai-3.2.1.1’. Perhaps you need to add ‘wai’ to the build-depends in your .cabal file. Use -v to see a list of the files searched for. greet.hs:14:1: error: Failed to load interface for ‘Network.Wai.Handler.Warp’ It is a member of the hidden package ‘warp-3.2.9’. Perhaps you need to add ‘warp’ to the build-depends in your .cabal file. Use -v to see a list of the files searched for. greet.hs:16:1: error: Failed to load interface for ‘Servant’ It is a member of the hidden package ‘servant-server-0.9.1.1’. Perhaps you need to add ‘servant-server’ to the build-depends in your .cabal file. Use -v to see a list of the files searched for.
生成されたgreet.cabal
ファイルに、下記の部分を追加しました。バージョンは省いています。
59 -- Other library packages from which modules are imported. 60 build-depends: base >=4.9 && <4.10, -- カンマを追加 61 aeson, -- 追加 62 text, -- 追加 63 wai, -- 追加 64 warp, -- 追加 65 servant-server -- 追加
実行します。
foo$ cabal run greet.cabal ./greet.cabal has been changed. Re-configuring with most recently used options. If this fails, please run configure manually. Resolving dependencies... Configuring greet-0.1.0.0... Preprocessing executable 'greet' for greet-0.1.0.0... Running greet... ^C foo$
最終的なファイルは下記です。
Servantのログ出力
@lotz さんの下記の記事があります。手元で動かそうとしたところ(2017/01/15時点)、記事が書かれた頃とライブラリのバージョンが変わっているため、そのままでは動作しませんでした。そこで、修正部分をメモしました。
修正のポイントは以下の2点です。
作業手順
- 適当な作業用のディレクトリを作ります。
- 記事より
servant-logger-example.cabal
をコピーします。 - 記事より
app/Main.hs
をコピーします。 - 修正をします(下記に差分のdiffをとりました)。
cabal install
します。cabal build
します。dist/build/app/app
を実行します。
修正の差分は次の通りです。
foo$ diff app/Main_before.hs app/Main.hs 14c14 < import Control.Monad.Trans.Either --- > import Control.Monad.Trans.Except 24,27c24 < instance MonadLogger m => MonadLogger (EitherT e m) where < monadLoggerLog a b c d = lift $ monadLoggerLog a b c d < < loggingServer :: ServerT API (EitherT ServantErr (LoggingT IO)) --- > loggingServer :: ServerT API (ExceptT ServantErr (LoggingT IO)) 35a33 > Warp.run 8080 $ serve api server
修正をしないと、下記のようなコンパイルエラーとなります。
[1 of 1] Compiling Main ( app/Main.hs, interpreted ) app/Main.hs:35:18: error: • Couldn't match type ‘EitherT ServantErr (LoggingT IO)’ with ‘Control.Monad.Trans.Except.ExceptT ServantErr (LoggingT IO)’ arising from a functional dependency between: constraint ‘servant-0.9.1.1:Servant.Utils.Enter.Enter (EitherT ServantErr (LoggingT IO) Text) (Control.Monad.Trans.Except.ExceptT ServantErr (LoggingT IO) :~> Control.Monad.Trans.Except.ExceptT ServantErr IO) (Control.Monad.Trans.Except.ExceptT ServantErr IO Text)’ arising from a use of ‘enter’ instance ‘servant-0.9.1.1:Servant.Utils.Enter.Enter (m a) (m :~> n) (n a)’ at <no location info> • In the expression: (hoistNat (Nat runStdoutLoggingT)) `enter` loggingServer In an equation for ‘server’: server = (hoistNat (Nat runStdoutLoggingT)) `enter` loggingServer In the expression: do { putStrLn "Listening on port 8080"; let server = (hoistNat (Nat runStdoutLoggingT)) `enter` loggingServer; Warp.run 8080 $ serve api server }
このコンパイルエラーの解決には下記が参考になりました。