Как исправить это несоответствие типа функции поиска Haskell?

#function #haskell #lookup #type-mismatch #algebraic-data-types

Вопрос:

Если бы кто-нибудь мог помочь исправить эту ошибку, я был бы очень признателен. Код таков:

 type Name = String
type Coordinates = (Int, Int)
type Pop = Int
type TotalPop = [Pop]
type City = (Name, (Coordinates, TotalPop))

testData :: [City]
testData = [("New York City", ((1,1), [5, 4, 3, 2])),
           ("Washingotn DC", ((3,3), [3, 2, 1, 1])),
           ("Los Angeles", ((2,2), [7, 7, 7, 5]))]

getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = head ([ z !! (yearIn - 1) | (x,z) <- lookup nameIn cs])
 

Ожидаемое поведение:

 getCityPopulation testData "New York City" 2
>>> 4
 

Фактическое поведение:

     • Couldn't match expected type ‘[(a0, [Int])]’
                  with actual type ‘Maybe (Coordinates, TotalPop)’
    • In the expression: lookup nameIn cs
      In a stmt of a list comprehension: (x, z) <- lookup nameIn cs
      In the first argument of ‘head’, namely
        ‘([z !! (yearIn - 1) | (x, z) <- lookup nameIn cs])’
   |
55 | getCityPopulation cs nameIn yearIn = head ([ z !! (yearIn - 1) | (x,z) <- lookup nameIn cs])
   |                                                                           ^^^^^^^^^^^^^^^^
 

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

1. Поиск возвращает Maybe City значение, так как это может привести к сбою.

2. @WillemVanOnsem Каким было бы решение для получения ожидаемого результата?

3. Каков ожидаемый результат, если вы введете getCityPopulation testData "New York City" 2000 текст ? О чем getCityPopulation testData "Boston" 2 ?

4. @n.’местоимения’. В нем должно быть написано что-то вроде Data unavailable, check inputs again

Ответ №1:

 getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation [] nameIn yearIn = error "Can't be empty"
getCityPopulation cs nameIn yearIn = 
   head ([ z !! (yearIn - 1) | (x,z) <- maybeToList $ lookup nameIn cs])
 

делает свою работу. Это использует

 maybeToList :: Maybe a -> [a]   -- Defined in `Data.Maybe'
 

который необходим для преобразования выходных данных

 lookup :: Eq a => a -> [(a, b)] -> Maybe b
 

Конечно, этот код на самом деле должен быть

    head ([ z !! (yearIn - 1) | (x,z) <- maybeToList $ lookup nameIn cs])
 ==
   case (lookup nameIn cs) of 
      Just (x,z) ->  z !! (yearIn - 1)
      Nothing    ->  error "couldn't find it"
 

так что

 > getCityPopulation testData "New York City" 2
4

> getCityPopulation testData "Las Vegas" 2
*** Exception: couldn't find it
 

но писать код, который может так провалиться, тоже неправильно. Лучше снова завернуть результат в Maybe :

 getCityPopulation :: [City] -> Name -> Int -> Maybe Int
getCityPopulation [] nameIn yearIn = error "Can't be empty"
getCityPopulation cs nameIn yearIn = 
   case (lookup nameIn cs) of 
      Just (x,z) ->  Just $ z !! (yearIn - 1)
      Nothing    ->  Nothing
 

и тогда у нас есть

 > getCityPopulation testData "New York City" 2
Just 4

> getCityPopulation testData "Las Vegas" 2
Nothing
 

Теперь, изменив тип вывода, мы действительно можем вернуться к исходному коду-почти:

 getCityPopulation :: [City] -> Name -> Int -> Maybe Int
getCityPopulation [] nameIn yearIn = error "Can't be empty"
getCityPopulation cs nameIn yearIn = 
   [ z !! (yearIn - 1) | (x,z) <- lookup nameIn cs]
 

Что это за магия, спросите вы? Он известен как MonadComprehensions . Вы включаете его либо по приглашению GHCi с помощью

 > :set -XMonadComprehensions
 

или путем включения

 {-# LANGUAGE MonadComprehensions #-}
 

прагма в верхней части вашего исходного файла.

Однако нет Int -> Maybe a» rel=»nofollow noreferrer»>никакого волшебного решения для возможной проблемы с доступом за пределы, с которой сталкивается ваш код, используя это !! без проверки. Или есть?

 getCityPopulation :: [City] -> Name -> Int -> Maybe Int
getCityPopulation [] nameIn yearIn = error "Can't be empty"
getCityPopulation cs nameIn yearIn = 
   [ e | (_,z) <- lookup nameIn cs, 
         e <- listToMaybe $ drop (yearIn-1) z ]
 

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

1. Я все еще довольно новичок в Хаскелле, что это $ значит?

2. f $ x === f ( x )

Ответ №2:

lookup :: Eq a => a -> [(a, b)] -> Maybe b возвращает a Maybe b , а не список b s, которые совпали. В случае, если он не находит ключ в списке кортежей, Nothing возвращается, в противном случае это a Just somecity .

 getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = … (lookup nameIn cs) 

Теперь , когда у нас есть a Maybe City , мы должны решить, что делать в случае a Nothing , мы можем, например, вернуться -1 в этом случае. Для этого мы можем использовать катаморфизм Maybe типа: maybe :: b -> (a -> b) -> b , здесь мы можем , таким образом, указать, что делать в случае a Nothing , и мы передаем функцию, что делать в случае a Just x с x :

 import Data.Maybe(maybe)

getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = maybe (-1) (…) (lookup nameIn cs) 

Теперь это a City , и нам нужно получить год, который мы можем использовать snd :: (a, b) -> b для получения списка населения, а затем найти соответствующее население, так что:

 import Data.Maybe(maybe)

getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = maybe (-1) ((!! yearIn) . snd) (lookup nameIn cs) 

Возврат простого Int , однако, не очень «хаскелловский» для вычисления, которое может завершиться неудачей, обычно в этом случае тип возвращаемого значения-a Maybe Int , если результат должен быть Int , но может завершиться неудачей. Мы можем работать с fmap :: Functor f => (a -> b) -> f a -> f b отображением значения, завернутого в конструктор Just данных , или использовать Nothing , если мы сопоставляем по a Nothing , чтобы функция выглядела так:

 getCityPopulation :: [City] -> Name -> Int -> Maybe Int
getCityPopulation cs nameIn yearIn = fmap ((!! yearIn) . snd) (lookup nameIn cs) 

Затем мы получаем, например:

 Prelude> getCityPopulation testData "New York" 2
Nothing
Prelude> getCityPopulation testData "New York City" 2
Just 3
 

(!!) Функция также не очень безопасна, так как возможно yearIn , что значение меньше 0 или больше или равно длине списка групп населения. Я оставляю это как упражнение для реализации более безопасного варинта.