在 Haskell 中不存在键的查找中使用 Either

Using Either in a lookup where no key exists in Haskell

我正在关注这个:

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

我正在尝试通过 headers 中的变量名从 CSV 中获取一列数据,前面部分给出了

这是我的代码:

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

有效:

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

和headers如下:

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

我有以下按字段索引 name 而不是 index

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

这也有效:

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

但是,当在 headers:

中找不到密钥时,我希望它能提供更多信息
Prelude> indexFieldbyName records "meow"
Maybe.fromJust: Nothing

这是我的尝试:

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’

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’

以上是缩进问题,just 必须在 case 的右边。现在我有:

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")

清理你的函数,你有:

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"

index 的类型是 Either String IntindexField records 需要 Int。您可以编写另一个 case,或使用 fmap,但您可能会发现代码更易读:

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"

或者使用更多辅助函数,例如 maybe 而不是 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。键入的答案,未经测试。

嗯,在讨论中解决了。

原来有两个问题:

第一个缩进是错误的。在 haskell wikibook 有一点阅读。简而言之,当代码与 where 在同一行时,以下行需要从与 where 之后的第一个单词相同的字符开始。但是由于 Just/Nothing 属于上一行中的情况,因此需要另一层缩进。

第二个是在处理仿函数时经常发生的事情。 return 类型不再是 Int 而是 f Int。所以indexField records index需要改写为fmap (indexField records) index。或者因为这是一个常见的模式,所以它有一个运算符 <$> 所以 (indexField records) <$> index。 (想想你也可以将原始语句写成 (indexField records) $ index)。