【问题标题】:Looking up keys (parsed as String) from a Haskell Record从 Haskell 记录中查找键(解析为字符串)
【发布时间】:2020-08-20 14:29:22
【问题描述】:

假设我有一个类似的 Haskell 记录

data HaskellRecord = HaskellRecord {
  _key1 :: Maybe String
, _key2 :: Maybe String
, _key3 :: Maybe String
}

有没有办法构造函数

getKey :: HaskellRecord -> String -> Maybe String

这样与

let haskellRecord = HaskellRecord { _key1 = Just "value1"
                                  , _key2 = Just "value2"
                                  , _key3 = Just "value3"
                                  }

然后

getKey haskellRecord "value1" == Just "_key1"
getKey haskellRecord "value2" == Just "_key2"
getKey haskellRecord "value3" == Just "_key3"
getKey haskellRecord "value4" == Nothing

【问题讨论】:

  • 所以你想返回键而不是值?
  • 您是在问是否可以手写,还是在问是否有一些预先存在的机器使用泛型或模板 Haskell 或其他东西来为您编写?
  • @JosephSible-ReinstateMonica 最好避免使用 Template Haskell?但如果这是必需的,那么可以。
  • 当记录包含不同类型的字段时getKey应该如何表现?
  • 为什么要这样做?拥有String 后,您将如何处理它?我怀疑对于您想要实现的任何顶级效果都有更好的设计。

标签: haskell


【解决方案1】:

当然你可以手写。这很容易,但有点乏味且容易出错:

getKey (HaskellRecord (Just x) _ _) y | x == y = Just "_key1"
getKey (HaskellRecord _ (Just x) _) y | x == y = Just "_key2"
getKey (HaskellRecord _ _ (Just x)) y | x == y = Just "_key3"
getKey _ _ = Nothing

看起来它也可以使用 Template Haskell 或泛型,但是没有一些预先构建的方法可以做到这一点,所以你必须自己编写它,而且它(显然)会更复杂一些.这样做的好处是您只需构建一次,而不必担心因数据类型更改而对其进行更改。

【讨论】:

    【解决方案2】:

    你可以改编我的solution for a similar problem。它使用废旧样板 (SYB) 泛型。该答案相当详细地解释了代码,所以我不会在这里解释。下面是适用于您的示例的示例代码。它支持混合了Maybe StringString字段的数据类型; getKey 会忽略其他字段类型。

    请注意,这不是会非常有效。如果这是您的应用程序逻辑的核心部分,而不是一些调试技巧,您应该考虑重新考虑您的数据类型。也许您实际上并不想要 Haskell 记录类型,而是某种双向映射(例如,Map k vMap v k 配对在一起,通过一组保持一致的函数访问)。

    {-# LANGUAGE DeriveDataTypeable #-}
    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE TypeApplications #-}
    
    import Data.Tuple
    import Data.Generics
    
    data HaskellRecord = HaskellRecord {
      _key1 :: Maybe String
    , _key2 :: Maybe String
    , _key3 :: Maybe String
    } deriving (Data)
    
    -- Get field names (empty list if not record constructor)
    getnames :: Data object => object -> [String]
    getnames = constrFields . toConstr
    
    -- Get field values as `Maybe String`s
    getfields :: Data object => object -> [Maybe String]
    getfields = gmapQ toString
    
    -- Generic function to convert one field.
    toString :: (Data a) => a -> Maybe String
    toString = mkQ    Nothing        -- make a query with default value Nothing
                      id             -- handle:          id     :: Maybe String -> Maybe String
               `extQ` Just           -- extend to:       Just   :: String -> Maybe String
    
    -- Get field name/value pairs from any `Data` object.
    getpairs :: Data object => object -> [(String, Maybe String)]
    getpairs = zip <$> getnames <*> getfields
    
    getKey :: Data record => String -> record -> Maybe String
    getKey v = lookup (Just v) . map swap . getpairs
    
    main :: IO ()
    main = do
      print $ getKey "value2" $
        HaskellRecord {
          _key1 = Just "value1",
          _key2 = Just "value2",
          _key3 = Just "value3"
          }
    

    【讨论】:

      【解决方案3】:

      我想我会这样写:

      getKey :: String -> HaskellRecord -> [String]
      getKey needle haystack =
          [ name
          | (name, selector) <- [("_key1", _key1), ("_key2", _key2), ("_key3", _key3)]
          , selector haystack == Just needle
          ]
      

      看看吧:

      > getKey "value1" haskellRecord
      ["_key1"]
      

      ...但我怀疑这是 X-Y 问题。 (因为那你打算用那个String做什么?如果答案是“把它变回选择器”或“用它来修改适当的字段”,那么为什么不返回一个镜头或其他东西?如果你实际上想要一个镜头,那么你可能一开始甚至都不想要这个数据结构......)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多