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

标签 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)

-- 组合器

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?

最佳答案

可以愉快地嵌套了do对应于不同 monad 的 block ,因此拥有 Maybe Weather 类型的 block 就可以了。在你的IO (Maybe Weather)中间 block 。

例如,

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 的答案中有警告)。

我在 Transformer 或 mtl 库中称为“flatten”的函数可能有一个规范名称,但我目前找不到它。

请注意函数 fromMaybe来自Data.Maybe本质上是为您进行案例分析,但将其抽象为函数。

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

相关文章:

haskell - 如何隐藏数据中的姓名和年龄 People a = Person { name::a ,age::Int} 派生 Show in haskell

haskell - 使用 Number 作为 Monoid

haskell - 为什么管道定义内部功能

haskell - 表示红黑树的最有效方法是什么?

haskell - 在 Haskell 中声明和使用种类

haskell - 将 Haskell IO 列表转换为列表类型

haskell "invalid type signature error"

haskell - 导出具有不同层次结构的 haskell 模块

haskell - 使用 http-client-tls 连接到 Azure Datamarket 时管道损坏

haskell - 我们可以在 Haskell 中调整 "a -> a"函数吗?