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

Haskell IO类型的链接函数(可能是a),haskell,Haskell,我正在编写一个小型库,用于与一些外部API进行交互。一组函数将构造对yahoo api的有效请求,并将结果解析为数据类型。另一组函数将基于IP查找用户的当前位置,并返回表示当前位置的数据类型。当代码工作时,它似乎必须显式地进行模式匹配,以对多个IO类型的函数(可能是a)进行排序 -- Yahoo API constructQuery :: T.Text -> T.Text -> T.Text constructQuery city state = "select astronomy

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

是否有可能在不使用显式模式匹配的情况下将GET定位并运行在一起,以获得可能的单元格?

< P>有些认为这是反模式,但您可以使用MayBET IO A而不是IO A。问题是,您只能处理getLocation失败的一种方式,它也可能引发IO异常。从这个角度看,如果解码失败,你也可以放弃它,只要你喜欢的地方就可以捕捉到它。

< P>你可以愉快地嵌套对应于不同单子的块,所以在你的IO中可能有一个类型的天气可能是天气块。 比如说,

runMyLocation :: IO (Maybe Weather)
runMyLocation = do
    r <- getLocation
    return $ do ip <- r; return (getWeather ip)
  where
    getWeather = run . (uncurry constructQuery) . (city &&& region)
现在你看到同样的模式再次出现,所以你可以写

runMyLocation :: IO (Maybe Weather)
runMyLocation = fmap (fmap getWeather) getLocation
  where
    getWeather = run . (uncurry constructQuery) . (city &&& region)
runMyLocation :: IO (Maybe Weather)
runMyLocation = flatten $ fmap (fmap getWeather) getLocation
  where
    getWeather = run . (uncurry constructQuery) . (city &&& region)
外部fmap映射到您的IO操作,内部fmap映射到您的值

我曲解了getWeather的类型,请参见下面的评论,这样您将以IO Maybe IO Maybe Weather而不是IO Maybe Weather结束

您需要的是通过两层monad堆栈进行连接。这基本上就是monad transformer为您提供的内容,请参见@dfeur的答案,但在可能的情况下,可以手动编写此组合器-

在这种情况下你可以写

runMyLocation :: IO (Maybe Weather)
runMyLocation = fmap (fmap getWeather) getLocation
  where
    getWeather = run . (uncurry constructQuery) . (city &&& region)
runMyLocation :: IO (Maybe Weather)
runMyLocation = flatten $ fmap (fmap getWeather) getLocation
  where
    getWeather = run . (uncurry constructQuery) . (city &&& region)
应该有正确的类型。如果您要像这样链接多个函数,您将需要多个调用来扁平化,在这种情况下,使用@dfeuer的答案中的警告构建monad transformer堆栈可能更容易

我在transformers或mtl库中调用的Flatte函数可能有一个规范名称,但我目前找不到它


请注意,函数from可能来自数据。可能本质上是为您进行案例分析,但将其抽象为一个函数。

将getWeather更改为具有可能的IpResponse->IO。。并使用>>=实现它,然后可以执行getLocation>>=getWeather。getWeather中的>>=是来自Maybe的,它将处理Just和Nothing,而另一个getLocation>=getWeather是来自IO的。
您甚至可以从Maybe中提取并使用任何Monad:getWeather::Monad m->m IpResponse->IO。。而且会起作用。

顺便问一下,为什么这是一个反模式?@Yuuri,因为它让你看起来总是会得到一个结果或什么都没有,但你可能会得到,比如说,一个网络超时异常。我不明白你为什么认为它看起来像“总是一个什么都没有的结果”。大多数哈斯凯尔人应该对单子变形金刚足够熟悉,知道它们是如何“由内而外”工作的。@leftaroundabout,我确信我读过一篇反对这种风格的文章,但我似乎找不到。我还没有找到自己最喜欢的错误处理方法,但它们似乎都有不同的缺点,所有语言都是如此。感谢您的响应,不过这会导致函数类型为:runMyLocation::IO Maybe IO Maybe Weather。有没有办法避免IO Maybe IO Maybe v的嵌套?抱歉,我把getWeather的类型误读为IpResponse->Weather而不是IpResponse::IO Maybe Weather。我将提供一个澄清的编辑。我不确定您在这里想说什么,但无论它似乎缺少什么。更改getWeather以获得IpResponse->IO。。并使用>>=实现它,然后可以执行getLocation>>=getWeather。getWeather中的>>=是来自Maybe的,它将处理Just and Nothing,而另一个getLocation>=getWeather是来自IOU的,您可以编辑您的答案以包含此建议。我个人不喜欢,但这是合法的。就目前情况而言,你的答案不是一个答案。我同意这并不优雅,但我也不喜欢你的建议,仅在这种情况下使用变压器。不管怎么说,根据我的建议,你甚至可以从中提取。getWeather::Monad m->m IpResponse->IO。。而且会起作用