我有简单的元组(例如从数据库中读取),因为我不知道元素的数量和内容.例如
(String, Int, Int)或(String, Float, String, Int).
我想编写一个泛型函数,它将采用所有类型的元组并用字符串"NIL"替换所有数据.如果字符串"NIL"已经存在,它应该保持不变.
回到这个例子:
("something", 3, 4.788)应该导致("something", "NIL", "NIL")
("something else", "Hello", "NIL", (4,6)) 应该导致 ("something else", "NIL", "NIL", "NIL")
我显然不知道从哪里开始,因为用已知的元组做这个不会有问题.如果没有Template Haskell,是否有可能达到我想要的结果?
它可以使用GHC.Generics,我想我会在这里记录它的完整性,虽然我不推荐它在这里的其他建议.
我们的想法是将您的元组转换为可以模式匹配的元素.典型的方式(我相信HList使用)是从n元组转换为嵌套元组:(,,,)- > (,(,(,))).
GHC.Generics通过将元组转换为产品:*:构造函数的嵌套应用程序来做类似的事情.to并且from是将值转换为通用表示形式的函数.元组字段通常由K1newtypes 表示,因此我们要做的是通过metadata(M1)和product(:*:)节点树向下递归,直到我们找到K1叶节点(常量)并用"NIL"字符串替换它们的内容.
该Rewrite类型的函数描述我们如何修改类型.Rewrite (K1 i c) = K1 i String说明我们要用a替换每个值(c类型参数)String.
鉴于一个小测试应用程序:
y0 :: (String, Int, Double)
y0 = ("something", 3, 4.788)
y1 :: (String, String, String, (Int, Int))
y1 = ("something else", "Hello", "NIL", (4,6))
main :: IO ()
main = do
print (rewrite_ y0 :: (String, String, String))
print (rewrite_ y1 :: (String, String, String, String))
Run Code Online (Sandbox Code Playgroud)
我们可以使用通用的重写器来生成:
*Main> :main
("something","NIL","NIL")
("something else","NIL","NIL","NIL")
使用内置Generics功能和类型类来进行实际转换:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
import Data.Typeable
import GHC.Generics
rewrite_
:: (Generic a, Generic b, Rewriter (Rep a), Rewrite (Rep a) ~ Rep b)
=> a -> b
rewrite_ = to . rewrite False . from
class Rewriter f where
type Rewrite f :: * -> *
rewrite :: Bool -> f a -> (Rewrite f) a
instance Rewriter f => Rewriter (M1 i c f) where
type Rewrite (M1 i c f) = M1 i c (Rewrite f)
rewrite x = M1 . rewrite x . unM1
instance Typeable c => Rewriter (K1 i c) where
type Rewrite (K1 i c) = K1 i String
rewrite False (K1 x) | Just val <- cast x = K1 val
rewrite _ _ = K1 "NIL"
instance (Rewriter a, Rewriter b) => Rewriter (a :*: b) where
type Rewrite (a :*: b) = Rewrite a :*: Rewrite b
rewrite x (a :*: b) = rewrite x a :*: rewrite True b
Run Code Online (Sandbox Code Playgroud)
此示例未使用的一些实例,其他数据类型也需要它们:
instance Rewriter U1 where
type Rewrite U1 = U1
rewrite _ U1 = U1
instance (Rewriter a, Rewriter b) => Rewriter (a :+: b) where
type Rewrite (a :+: b) = Rewrite a :+: Rewrite b
rewrite x (L1 a) = L1 (rewrite x a)
rewrite x (R1 b) = R1 (rewrite x b)
Run Code Online (Sandbox Code Playgroud)
通过更多的努力,Typeable可以从K1实例中移除约束,无论是否因为重叠/不可判断实体而更好.GHC也无法推断结果类型,尽管它看起来应该能够.在任何情况下,结果类型都需要正确,否则您将收到难以阅读的错误消息.
Vinyl需要GHC 7.6,并且很快就会有HList的更新(根据最新的Haskell社区活动报告.)HList特别适合代表SQL查询的结果.
关于HList:
HList是一个全面的,通用的Haskell库,用于类型化的异构集合,包括可扩展的多态记录和变体.HList类似于标准列表库,提供各种构造,查找,过滤和迭代原语.与常规列表相比,异构列表的元素不必具有相同的类型.HList允许用户制定静态可检查约束:例如,集合中没有两个元素可以具有相同的类型(因此元素可以按其类型明确地索引).
...
2012年10月版的HList库标志着重要的重写,以利用GHC 7.4+提供的更高级的类型.HList现在依赖于类型级布尔值,自然数和列表,以及类型多态.许多操作被实现为类型函数.另一个值得注意的补充是针对异构列表展开的.现在,许多操作(投影,拆分)都是以展开的方式实现的.这样的重构将更多计算转移到类型级别,没有运行时开销.
| 归档时间: |
|
| 查看次数: |
1358 次 |
| 最近记录: |