#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
или больше или равно длине списка групп населения. Я оставляю это как упражнение для реализации более безопасного варинта.