操纵"任意"元组

J F*_*sch 5 haskell

我有简单的元组(例如从数据库中读取),因为我不知道元素的数量和内容.例如 (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,是否有可能达到我想要的结果?

Nat*_*ell 8

它可以使用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也无法推断结果类型,尽管它看起来应该能够.在任何情况下,结果类型都需要正确,否则您将收到难以阅读的错误消息.


Eri*_*ikR 5

看看HListVinyl软件包作为使用元组的替代方法.

Vinyl需要GHC 7.6,并且很快就会有HList的更新(根据最新的Haskell社区活动报告.)HList特别适合代表SQL查询的结果.

关于HList:

HList是一个全面的,通用的Haskell库,用于类型化的异构集合,包括可扩展的多态记录和变体.HList类似于标准列表库,提供各种构造,查找,过滤和迭代原语.与常规列表相比,异构列表的元素不必具有相同的类型.HList允许用户制定静态可检查约束:例如,集合中没有两个元素可以具有相同的类型(因此元素可以按其类型明确地索引).

...

2012年10月版的HList库标志着重要的重写,以利用GHC 7.4+提供的更高级的类型.HList现在依赖于类型级布尔值,自然数和列表,以及类型多态.许多操作被实现为类型函数.另一个值得注意的补充是针对异构列表展开的.现在,许多操作(投影,拆分)都是以展开的方式实现的.这样的重构将更多计算转移到类型级别,没有运行时开销.