我正在编写一个用于与一些外部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)

-组合器
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线程并一起运行而无需借助显式模式匹配来“退出” Maybe Monad?

最佳答案

您可以愉快地嵌套对应于不同monad的do块,因此在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)

这个简单的do a <- r; return f a模式表明您完全不需要Maybe的monad实例-一个简单的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值上。

我误解了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)

在这种情况下,您可以写
runMyLocation :: IO (Maybe Weather)
runMyLocation = flatten $ fmap (fmap getWeather) getLocation
  where
    getWeather = run . (uncurry constructQuery) . (city &&& region)

其中应具有正确的类型。如果要像这样链接多个函数,则将需要多次调用flatten,在这种情况下,构建monad转换器堆栈可能会更容易(使用@dfeuer的答案进行警告)。

我在转换器或mtl库中称为“flatten”的函数可能有一个规范名称,但目前无法找到。

请注意,fromMaybe中的Data.Maybe函数本质上为您进行了案例分析,但将其抽象为一个函数。

关于haskell - IO类型的链接功能(可能是),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32442173/

10-15 04:43