Haskell 根据字段名称字符串动态设置记录字段? [英] Haskell dynamically set record field based on field name string?

查看:26
本文介绍了Haskell 根据字段名称字符串动态设置记录字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有以下记录:

data Rec = Rec {
   field1 :: Int,
   field2 :: Int
}

我如何编写函数:

changeField :: Rec -> String -> Int -> Rec
changeField rec fieldName value 

这样我就可以将字符串field1"或field2"传入fieldName 参数并让它更新关联的字段?我知道 Data.DataData.Typeable 是在这里使用的,但我无法弄清楚这两个包.

such that I can pass in the strings "field1" or "field2" into the fieldName argument and have it update the associated field? I understand Data.Data and Data.Typeable are what to use here but I can't figure these two packages out.

我见过的一个库的例子是 cmdArgs.下面是一篇关于如何使用这个库的博客文章的摘录:

An example of a library I've seen do this is cmdArgs. Below is an excerpt from a blog posting on how to use this library:

{-# LANGUAGE DeriveDataTypeable #-}
import System.Console.CmdArgs

data Guess = Guess {min :: Int, max :: Int, limit :: Maybe Int} deriving (Data,Typeable,Show)

main = do
    x <- cmdArgs $ Guess 1 100 Nothing
    print x

现在我们有了一个简单的命令行解析器.一些示例交互是:

Now we have a simple command line parser. Some sample interactions are:

$ guess --min=10
NumberGuess {min = 10, max = 100, limit = Nothing}

推荐答案

好的,这里有一个不使用模板 haskell 的解决方案,也不需要您手动管理字段映射.

OK, here's a solution that doesn't use template haskell, or require you to manage the field map manually.

我实现了一个更通用的 modifyField,它接受一个 mutator 函数,并实现了 setField (nee changeField) 和 const 一起使用价值.

I implemented a more general modifyField which accepts a mutator function, and implemented setField (nee changeField) using it with const value.

modifyFieldsetField 的签名在记录和 mutator/value 类型中都是通用的;但是,为了避免 Num 的歧义,调用示例中的数字常量必须具有明确的 :: Int 签名.

The signature of modifyField and setField is generic in both the record and mutator/value type; however, in order to avoid Num ambiguity, the numeric constants in the invocation example have to be given explicit :: Int signatures.

我还改变了参数顺序,所以 rec 排在最后,允许通过正常的函数组合创建一个 modifyField/setField 链(见最后一个调用示例).

I also changed the parameter order so rec comes last, allowing a chain of modifyField/setField to be created by normal function composition (see the last invocation example).

modifyField 建立在原始 gmapTi 之上,它是 Data.Data 中的一个缺失"函数.它是 gmapTgmapQi 之间的交叉.

modifyField is built on top of the primitive gmapTi, which is a 'missing' function from Data.Data. It is a cross between gmapT and gmapQi.

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RankNTypes #-}

import Data.Typeable (Typeable, typeOf)
import Data.Data (Data, gfoldl, gmapQi, ConstrRep(AlgConstr),
                  toConstr, constrRep, constrFields)
import Data.Generics (extT, extQ)
import Data.List (elemIndex)
import Control.Arrow ((&&&))

data Rec = Rec {
    field1 :: Int,
    field2 :: String
} deriving(Show, Data, Typeable)

main = do
  let r = Rec { field1 = 1, field2 = "hello" }
  print r
  let r' = setField "field1" (10 :: Int) r
  print r'
  let r'' = setField "field2" "world" r'
  print r''
  print . modifyField "field1" (succ :: Int -> Int) . setField "field2" "there" $ r
  print (getField "field2" r' :: String)

---------------------------------------------------------------------------------------

data Ti a = Ti Int a

gmapTi :: Data a => Int -> (forall b. Data b => b -> b) -> a -> a
gmapTi i f x = case gfoldl k z x of { Ti _ a -> a }
  where
    k :: Data d => Ti (d->b) -> d -> Ti b
    k (Ti i' c) a = Ti (i'+1) (if i==i' then c (f a) else c a)
    z :: g -> Ti g
    z = Ti 0

---------------------------------------------------------------------------------------

fieldNames :: (Data r) => r -> [String]
fieldNames rec =
  case (constrRep &&& constrFields) $ toConstr rec of
    (AlgConstr _, fs) | not $ null fs -> fs
    otherwise                         -> error "Not a record type"

fieldIndex :: (Data r) => String -> r -> Int
fieldIndex fieldName rec =
  case fieldName `elemIndex` fieldNames rec of
    Just i  -> i
    Nothing -> error $ "No such field: " ++ fieldName

modifyField :: (Data r, Typeable v) => String -> (v -> v) -> r -> r
modifyField fieldName m rec = gmapTi i (e `extT` m) rec
  where
    i = fieldName `fieldIndex` rec
    e x = error $ "Type mismatch: " ++ fieldName ++
                             " :: " ++ (show . typeOf $ x) ++
                           ", not " ++ (show . typeOf $ m undefined)

setField :: (Data r, Typeable v) => String -> v -> r -> r
setField fieldName value = modifyField fieldName (const value)

getField :: (Data r, Typeable v) => String -> r -> v
getField fieldName rec = gmapQi i (e `extQ` id) rec
  where
    i = fieldName `fieldIndex` rec
    e x = error $ "Type mismatch: " ++ fieldName ++
                             " :: " ++ (show . typeOf $ x) ++
                           ", not " ++ (show . typeOf $ e undefined)

这篇关于Haskell 根据字段名称字符串动态设置记录字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆