Haskell:使用RankNTypes折叠记录构造函数 [英] Haskell: Using RankNTypes to fold a record constructor
问题描述
import Data.ConfigFile
data Test = Test
{field1 :: Int
,field2 :: Bool
,field3 :: String
}派生(显示)
whatMyConfigLooksLike =
[(field1,5)
,(field2,True )
,(field3,我是一个字符串)
]
options = fst。 unzip $ whatMyConfigLooksLike
readConfigFile = do
rv< - runErrorT $ do
cp< - join。 liftIO $ readfile emptyCPtheconfig.cfg
让printn = liftIO。 putStrLn
getn = get xDEFAULT
x = cp
printn加载配置文件...
- 我不想做以下
one< - getnfield1
two< - getnfield2
three< - getnfield3
return $测试一个两三 - ...
- ...等等,因为我有一个数据类型有很多字段
- 我想将它们折叠到数据构造函数上而不是
return $ foldl(\fs - > getn s>> = f)(测试)选项
- 但我认为这不会检查类型,因为f的类型不断变化?
print rv
在上面的代码中,我有一个带有多态类型<$的lambda c $ c> foldl(\fs - > getn s>> = f)。从我所知道的情况来看,这导致它在下面的递归中不会检查。
我认为我可以使用RankNTypes语言扩展来定义一个多态递归类型,它可以表示函数的任何部分应用程序,因此允许函数进行类型检查。但是,通过实验,大量的尝试和相当数量的错误,我一直无法想出任何编译的内容。
如果有人可以向我展示,我将不胜感激如何根据上面的示例代码实现RankNTypes扩展(或者提出替代方案)。我使用GHC 7.4.2。
:为您生成您的代码使用代码摘要底部列出的模块
您无法忍受编写所有样板代码。我非常同情。还有其他方法,但我们可以使用Template Haskell生成我们需要的代码。
如果你是模板Haskell的新手,你应该看看 haskellwiki页面。
首先,让我们转在模板Haskell扩展,并导入Control.Applicative整理代码了一下:
{ - #LANGUAGE TemplateHaskell# - }
import Language.Haskell.TH
import Control.Applicative
什么模板我们应该生成haskell代码?
让我们让ghci为我们转换一个合适的表达式。 (为了方便起见,我伪造了一个 getn
函数,所以我可以在独立代码中使用它。)
*主> :set -XTemplateHaskell
* Main> runQ [|测试< $> getnfield1*< getnfield2*< (Just(InfixE(Just(ConE Main.Test))(VarE Data.Functor。< $>)(Just(AppE(VarE Main.getn) (LitE(StringLfield2)))))(VarE Control.Applicative。 Control.Applicative。*)(Just(AppE(VarE Main.getn))(LitE(StringLfield3))))
哇!让我们整理一下,并使其有效的haskell代码。首先请注意,诸如 Data.Functor。< $>
之类的表达式实际上是类型 Name
。为了得到它,我们可以做 mkName< $>
,但是字符串修改是最糟糕的源代码操作,所以让我们来做'(< $>)
,它会从函数生成(完全限定)名称:
<$ p (Just(InfixE
(Just(ConE'Test))
(VarE'(< b $ b) $>))
(Just(AppE(VarE'getn)(LitE(StringLfield1))))))
(VarE'(*))
(Just(AppE(VarE'getn)(LitE(StringLfield2))))))
(VarE'(*))
(AppE(VarE'getn) (LitE(StringLfield3))))
这个(隐藏)生成我们需要的表达式
fieldExpressions :: Name - > [字符串] - > [Exp]
fieldExpressions getter = map $ \field - > AppE(VarE getter)(LitE(StringL field))
让我们使用 现在,当我们获得字段时,我们首先通过 快速检查: 我们可以在同一个源文件中用 除了会遇到相当不方便的阶段限制: p> 这意味着你必须定义 GetFields.hs: Main.hs: In the above code I have a lambda with a very polymorphic type I think that I can use the RankNTypes language extension for my purpose to define a polymorphic recursive type that can represent any partial application of a function and, hence, allow the function to typecheck. With experimentation, much trial and equal amounts of error, though, I have been unable to come up with anything which compiles. I would be very grateful if somebody can show me how to implement the RankNTypes extension in terms of the example code above (or suggest alternatives). I'm using GHC 7.4.2. TL;DR: Generate your code for you using the module listed at the bottom under "Code Summary" You can't bear to write all that boilerplate code. I can very much sympathise. There are other approaches, but we could use Template Haskell to generate the code we need. If you're new to Template Haskell, you should have a look at the haskellwiki page. First, let's turn on the Template Haskell extension, and import Control.Applicative to tidy the code up a bit:
And let's ask ghci to convert an appropriate expression for us. (I faked a Woah! let's tidy that up a bit, and make it valid haskell code. Firstly note that an expression such as The (hidden) beauty of this is that it's just a load of very similar expressions that we can fold together.< ;< / code>作为
< *>< / code>:<<><> ::< Exp - > Exp - > Exp
a<>>> b = InfixE(Just a)(VarE'(*))(Just b)
< $>
将构造函数应用于第一个字段,然后我们可以使用它作为折叠的基础在其他领域。
getFields :: Name - > [Exp] - > Exp
getFields _ [] = errorgetFields:empty field list
getFields构造函数(f:fs)= foldl(<>)
(InfixE $ Con $构造函数)(VarE'(< $>))(仅f))
fs
*主要> whatWeWant ==(getFields'Test $ fieldExpressions'getn [field1,field2,field3])
True
舞台限制叮咬
$ b来测试/使用它$ b
domything = do
optionsRecord< - $(return $ getFields'Test $ fieldExpressions'getn [field1,field2,field3])
print optionsRecord
GHC阶段限制:`getFields'
用于顶级拼接或注释中,
且必须被导入,而不是在本地定义
getFields
等放入另一个模块中,然后将其导入到您的主文件中,您可以将其拼接到其中。
代码摘要
{ - #LANGUAGE TemplateHaskell # - }
import Language.Haskell.TH
import Control.Applicative
module GetFields where
fieldExpressions :: Name - > [字符串] - > [Exp]
fieldExpressions getter = map $ \field - > AppE(VarE getter)(LitE(StringL field))
(<<>>):: Exp - > Exp - > Exp
a<>>> b = InfixE(Just a)(VarE'(*))(Just b)
getFields :: Name - > [Exp] - > Exp
getFields _ [] = errorgetFields:empty field list
getFields构造函数(f:fs)= foldl(<>)
(InfixE $ Con $构造函数)(VarE'(< $>))(仅f))
fs
import GetFields
import Data.ConfigFile
... defs ...
readConfigFile = do
rv< - runErrorT $ do
cp< - join。 liftIO $ readfile emptyCPtheconfig.cfg
让printn = liftIO。 putStrLn
getn = get xDEFAULT
x = cp
printn加载配置文件...
someoptions< - $(getFields'Test $ fieldExpressions'getn [字段++ show n | n < - [1..30]])
import Data.ConfigFile
data Test = Test
{ field1 :: Int
, field2 :: Bool
, field3 :: String
} deriving (Show)
whatMyConfigLooksLike =
[ ("field1", "5")
, ("field2", "True")
, ("field3", "I am a string")
]
options = fst . unzip $ whatMyConfigLooksLike
readConfigFile = do
rv <- runErrorT $ do
cp <- join . liftIO $ readfile emptyCP "theconfig.cfg"
let printn = liftIO . putStrLn
getn = get x "DEFAULT"
x = cp
printn "Loading configuration file..."
-- I don't want to do the following
one <- getn "field1"
two <- getn "field2"
three <- getn "field3"
return $ Test one two three -- ...
-- ... and so on because I have a data type with many fields
-- I want to fold them onto the data constructor instead
return $ foldl (\f s -> getn s >>= f) (Test) options
-- but I think this doesn't type check because f's type is constantly changing?
print rv
foldl (\f s -> getn s >>= f)
. From what I can tell, this causes it to not typecheck in its following recursions.{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Control.Applicative
What template haskell code should we generate?
getn
function for convenience so I can use it in standalone code.)*Main> :set -XTemplateHaskell
*Main> runQ [| Test <$> getn "field1" <*> getn "field2" <*> getn "field3" |]
InfixE (Just (InfixE (Just (InfixE (Just (ConE Main.Test)) (VarE Data.Functor.<$>) (Just (AppE (VarE Main.getn) (LitE (StringL "field1")))))) (VarE Control.Applicative.<*>) (Just (AppE (VarE Main.getn) (LitE (StringL "field2")))))) (VarE Control.Applicative.<*>) (Just (AppE (VarE Main.getn) (LitE (StringL "field3"))))
Data.Functor.<$>
is actually of type Name
. To get it we could do mkName "<$>"
, but string mangling is the ugliest sort of source code manipulation, so let's do '(<$>)
instead, which generates the (fully qualified) name from the function:whatWeWant = InfixE
(Just (InfixE
(Just (InfixE
(Just (ConE 'Test))
(VarE '(<$>))
(Just (AppE (VarE 'getn) (LitE (StringL "field1"))))))
(VarE '(<*>))
(Just (AppE (VarE 'getn) (LitE (StringL "field2"))))))
(VarE '(<*>))
(Just (AppE (VarE 'getn) (LitE (StringL "field3"))))
Generating the expressions we need
fieldExpressions :: Name -> [String] -> [Exp]
fieldExpressions getter = map $ \field -> AppE (VarE getter) (LitE (StringL field))
Let's use <<*>>
as a sort of lift of <*>
to glue expressions together with <*>
:
(<<*>>) :: Exp -> Exp -> Exp
a <<*>> b = InfixE (Just a) (VarE '(<*>)) (Just b)
Now when we get the fields, we'll first apply the constructor via <$>
to the first field, then we can use that as the base for a fold over the other fields.
getFields :: Name -> [Exp] -> Exp
getFields _ [] = error "getFields: empty field list"
getFields constructor (f:fs) = foldl (<<*>>)
( InfixE (Just $ ConE constructor) (VarE '(<$>)) (Just f) )
fs
A quick check:
*Main> whatWeWant == (getFields 'Test $ fieldExpressions 'getn ["field1","field2","field3"])
True
The stage restriction bites
We could test/use that in the same source file with
domything = do
optionsRecord <- $(return $ getFields 'Test $ fieldExpressions 'getn ["field1","field2","field3"])
print optionsRecord
except that you'll run into the rather inconvenient stage restriction:
GHC stage restriction: `getFields'
is used in a top-level splice or annotation,
and must be imported, not defined locally
That means that you'll have to define getFields
etc in another module, and then import that into your main file where you can splice it in.
Code Summary
GetFields.hs:
{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Control.Applicative
module GetFields where
fieldExpressions :: Name -> [String] -> [Exp]
fieldExpressions getter = map $ \field -> AppE (VarE getter) (LitE (StringL field))
(<<*>>) :: Exp -> Exp -> Exp
a <<*>> b = InfixE (Just a) (VarE '(<*>)) (Just b)
getFields :: Name -> [Exp] -> Exp
getFields _ [] = error "getFields: empty field list"
getFields constructor (f:fs) = foldl (<<*>>)
( InfixE (Just $ ConE constructor) (VarE '(<$>)) (Just f) )
fs
Main.hs:
import GetFields
import Data.ConfigFile
...defs...
readConfigFile = do
rv <- runErrorT $ do
cp <- join . liftIO $ readfile emptyCP "theconfig.cfg"
let printn = liftIO . putStrLn
getn = get x "DEFAULT"
x = cp
printn "Loading configuration file..."
someoptions <- $(getFields 'Test $ fieldExpressions 'getn ["field" ++ show n| n<-[1..30]])
这篇关于Haskell:使用RankNTypes折叠记录构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!