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