Skip to content

Commit 741412d

Browse files
Merge pull request #164 from haskell-jp/post-fallible-package
記事の追加: fallibleというパッケージをリリースしました
2 parents 96458fa + edaca6b commit 741412d

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed
112 KB
Loading
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
title: fallibleというパッケージをリリースしました
3+
headingBackgroundImage: ../../img/background.png
4+
headingDivClass: post-heading
5+
author: Nobutada MATSUBARA
6+
postedBy: <a href="https://matsubara0507.github.io/whoami">Nobutada MATSUBARA(@matsubara0507)</a>
7+
date: July 18, 2019
8+
tags:
9+
...
10+
---
11+
12+
タイトルの通り、fallibleというパッケージを紹介します。
13+
14+
- [matsubara0507/fallible: interface for fallible data type like Maybe and Either. - GitHub](https://github.com/matsubara0507/fallible)
15+
16+
ちなみに、fallibleはHaskell-jp Slackで:
17+
18+
<img src="../../img/2019/fallible/slack.jpg" style="width: 100%;">
19+
20+
と質問したところ、該当するようなパッケージは無さそうだったので作ったという経緯があります。
21+
その際に助言をくれた [fumieval](https://github.com/fumieval)氏のコードをほとんど引用した形になったので、Haskell-jp Blogに紹介記事を載せることにしました(僕は普段、自分のブログに自作したパッケージを書いています)。
22+
23+
## fallibleパッケージ
24+
25+
Haskellでアプリケーションを記述してると次のようなコードを書くことがありますよね?
26+
27+
```Haskell
28+
import qualified Data.List as L
29+
30+
run :: String -> Token -> Bool -> IO ()
31+
run targetName token verbose = do
32+
users <- getUsers token
33+
case users of
34+
Left err -> logDebug' err
35+
Right us -> do
36+
case userId <$> L.find isTarget us of
37+
Nothing -> logDebug' emsg
38+
Just tid -> do
39+
channels <- getChannels token
40+
case channels of
41+
Left err -> logDebug' err
42+
Right chs -> do
43+
let chs' = filter (elem tid . channelMembers) chs
44+
mapM_ (logDebug' . channelName) chs'
45+
where
46+
logDebug' = logDebug verbose
47+
emsg = "user not found: " ++ targetName
48+
isTarget user = userName user == targetName
49+
50+
logDebug :: Bool -> String -> IO ()
51+
logDebug verbose msg = if verbose then putStrLn msg else pure ()
52+
```
53+
54+
Slackのようなチャットツールをイメージしてください。
55+
該当の名前(`targetName`)を持つユーザーを与えると、そのユーザーが参加しているチャンネルの一覧を表示するというような振る舞いです。
56+
こう段々になってしまうのは気持ち悪いですよね。
57+
fallibleの目的はこの段々を次のように平坦にすることです(`where` などは割愛):
58+
59+
```Haskell
60+
import Data.Fallible (evalContT, exit, lift, (!?=), (???))
61+
62+
run :: String -> Token -> Bool -> IO ()
63+
run targetName token verbose = evalContT $ do
64+
users <- lift (getUsers token) !?= exit . logDebug'
65+
targetId <- userId <$> L.find isTarget users ??? exit (logDebug' emsg)
66+
channels <- lift (getChannels token) !?= exit . logDebug'
67+
lift $ mapM_ (logDebug' . channelName) $
68+
filter (elem targetId . channelMembers) channels
69+
```
70+
71+
### やってること
72+
73+
というか、もともとのアイデアは下記のブログです:
74+
75+
- [ContT を使ってコードを綺麗にしよう! - BIGMOON Haskeller's BLOG](https://haskell.e-bigmoon.com/posts/2018/06-26-cont-param.html)
76+
77+
これを一般化(`Maybe a` 固有ではなく `Either e a` でも使う)できないかなぁというのがもともとの発想です。
78+
79+
### 基本演算子
80+
81+
次の4つの演算子を利用します:
82+
83+
```Haskell
84+
(!?=) :: Monad m => m (Either e a) -> (e -> m a) -> m a
85+
(!??) :: Monad m => m (Maybe a) -> m a -> m a
86+
(??=) :: Applicative f => Either e a -> (e -> f a) -> f a
87+
(???) :: Applicative f => Maybe a -> f a -> f a
88+
```
89+
90+
ただし内部実装的には `Maybe a` や `Either e a` は `Fallible` 型クラスで一般化されています:
91+
92+
```Haskell
93+
class Applicative f => Fallible f where
94+
type Failure f :: *
95+
tryFallible :: f a -> Either (Failure f) a
96+
97+
instance Fallible Maybe where
98+
type Failure Maybe = ()
99+
tryFallible = maybe (Left ()) Right
100+
101+
instance Fallible (Either e) where
102+
type Failure (Either e) = e
103+
tryFallible = id
104+
105+
(!?=) :: (Monad m, Fallible t) => m (t a) -> (Failure t -> m a) -> m a
106+
(???) :: (Applicative f, Fallible t) => t a -> f a -> f a
107+
```
108+
109+
これらを継続モナドと組み合わせることでIOと失敗系モナド(`Maybe a` や `Either e a`)をモナドトランスフォーマーなしにDo記法で書くことができます
110+
111+
```Haskell
112+
-- 継続モナドに関する関数
113+
evalConstT :: Monad m => ContT r m r -> m r
114+
115+
exit :: m r -> ContT r m a
116+
exit = ContT . const
117+
```
118+
119+
## サンプルコード
120+
121+
疑似的なIOで良いなら[fallibleリポジトリのexampleディレクトリ](https://github.com/matsubara0507/fallible/tree/master/example)にあります(上述の例はそれです)。
122+
123+
実際の利用例であれば、最近自作した[matsubara0507/mixlogue](https://github.com/matsubara0507/mixlogue)というHaskellアプリケーションで多用しています([ココ](https://github.com/matsubara0507/mixlogue/blob/8afd16ab4048ff62976b8e38347078fdaa7417dd/src/Mixlogue/Cmd.hs#L81-L93)とか[ココ](https://github.com/matsubara0507/mixlogue/blob/8afd16ab4048ff62976b8e38347078fdaa7417dd/src/Mixlogue/Message.hs#L15-L25)とか)。
124+
ちなみに、mixlogueは特定のSlackの分報チャンネル(`times_hoge`)の発言を収集するというだけのツールです。
125+
126+
## 使い方
127+
128+
READMEを参照してください。
129+
130+
現状Hackageにはあげてないので、stackやCabalでGitHubリポジトリから参照する方法を利用してください。
131+
132+
## おしまい
133+
134+
fumieval氏のコードをほとんど引用するだけになったので自分でリリースするか迷ったんですけど、リリースしてくれというのも丸投げがひどいので自分でリリースしました。
135+
まぁこういう結果が生まれるのもOSSコミュニティの醍醐味ということで。
136+
fumieval氏、いつもアドバイスをくれてありがとう!
137+
138+
(もちろん他のHaskell-jpの皆さんも!)

0 commit comments

Comments
 (0)