Получить столбец в Haskell CSV и определить тип столбца
Я изучаю CSV-файл в интерактивном сеансе GHCI (в блокноте Jupyter):
import Text.CSV
import Data.List
import Data.Maybe
dat <- parseCSVFromFile "/home/user/data.csv"
headers = head dat
records = tail dat
-- define a way to get a particular row by index
indexRow :: [[Field]] -> Int -> [Field]
indexRow csv index = csv !! index
indexRow records 1
-- this works!
-- Now, define a way to get a particular column by index
indexField :: [[Field]] -> Int -> [Field]
indexField records index = map (\x -> x !! index) records
Хотя это работает, если я заранее знаю тип столбца 3:
map (\x -> read x :: Double) $ indexField records 3
Как я могу спросить read
определить, какой тип может быть, когда, например, мои столбцы могут содержать строки или num? Я хотел бы попробовать это для меня, но:
map read $ indexField records 3
не удается с
Prelude.read: no parse
Мне все равно, являются ли они строковыми или num, мне просто нужно, чтобы все они были одинаковыми, и я не могу найти способ указать это вообще с помощью функции чтения, по крайней мере.
Странно, если я определю среднюю функцию следующим образом:
mean :: Fractional a => [a] -> Maybe a
mean [] = Nothing
mean [x] = Just x
mean xs = Just (sum(xs) / (fromIntegral (length xs)))
Это работает:
mean $ map read $ indexField records 2
Just 13.501359655240003
Но без значения это все равно не получается
map read $ indexField records 2
Prelude.read: no parse
1 ответ
К несчастью, read
в конце своего ума, когда дело доходит до таких ситуаций. Давайте вернемся read
:
read :: Read a => String -> a
Как вы видете, a
не зависит от ввода, но исключительно от вывода и, следовательно, от контекста нашей функции. Если вы используете read a + read b
тогда дополнительный Num
контекст будет ограничивать типы Integer
или же Double
из-за default
правила. Давайте посмотрим на это в действии:
> :set +t
> read "1234"
*** Exception: Prelude.read: no parse
> read "1234" + read "1234"
2468
it :: (Num a, Read a) => a
В порядке, a
все еще не помогает. Есть ли какой-нибудь тип, который мы можем прочитать без дополнительного контекста? Конечно, единица измерения:
> read "()"
()
it :: Read a => a
Это все еще бесполезно, поэтому давайте включим ограничение мономорфизма:
> :set -XMonomorphismRestriction
> read "1234" + read "1234"
2468
it :: Integer
Ага. В итоге у нас был Integer
, Из-за +
нам пришлось определиться с типом. Теперь с MonomorphismRestriction
включено, что происходит на read "1234"
без дополнительного контекста?
> read "1234"
<interactive>:20:1
No instance for (Read a0) arising from a use of 'read'
The type variable 'a0' is ambiguous
Теперь GHCi не выбирает какой-либо (по умолчанию) тип и заставляет вас выбрать один. Что делает основную ошибку намного более ясной.
Так как же это исправить? Поскольку CSV может содержать произвольные поля во время выполнения и все типы определяются статически, мы должны обмануть, введя что-то вроде
data CSVField = CSVString String | CSVNumber Double | CSVUnknown
а потом написать
parse :: Field -> CSVField
В конце концов, наш тип должен охватывать все возможные поля.
Однако в вашем случае мы можем просто ограничить read's
тип:
myRead :: String -> Double
myRead = read
Но это не разумно, так как мы все равно можем получить ошибки, если столбец не содержит Double
с начала. Итак, вместо этого, давайте использовать readMaybe
а также mapM
:
columnAsNumbers :: [Field] -> Maybe [Double]
columnAsNumbers = mapM readMaybe
Таким образом, тип является фиксированным, и мы вынуждены проверить, есть ли у нас Just
что-то или Nothing
:
mean <$> columnAsNumbers (indexFields records 2)
Если вы часто используете columnAsNumbers
создать оператора, хотя:
(!!$) :: [[Field]] -> Maybe [Double]
records !!$ index = columnAsNumbers $ indexFields records index