Получить столбец в 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
Другие вопросы по тегам