我撰寫了一個函式來從 API 查詢貨幣匯率。它作業正常,但代碼太長且不可讀。我想有人可以幫我簡化這個,特別是因為有很多重復的模式和運算子,比如重復使用
編輯:我沒有意識到系結任何東西pure是絕對沒用的!
... <&> (=<<) (something >>= pure) ...
我剛剛開始學習 Haskell,因此不知道很多可以在這里使用的聰明的運算子/函式/鏡頭。
順便說一句,我知道 do-notation 存在。
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))
收到的 JSON 看起來像這樣
{"rates":{"EURUSD":{"rate":1.087583,"timestamp":1649600523}},"code":200}
提前致謝。
uj5u.com熱心網友回復:
哇,太長了。讓我們一步一步來;最后,我們將看到以下代碼片段,我發現它更自然地閱讀,但執行完全相同的計算:
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)
讓我們看看如何。
首先,我認為如果有策略地選擇換行符,則更容易查看和編輯。
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))單子定律之一是,所以讓我們到處
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))另一個單子定律是
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))的使用
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))我的心理型別檢查器快瘋了。讓我們把這個計算分成三種不同的東西:一種適用于
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))現在讓我們拆分
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))讓我們把純凈的部分分開。函子定律之一是
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))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)
可能還有其他可以做的美化事情(我想到了添加型別簽名,我有幾個想法可以改變/改進代碼的行為),但我認為到目前為止你已經有了一些相當合理的閱讀和理解. 一路走來,讓事物變得可讀,作為一個副作用,消除了你發現令人不安的重復代碼片段,所以這是一個小小的好處;但是,如果它們仍然存在,那么嘗試將它們作為額外的步驟來解決是很自然的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/462858.html
標籤:哈斯克尔
上一篇:從自定義資料型別中獲取值
