0

I'm following along with this:

https://www.schoolofhaskell.com/school/starting-with-haskell/basics-of-haskell/10_Error_Handling#either-may-be-better-than-maybe

And I am trying to get a column of data from a CSV by variable name in the headers, earlier part given here.

Here's my code:

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

which works:

Prelude> indexField records 0
[1,2,3]

And headers are as follows:

Prelude> headers
["id", "category", "value"]

I have the following to index by field name rather than index

indexFieldbyName :: [[Field]] -> String -> [Field]
indexFieldbyName records indexName = indexField records (fromJust (elemIndex indexName headers))

Which also works:

Prelude> indexFieldbyName records "id"
[1,2,3]

However, I want this to fail more informatively when the key is not found in headers:

Prelude> indexFieldbyName records "meow"
Maybe.fromJust: Nothing

Here are my attempts:

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’

And

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’

The above were indentation issues, the just had to be to the right of the case. Now I have:

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")
Mittenchops
  • 18,633
  • 33
  • 128
  • 246
  • 2
    How are you using <- from do notation at the top level? (As in, ` dat <- parseCSVFromFile "/home/user/data.csv"` ) – Eliza Brandt Dec 28 '18 at 12:41
  • I'm in a ghci/ihaskell/jupyter notebook session. When I change this to an equals sign, I get many errors later, I guess as it's looking for an IO wrapper. – Mittenchops Dec 28 '18 at 12:41
  • I'm not at the machine so can't test. Are you sure indentation is correct? IMO Just and Nothing need to be indented to be right of the i of index (say two spaces). Besides the first implementation would look good to me. – bdecaf Dec 28 '18 at 12:56
  • 1
    for the other question you need to stay in the functor - use fmap or <$>. e.g.: `(indexField records) <$> index` – bdecaf Dec 28 '18 at 13:24
  • 1
    That solves it. If you write that as an answer I'd happily accept. Thank you! – Mittenchops Dec 28 '18 at 13:27

2 Answers2

1

Cleaning up your function, you have:

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"

The type of index is an Either String Int while indexField records requires a Int. You can write another case, or use fmap, but you might find the code more readable straight-line:

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"

Or using more helper functions like maybe instead of 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

N.B. Answer typed, not tested.

Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
1

Well the solving happened in the discussion.

It turns out there were two issues:

First indentation was wrong. There are is a little read at haskell wikibook. In short when the code is in the same line as the where the following lines need to start at the same character as the first word after the where. But since the Just/Nothing belongs to the case in the previous line another level of indentation was needed.

Second was a common occurrence when dealing with functors. The return type was no longer an Int but an f Int. So indexField records index needs to be rewritten to fmap (indexField records) index. Or since this is a common pattern it got an operator <$> so to (indexField records) <$> index. (Think about that you could also write the original statement as (indexField records) $ index).

bdecaf
  • 4,652
  • 23
  • 44