Соответствие шаблону на охранном выражении

#haskell

Вопрос:

Предположим, у меня есть какой-то очень простой код:

 import qualified Data.Map as Map import Data.Maybe(fromJust)  keySum :: Map.Map Char Int -gt; Char -gt; Char -gt; Either String Int keySum m key1 key2  | val1 == Nothing = Left $ show val1    " not in map"  | val2 == Nothing = Left $ show val2    " not in map"  | otherwise = Right $ val1'   val2'  where   val1 = Map.lookup key1 m  val2 = Map.lookup key2 m  val1' = fromJust val1  val2' = fromJust val2  

Конечно, я не хочу использовать fromJust , и предпочел бы, чтобы шаблон совпадал с результатом Map.lookup здесь.

Но как я могу этого достичь, не оценивая Map.lookup дважды или не передавая его через какую-либо функцию-оболочку?

Комментарии:

1. Я не понимаю, почему это неправильно использовать fromJust . Вы можете избежать дополнительных переменных и записать otherwise = Right $ fromJust val1 fromJust val2

2. Или, если вы хотите быть особенно крутым, сделайте что-нибудь подобное: | otherwise = Right . fromJust $ ( ) lt;$gt; val1 lt;*gt; val2

3. @user1984 Вы всегда должны предполагать, что его неправильно использовать fromJust , если только вы не видите очень веской причины, почему это правильно (единственное, что я могу придумать навскидку mfix Maybe ).

4. О, спасибо, что дали мне знать @JosephSible-RestorateMonica, я очень новичок в Haskell и только начинаю учиться.

Ответ №1:

Вы можете использовать защиту от шаблонов:

 keySum m key1 key2  | Just val1 lt;- Map.lookup key1 m  , Just val2 lt;- Map.lookup key2 m  = Right $ val1   val2  | key1 `Map.notMember` m  = Left $ show key1    " not in map"  | otherwise = Left $ show key2    " not in map"  

Но, возможно, старый добрый case на самом деле более чистый вариант:

 keySum m k₀ k₁ = case (`Map.lookup` m) lt;$gt; [k₀,k₁] of  [Just v₀, Just v₁] -gt; Right $ v₀   v₁  [Nothing, _ ] -gt; Left $ show k₀    " not in map"  _ -gt; Left $ show k₁    " not in map"  

Это можно еще больше упростить, заметив, что это всего лишь стандартная прикладная цепочка:

 keySum m k₀ k₁ = ( ) lt;$gt; lku k₀ lt;*gt; lku k₁  where lku k = case Map.lookup k m of  Just v -gt; Right v  Nothing -gt; Left $ show k    " not in map"  

Если вы разбираетесь в шаблонах, это также можно написать таким образом (лично я не такой поклонник этого расширения):

 {-# LANGUAGE ViewPatterns #-}  keySum m k₀ k₁ = ( ) lt;$gt; lku k₀ lt;*gt; lku k₁  where lku ((`Map.lookup` m) -gt; Just v) = Right v  lku k = Left $ show k    " not in map"  

И, наконец, нет причин ограничивать это только двумя ключами, с таким же успехом это может быть последовательность по всему списку произвольной длины. Кстати, нет никаких причин выбирать конкретные типы.

 import Data.Traversable (forM)  keySum :: (Ord a, Show a, Num b) =gt; Map.Map a b -gt; [a] -gt; Either String b keySum m ks = sum lt;$gt; forM ks `id` k -gt; case Map.lookup k m of  Just v -gt; Right v  Nothing -gt; Left $ show k    " not in map"  

Комментарии:

1. Ах, используя прикладной оператор. Спасибо.