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

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

问题描述

假设我有以下记录:

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

如何编写函数:

  changeField :: Rec  - >字符串 - > Int  - > Rec 
changeField rec fieldName value

这样我就可以传递字符串field1或field2放入 fieldName 参数中并让它更新相关字段?我理解 Data.Data Data.Typeable 是在这里使用的,但我无法将这两个包。




我见过的库的一个例子是cmdArgs。以下是关于如何使用此库的博客文章的摘录

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

数据Guess =猜测{min :: Int,max :: Int,limit :: Maybe Int}派生(Data,Typeable,Show)

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

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

  $ guess --min = 10 
NumberGuess {min = 10,max = 100,限制=无}}


解决方案



我实现了一个更通用的修改字段,并且使用模板haskell,或者要求您手动管理字段映射。它接受一个mutator函数,并使用它与 const值一起实现 setField (nee changeField >)


$ b modifyField setField 在record和mutator / value类型中都是通用的;然而,为了避免 Num 歧义,调用示例中的数字常量必须显式给出 :: Int 签名。



我也改变了参数顺序,所以 rec 最后出现,允许一个修改字段 / setField 由普通函数组合创建(参见上一个调用示例)。

modifyField 建立在基元 gmapTi 之上,它是 Data.Data 。它是 gmapT gmapQi 之间的交叉点。



<$ p $ b $ {$#$ c $ {$#$ $ $ $ $ $ $ $'$ $ $ $ $ $'$ $ $ $'导入Data.Data(Data,gfoldl,gmapQi,ConstrRep(AlgConstr),
toConstr,constrRep,constrFields)
导入Data.Generics(extT,extQ)
导入Data.List(elemIndex)
导入Control.Arrow((&&&))

数据Rec = Rec {
field1 :: Int,
field2 :: String







$ r $ = $ {$ field $ b让r'= setFieldfield1(10 :: Int)r
print r'
let r''= setFieldfield2worldr'
print r''
印刷。 modifyFieldfield1(succ :: Int - > Int)。 setFieldfield2there$ r
print(getFieldfield2r':: String)

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

data Ti a = Ti Int a

gmapTi :: Data a => ; Int - > (全部b数据b => b→b)→> a - > a
gmapTi i f x = {Ti _ a - >> a}
其中
k :: Data d => Ti(d-> b) - > d - >如果i == i,那么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)|不是$ null fs - > fs
其他 - >错误不是记录类型

fieldIndex ::(Data r)=>字符串 - > r - > Int
fieldIndex fieldName rec =
case fieldName`elemIndex` fieldNames rec of
Just i - >我
无 - > $ fieldName

modifyField ::(Data r,Typeable v)=>字符串 - > (v→v)→> r - > r
modifyField fieldName m rec = gmapTi i(e`extT` m)rec
其中
i = fieldName`fieldIndex` rec
ex = error $类型不匹配:++ fieldName ++
::++(show。typeOf $ x)++
,而不是++(show。typeOf $ m undefined)

setField :: (Data r,Typeable v)=>字符串 - > v - > r - > r
setField fieldName value = modifyField fieldName(const value)
$ b $ getField ::(Data r,Typeable v)=>字符串 - > r - > v
getField fieldName rec = gmapQi i(e`extQ` id)rec
其中
i = fieldName`fieldIndex` rec
ex = error $类型不匹配:++ fieldName + +
::++(show。typeOf $ x)++
,not++(show。typeOf $ e undefined)


Say I have the following record:

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

How do I write the function:

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

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.


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}

解决方案

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

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

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.

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 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天全站免登陆