2015-09-07 10 views
5

私はいくつかの外部APIとやりとりするための小さなライブラリを書いています。 1つの関数セットは、yahoo APIへの有効なリクエストを作成し、その結果をデータ型に解析します。もう1つの関数セットは、IPに基づいてユーザーの現在の場所を検索し、現在の場所を表すデータ型を返します。コードが機能する間、それはタイプIO(たぶんa)のシーケンスの複数の関数に明示的にパターンマッチする必要があるようです。IO型の連鎖機能(たぶんa)

-- Yahoo API 

constructQuery :: T.Text -> T.Text -> T.Text 
constructQuery city state = "select astronomy, item.condition from weather.forecast" <> 
          " where woeid in (select woeid from geo.places(1)" <> 
          " where text=\"" <> city <> "," <> state <> "\")" 

buildRequest :: T.Text -> IO ByteString 
buildRequest yql = do 
    let root = "https://query.yahooapis.com/v1/public/yql" 
     datatable = "store://datatables.org/alltableswithkeys" 
     opts = defaults & param "q" .~ [yql] 
          & param "env" .~ [datatable] 
          & param "format" .~ ["json"] 
    r <- getWith opts root 
    return $ r ^. responseBody 

run :: T.Text -> IO (Maybe Weather) 
run yql = buildRequest yql >>= (\r -> return $ decode r :: IO (Maybe Weather)) 


-- IP Lookup 
getLocation:: IO (Maybe IpResponse) 
getLocation = do 
    r <- get "http://ipinfo.io/json" 
    let body = r ^. responseBody 
    return (decode body :: Maybe IpResponse) 

- Combinatorの

runMyLocation:: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    case r of 
     Just ip -> getWeather ip 
     _ -> return Nothing 
    where getWeather = (run . (uncurry constructQuery) . (city &&& region)) 

が、それはのgetLocationスレッドとたぶんモナドの「脱出」するマッチング明示的なパターンに頼らずに一緒に実行することは可能ですか?

答えて

3

いくつかは、このアンチパターンを検討していますが、MaybeT IO aの代わりIO (Maybe a)を使用することができます。問題は、getLocationが失敗する可能性がある方法のうちの1つにのみ対処することです。—また、IO例外をスローする可能性があります。そのような観点から、Maybeをドロップして、デコードが失敗した場合には自分の例外をスローして、好きな場所にキャッチすることもできます。

+2

ところで、なぜこれは反パターンですか? – Yuuri

+4

@ Yuuri、あなたはいつも結果や 'Nothing'を得るように見えるので、ネットワークタイムアウト例外が出るかもしれません。 – dfeuer

+0

「いつもNothingの結果」のように見える理由は分かりません。ほとんどのハスケラーは、モナド変圧器がどのように「内側に」働いているかを知るためには十分に精通している必要があります。 – leftaroundabout

5

IO (Maybe Weather)ブロックの中央にタイプMaybe Weatherのブロックを持つことはうれしく、doブロックを異なるモナドに対応させてネストすることができます。

例えば、

runMyLocation :: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    return $ do ip <- r; return (getWeather ip) 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

この単純なパターンdo a <- r; return f aはあなたがが全くMaybeのためのモナドのインスタンスを必要としないことを示している - シンプルfmapは十分

runMyLocation :: IO (Maybe Weather) 
runMyLocation = do 
    r <- getLocation 
    return (fmap getWeather r) 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 

、今あなたが見ていますことは、同じパターンが再び表示されますので、あなたは

runMyLocation :: IO (Maybe Weather) 
runMyLocation = fmap (fmap getWeather) getLocation 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 
を書くことができます外 fmapがあなたの IOアクションの上にマッピングされ

、そして内側のfmapはあなたMaybe値以上のマッピングです。


私はあなたがIO (Maybe (IO (Maybe Weather)))はなくIO (Maybe Weather)で終わるようなもの(下記のコメントを参照)getWeatherの種類を誤解。

必要なものは、2層のモナドスタックを介した「結合」です。これは、モナド変換子があなたのために提供するもの、本質的である(dfeuerの答え@参照)が、Maybeの場合には、手動でこのコンビネータを書き込むことができる - あなたは

runMyLocation :: IO (Maybe Weather) 
runMyLocation = flatten $ fmap (fmap getWeather) getLocation 
    where 
    getWeather = run . (uncurry constructQuery) . (city &&& region) 
を書くことができ、その場合には

import Data.Maybe (maybe) 

flatten :: (Monad m) => m (Maybe (m (Maybe a))) -> m (Maybe a) 
flatten m = m >>= fromMaybe (return Nothing) 

は正しいタイプでなければなりません。このような複数の関数を連鎖させる場合は、flattenを複数回呼び出す必要があります。その場合は、@ dfeuerの答えに注意してモナド変換スタックを構築する方が簡単かもしれません。

私はトランスフォーマーやMTLライブラリで "flatten"と呼ばれていた関数の標準名がありますが、現時点では見つかりません。

Data.Maybeの機能fromMaybeは本質的にあなたのためのケース分析を行いますが、それを関数に抽象化しています。

+0

レスポンスを気に入っても、runMyLocation :: IO(おそらく(IO(天気)))のタイプの関数が返されます。 IOのネストを避けるための方法(多分(IO Maybe v))? – user2726995

+0

申し訳ありませんが、 'IpResponse :: IO(Maybe Weather)'ではなく、 'getWeather'の型を' IpResponse - > Weather'に誤読しました。私は明確な編集を提供します。 –

0

を変更してMaybe IpResponse->IO..とし、>>=を使用して実装してからgetLocation >>= getWeatherを実行します。 getWeatherの>>=はMaybeのもので、JustとNothingを扱い、getLocation>>= getWeatherはIOのものです。
Maybeから抽象化して、Monad: getWeather :: Monad m -> m IpResponse -> IO ..を使用しても問題なく動作します。

+0

私はあなたがここで何を言おうとしているのかは分かりませんが、何があっても見当たりません。 – dfeuer

+0

getWeatherをIpResponse-> IOに変更し、>> =を使用して実装し、getLocation >> = getWeatherを実行します。 getWeatherの>> =はMaybeからのもので、JustとNothingを処理します。もう一つのgetLocation >> = getWeatherはIOからのものです –

+0

あなたの答えを編集してこの提案を含めることができます。私は個人的にそれが好きではありませんが、合法です。あなたの答えは答えではありません。 – dfeuer