Haskell:使用RankNTypes折叠记录构造函数 [英] Haskell: Using RankNTypes to fold a record constructor

查看:111
本文介绍了Haskell:使用RankNTypes折叠记录构造函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  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 Con​​trol.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))

让我们使用< ;< / code>作为< $>< / 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

除了会遇到相当不方便的阶段限制: p>

  GHC阶段限制:`getFields'
用于顶级拼接或注释中,
且必须被导入,而不是在本地定义

这意味着你必须定义 getFields 等放入另一个模块中,然后将其导入到您的主文件中,您可以将其拼接到其中。

代码摘要



GetFields.hs:

  { - #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

Main.hs:

  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

In the above code I have a lambda with a very polymorphic type foldl (\f s -> getn s >>= f). From what I can tell, this causes it to not typecheck in its following recursions.

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:

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Control.Applicative

What template haskell code should we generate?

And let's ask ghci to convert an appropriate expression for us. (I faked a 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"))))

Woah! let's tidy that up a bit, and make it valid haskell code. Firstly note that an expression such as 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"))))

The (hidden) beauty of this is that it's just a load of very similar expressions that we can fold together.

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屋!

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