类型IO的链接函数(可能是a)

use*_*995 5 haskell

我正在编写一个小型库,用于与一些外部API进行交互.一组函数将构造对yahoo api的有效请求,并将结果解析为数据类型.另一组功能将基于IP查找用户当前位置并返回表示当前位置的数据类型.虽然代码有效,但似乎必须显式模式匹配才能对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)
Run Code Online (Sandbox Code Playgroud)

- 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))
Run Code Online (Sandbox Code Playgroud)

是否有可能将getLocation线程化并一起运行而不需要使用显式模式匹配来"淘汰"Maybe Monad?

Chr*_*lor 5

你可以愉快地嵌套do对应于不同monad的块,所以在块Maybe Weather的中间有一个类型块就好了IO (Maybe Weather).

例如,

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

这个简单的模式do a <- r; return f a表明你根本不需要monad实例Maybe- 一个简单fmap就足够了

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

现在你看到同样的模式再次出现,所以你可以写

runMyLocation :: IO (Maybe Weather)
runMyLocation = fmap (fmap getWeather) getLocation
  where
    getWeather = run . (uncurry constructQuery) . (city &&& region)
Run Code Online (Sandbox Code Playgroud)

外部fmap映射到您的IO操作,内部fmap映射您的Maybe值.


我误解了getWeather(见下面的评论)的类型,这样你最终会得到IO (Maybe (IO (Maybe Weather)))而不是IO (Maybe Weather).

你需要的是通过两层monad堆栈的"连接".这基本上是monad变换器为你提供的(参见@dfeuer的回答)但是在以下情况下可以手动编写这个组合器Maybe-

import Data.Maybe (maybe)

flatten :: (Monad m) => m (Maybe (m (Maybe a))) -> m (Maybe a)
flatten m = m >>= fromMaybe (return Nothing)
Run Code Online (Sandbox Code Playgroud)

在这种情况下你可以写

runMyLocation :: IO (Maybe Weather)
runMyLocation = flatten $ fmap (fmap getWeather) getLocation
  where
    getWeather = run . (uncurry constructQuery) . (city &&& region)
Run Code Online (Sandbox Code Playgroud)

哪个应该是正确的类型.如果您要链接多个这样的函数,则需要多次调用flatten,在这种情况下,构建monad变换器堆栈可能更容易(在@dfeuer的答案中有警告).

我在变换器或mtl库中称之为"flatten"的函数可能有一个规范名称,但目前我找不到它.

请注意该函数fromMaybe从Data.Maybe本质上是做案例分析你,但它抽象成一个函数.


dfe*_*uer 3

有些人认为这是一种反模式,但您可以MaybeT IO a使用IO (Maybe a). 问题是你只处理一种getLocation可能失败的方法——它也可能抛出IO异常。从这个角度来看,如果解码失败,您不妨放弃Maybe并抛出自己的异常,并在任何您喜欢的地方捕获它。

  • @Yuuri,因为它看起来你总是会得到结果或“什么都没有”,但你可能会得到,比如说,网络超时异常。 (4认同)
  • 顺便问一下,为什么这是一个反模式? (2认同)