这似乎是可能的,尽管它并非完全微不足道。
预赛:
{-# 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
用作构造函数的参数。
基本上以orgunfold
开始展开,然后下一步是尝试为其提供参数。Just IFoo
Just SFoo
addArg
对于多参数构造函数,这将被重复调用,所以如果你定义了一个IIFoo
需要两个Int
s 的构造函数,它也会被成功填充gconstrn
。显然,通过更多的工作,你可以做一些更复杂的事情,比如提供一个参数列表。
那么这只是一个尝试使用所有可能的构造函数的问题。result
和之间的递归定义dt
只是为了获得正确的类型参数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)
正如评论中所讨论的,这两个函数都可以使用<*>
fromControl.Applicative
到以下进行简化,尽管很难看到 中发生了什么gunfold
:
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)