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