Data实例的通用数据构造函数

时间:2014-07-13 19:01:55

标签: haskell scrap-your-boilerplate

给定数据类型

data Foo = IFoo Int | SFoo String deriving (Data, Typeable)

的简单定义是什么
gconstr :: (Typeable a, Data t) => a -> t

这样

gconstr (5 :: Int) :: Foo == IFoo 5
gconstr "asdf" :: Foo == SFoo "asdf"
gconstr True :: Foo == _|_

它基本上与syb的gfindtype相反。

或者这样的事情已经存在了吗?我已经尝试了类型和避风港发现了很多,但syb类型很难解释。错误返回Nothing的函数也是可以接受的。

1 个答案:

答案 0 :(得分:2)

这似乎是可能的,虽然它并非完全无足轻重。

预赛:

{-# LANGUAGE DeriveDataTypeable #-}
import Control.Monad ( msum )
import Data.Data
import Data.Maybe

首先是辅助函数gconstrn,它尝试执行gconstr所需的相同操作,但仅针对特定构造函数:

gconstrn :: (Typeable a, Data t) => Constr -> a -> Maybe t
gconstrn constr arg = gunfold addArg Just constr
    where
        addArg :: Data b => Maybe (b -> r) -> Maybe r
        addArg Nothing = Nothing
        addArg (Just f) =
            case cast arg of
                Just v -> Just (f v)
                Nothing -> Nothing

关键部分是addArg函数将使用arg作为构造函数的参数,如果类型匹配。

基本上gunfold开始使用Just IFooJust SFoo展开,然后下一步是尝试addArg为其提供参数。

对于多参数构造函数,这将被重复调用,因此如果您定义了一个IIFoo构造函数,该构造函数需要两个Int,它也会被gconstrn成功填充。显然,通过更多的工作,你可以做一些更复杂的事情,比如提供一个参数列表。

然后,这只是一个与所有可能的构造函数一起尝试的问题。 resultdt之间的递归定义只是为dataTypeOf获取正确的类型参数,传递的实际值根本不重要。 ScopedTypeVariables将成为实现这一目标的替代方案。

gconstr :: (Typeable a, Data t) => a -> Maybe t
gconstr arg = result
    where result = msum [gconstrn constr arg | constr <- dataTypeConstrs dt]
          dt = dataTypeOf (fromJust result)

正如评论中所讨论的,两个函数都可以使用<*>Control.Applicative简化为以下内容,但是很难看到gunfold中发生的事情。 {1}}:

gconstr :: (Typeable a, Data t) => a -> Maybe t
gconstr arg = result
    where
        result = msum $ map (gunfold (<*> cast arg) Just) (dataTypeConstrs dt)
        dt = dataTypeOf (fromJust result)