Использование Either в поиске, где в Haskell нет ключа
Я следую за этим:
https://www.schoolofhaskell.com/school/starting-with-haskell/basics-of-haskell/10_Error_Handling
И я пытаюсь получить столбец данных из CSV по имени переменной в заголовках, ранее приведенная здесь часть.
Вот мой код:
import Text.CSV
import Data.List
import Data.Maybe
dat <- parseCSVFromFile "/home/user/data.csv"
headers = head dat
records = tail dat
indexField :: [[Field]] -> Int -> [Field]
indexField records index = map (\x -> x !! index) records
который работает:
Prelude> indexField records 0
[1,2,3]
И заголовки следующие:
Prelude> headers
["id", "category", "value"]
У меня есть следующее для индексации по имени поля, а не по индексу
indexFieldbyName :: [[Field]] -> String -> [Field]
indexFieldbyName records indexName = indexField records (fromJust (elemIndex indexName headers))
Который также работает:
Prelude> indexFieldbyName records "id"
[1,2,3]
Тем не менее, я хочу, чтобы это не помогло более информативно, когда ключ не найден в headers
:
Prelude> indexFieldbyName records "meow"
Maybe.fromJust: Nothing
Вот мои попытки:
indexFieldbyName2 :: [[Field]] -> String -> Either String [Field]
indexFieldbyName2 records indexName = indexField records index
where index = case (elemIndex indexName headers) of
Just v -> Right (v)
Nothing -> Left ("Index not found")
Parse error (line 31, column 5): parse error on input ‘Just’
А также
indexFieldbyName3 :: [[Field]] -> String -> Either String [Field]
indexFieldbyName3 records indexName = indexField records index
where index = case v of
Just (elemIndex indexName headers) -> Right (v)
Nothing -> Left ("Index not found")
Parse error (line 44, column 4): parse error on input ‘Just’
indexFieldbyName4 :: [[Field]] -> String -> Either String [Field]
indexFieldbyName4 records indexName = indexField records index
where index = case v of
Just v -> Right (elemIndex indexName headers)
Nothing -> Left ("Index not found")
Parse error (line 37, column 4): parse error on input ‘Just’
Выше были проблемы с отступами, just
должен был быть справа от case
, Теперь у меня есть:
indexFieldbyName2' :: [[Field]] -> String -> Either String [Field]
indexFieldbyName2' records indexName = indexField records index
where index = case (elemIndex indexName headers) of
Just v -> Right (v)
Nothing -> Left ("Index not found")
<interactive>:4:39: error:
• Couldn't match expected type ‘Either String [Field]’ with actual type ‘[Field]’
• In the expression: indexField records index
In an equation for ‘indexFieldbyName2’:
indexFieldbyName2 records indexName
= indexField records index
where
index
= case (elemIndex indexName headers) of
Just v -> Right (v)
Nothing -> Left ("Index not found")
<interactive>:4:58: error:
• Couldn't match expected type ‘Int’ with actual type ‘Either String Int’
• In the second argument of ‘indexField’, namely ‘index’
In the expression: indexField records index
In an equation for ‘indexFieldbyName2’:
indexFieldbyName2 records indexName
= indexField records index
where
index
= case (elemIndex indexName headers) of
Just v -> Right (v)
Nothing -> Left ("Index not found")
2 ответа
Ну, решение произошло в обсуждении.
Оказывается, было две проблемы:
Первый отступ был неверным. В Haskell Wikibook есть немного прочитанного. Короче говоря, когда код находится в той же строке, что и where
следующие строки должны начинаться с того же символа, что и первое слово после символа where. Но так как Just/Nothing относится к случаю в предыдущей строке, необходим другой уровень отступа.
Второе было обычным явлением при работе с функторами. Тип возврата больше не был Int
но f Int
, Так indexField records index
нужно переписать в fmap (indexField records) index
, Или, поскольку это обычная модель, она получила оператор <$>
так чтобы (indexField records) <$> index
, (Подумайте, что вы могли бы также написать оригинальное утверждение как (indexField records) $ index
).
Очистка вашей функции, у вас есть:
indexFieldbyName2' :: [[Field]] -> String -> Either String [Field]
indexFieldbyName2' records indexName = indexField records index
where index = case elemIndex indexName headers of
Just v -> Right v
Nothing -> Left "Index not found"
Тип index
является Either String Int
в то время как indexField records
требует Int
, Вы можете написать другое case
или используйте fmap
, но вы могли бы найти код более читабельным прямой:
indexFieldbyName3 :: [[Field]] -> String -> Either String [Field]
indexFieldbyName3 records indexName =
case elemIndex indexName headers of
Just v -> Right (indexField records v)
Nothing -> Left "Index not found"
Или используя больше вспомогательных функций, таких как maybe
вместо case
:
indexFieldbyName4 :: [[Field]] -> String -> Either String [Field]
indexFieldbyName4 records indexName =
let midx = elemIndex indexName headers
err = Left "Index not found"
in maybe err (Right . indexField records) midx
NB Ответ набран, не проверен.