我编写了一个函数来通过 API 查询货币汇率。它工作正常,但代码太长且难以阅读。我认为有人能够帮助我简化这个过程,特别是因为有许多重复的模式和运算符,例如重复使用
编辑:我没有意识到绑定任何东西pure是绝对没用的!
... <&> (=<<) (something >>= pure) ...
Run Code Online (Sandbox Code Playgroud)
我刚刚开始学习 Haskell,因此不知道可以在这里使用的许多聪明的运算符/函数/镜头。
顺便说一句,我知道存在 do 符号。
forex :: (String, String) -> IO (Maybe (Scientific, UnixTime))
forex cp = (get ("https://www.freeforexapi.com/api/live?pairs=" ++ uncurry (++) cp) <&> decode . flip (^.) responseBody <&> (=<<) (parseMaybe (.: "rates") >>= pure) :: IO (Maybe (Map Key (Map Key Scientific)))) <&> (=<<) (Data.Map.lookup (fromString (uncurry (++) cp)) >>= pure) <&> (=<<) ((pure . toList) >>= pure) <&> (=<<) (pure . map snd >>= pure) <&> fmap (\y -> (head y, UnixTime ((CTime . fromRight 0 . floatingOrInteger) (y !! 1)) 0))
Run Code Online (Sandbox Code Playgroud)
收到的 JSON 看起来像这样
{"rates":{"EURUSD":{"rate":1.087583,"timestamp":1649600523}},"code":200}
Run Code Online (Sandbox Code Playgroud)
提前致谢。
Dan*_*ner 13
哇,那太长了。让我们一步一步来;最后,我们将得到以下代码片段,我发现它读起来更自然,但执行完全相同的计算:
forex (c, p) = extractFirstTime c p
<$> get ("https://www.freeforexapi.com/api/live?pairs=" ++ c ++ p)
extractFirstTime c p response = firstTime
<$> parseAndLookUp c p (response ^. responseBody)
parseAndLookUp c p body =
decode body >>=
parseMaybe (.: "rates") >>=
Data.Map.lookup (fromString (c ++ p))
firstTime = case Data.Map.elems m of
k:t:_ -> (k, UnixTime ((CTime . fromRight 0 . floatingOrInteger) t) 0)
Run Code Online (Sandbox Code Playgroud)
让我们看看如何。
首先,我认为如果有策略性地选择换行符,则更容易查看和编辑。
forex cp =
(get ("https://www.freeforexapi.com/api/live?pairs=" ++ uncurry (++) cp)
<&> decode . flip (^.) responseBody
<&> (=<<) (parseMaybe (.: "rates") >>= pure)
:: IO (Maybe (Map Key (Map Key Scientific)))
)
<&> (=<<) (Data.Map.lookup (fromString (uncurry (++) cp)) >>= pure)
<&> (=<<) ((pure . toList) >>= pure)
<&> (=<<) (pure . map snd >>= pure)
<&> fmap (\y -> (head y, UnixTime ((CTime . fromRight 0 . floatingOrInteger) (y !! 1)) 0))
Run Code Online (Sandbox Code Playgroud)
单子定律之一是m >>= pure = m,所以让我们删除>>= pure所有地方。(第 4、7、8 和 9 行各一个。)
forex cp =
(get ("https://www.freeforexapi.com/api/live?pairs=" ++ uncurry (++) cp)
<&> decode . flip (^.) responseBody
<&> (=<<) (parseMaybe (.: "rates"))
:: IO (Maybe (Map Key (Map Key Scientific)))
)
<&> (=<<) Data.Map.lookup (fromString (uncurry (++) cp))
<&> (=<<) (pure . toList)
<&> (=<<) (pure . map snd)
<&> fmap (\y -> (head y, UnixTime ((CTime . fromRight 0 . floatingOrInteger) (y !! 1)) 0))
Run Code Online (Sandbox Code Playgroud)
另一个单子定律是m >>= pure . f = fmap f m。让我们尽可能用这条定律来简化。(第 8 行和第 9 行各一个。)
forex cp =
(get ("https://www.freeforexapi.com/api/live?pairs=" ++ uncurry (++) cp)
<&> decode . flip (^.) responseBody
<&> (=<<) (parseMaybe (.: "rates"))
:: IO (Maybe (Map Key (Map Key Scientific)))
)
<&> (=<<) Data.Map.lookup (fromString (uncurry (++) cp))
<&> fmap toList
<&> fmap (map snd)
<&> fmap (\y -> (head y, UnixTime ((CTime . fromRight 0 . floatingOrInteger) (y !! 1)) 0))
Run Code Online (Sandbox Code Playgroud)
的使用uncurry正在发生,因为我们没有对 进行模式匹配cp。让我们解决这个问题。(1、2 和 7 号线。)
forex (c, p) =
(get ("https://www.freeforexapi.com/api/live?pairs=" ++ c ++ p)
<&> decode . flip (^.) responseBody
<&> (=<<) (parseMaybe (.: "rates"))
:: IO (Maybe (Map Key (Map Key Scientific)))
)
<&> (=<<) Data.Map.lookup (fromString (c ++ p))
<&> fmap toList
<&> fmap (map snd)
<&> fmap (\y -> (head y, UnixTime ((CTime . fromRight 0 . floatingOrInteger) (y !! 1)) 0))
Run Code Online (Sandbox Code Playgroud)
我的心理类型检查器快疯了。让我们把这个计算分成三种不同的东西:一种在 中工作IO,一种在 中工作Maybe,一种是纯粹的。首先,让我们将其IO与其他所有内容分开。
forex (c, p) = extractFirstTime c p
<$> get ("https://www.freeforexapi.com/api/live?pairs=" ++ c ++ p)
extractFirstTime c p response = response
& decode . flip (^.) responseBody
& (=<<) (parseMaybe (.: "rates"))
& (=<<) Data.Map.lookup (fromString (c ++ p))
& fmap toList
& fmap (map snd)
& fmap (\y -> (head y, UnixTime ((CTime . fromRight 0 . floatingOrInteger) (y !! 1)) 0))
Run Code Online (Sandbox Code Playgroud)
现在让我们把各个部分分开Maybe。
forex (c, p) = extractFirstTime c p
<$> get ("https://www.freeforexapi.com/api/live?pairs=" ++ c ++ p)
extractFirstTime c p response = parseAndLookUp c p (response ^. responseBody)
& fmap toList
& fmap (map snd)
& fmap (\y -> (head y, UnixTime ((CTime . fromRight 0 . floatingOrInteger) (y !! 1)) 0))
parseAndLookUp c p body =
decode body >>=
parseMaybe (.: "rates") >>=
Data.Map.lookup (fromString (c ++ p))
Run Code Online (Sandbox Code Playgroud)
让我们把纯净的部分分开。函子定律之一是fmap f . fmap g = fmap (f . g),所以我们可以将这三个fmaps 合并到 中extractFirstTime。此时,(&)剩下的两个参数足够短,我们可以内联 的定义(&)。我还将使用名称(<$>)而不是fmap; 我觉得这样读起来比较清晰一些。
forex (c, p) = extractFirstTime c p
<$> get ("https://www.freeforexapi.com/api/live?pairs=" ++ c ++ p)
extractFirstTime c p response = firstTime
<$> parseAndLookUp c p (response ^. responseBody)
parseAndLookUp c p body =
decode body >>=
parseMaybe (.: "rates") >>=
Data.Map.lookup (fromString (c ++ p))
firstTime m = m
& toList
& map snd
& (\y -> (head y, UnixTime ((CTime . fromRight 0 . floatingOrInteger) (y !! 1)) 0))
Run Code Online (Sandbox Code Playgroud)
Data.Map有一个名称map snd . toList,即elems。让我们使用模式匹配来挑选我们想要的元素,而不是使用headand 。!!(所有更改均在 中firstTime。)
forex (c, p) = extractFirstTime c p
<$> get ("https://www.freeforexapi.com/api/live?pairs=" ++ c ++ p)
extractFirstTime c p response = firstTime
<$> parseAndLookUp c p (response ^. responseBody)
parseAndLookUp c p body =
decode body >>=
parseMaybe (.: "rates") >>=
Data.Map.lookup (fromString (c ++ p))
firstTime = case Data.Map.elems m of
k:t:_ -> (k, UnixTime ((CTime . fromRight 0 . floatingOrInteger) t) 0)
Run Code Online (Sandbox Code Playgroud)
可能还可以做一些额外的美化事情(我想到了添加类型签名,我有几个改变/改进代码行为的想法),但我认为到目前为止,您已经有了一些相当合理的阅读和理解内容。一路走来,使内容变得可读的一个副作用是消除了令人不安的重复代码片段,所以这是一个小小的好处;但如果它们仍然存在,那么尝试以额外步骤解决它们将是很自然的。