Получить столбец в Haskell CSV и определить тип столбца - PullRequest
0 голосов
/ 28 декабря 2018

Я исследую 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

Мне все равно, являются ли они строкой или числом, мне просто нужно, чтобы они быливсе равно, и я не могу найти способ указать это, как правило, с помощью функции чтения, по крайней мере.

Странно, если я определяю среднюю функцию следующим образом:

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 Ответ

0 голосов
/ 28 декабря 2018

К сожалению, 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

В конце концов, нашtype должен охватывать все возможные поля.

Однако в вашем случае мы можем просто ограничить read's type:

myRead :: String -> Double
myRead = read

Но это не разумно, так какмы все равно можем получить ошибки, если столбец не содержит Double s для начала.Поэтому вместо этого давайте используем 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...