5

我有简单的元组(例如从数据库中读取),我不知道元素的数量也不知道内容。例如 (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")

我显然不知道从哪里开始,因为用已知的元组来做这件事不会有问题。没有模板 Haskell 是否有可能达到我想要的结果?

4

4 回答 4

8

可以使用GHC.Generics,我想我会在这里记录它以保持完整性,尽管我不会推荐它而不是这里的其他建议。

这个想法是将您的元组转换为可以进行模式匹配的东西。典型的方法(我相信HList使用)是从 n 元组转换为嵌套元组:(,,,)-> (,(,(,)))

GHC.Generics:*:通过将元组转换为产品构造函数的嵌套应用程序来做类似的事情。to并且from是将值与它们的通用表示进行转换的函数。元组字段通常由新类型表示K1,所以我们要做的是向下递归遍历元数据 ( M1) 和产品 ( :*:) 节点的树,直到找到K1叶节点(常量)并将其内容替换为“NIL”字符串.

Rewritetype 函数描述了我们如何修改类型。Rewrite (K1 i c) = K1 i String声明我们要用 . 替换每个值(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))

我们可以使用通用重写器来生成:

*主> :主
(“某事”、“无”、“无”)
(“别的东西”,“无”,“无”,“无”)

使用内置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

还有一些本示例未使用的实例,其他数据类型需要它们:

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)

通过更多的努力,Typeable可以从K1实例中删除约束,由于重叠/UndecidableInstances,它是否更好是有争议的。GHC 也无法推断结果类型,尽管它似乎应该能够。在任何情况下,结果类型都必须正确,否则您将收到难以阅读的错误消息。

于 2012-11-18T08:50:20.210 回答
5

查看HListVinyl包作为使用元组的替代方案。

Vinyl 需要 GHC 7.6,并且预计很快就会对 HList 进行更新(根据最新的 Haskell 社区活动报告)。特别是 HList 似乎非常适合表示 SQL 查询的结果。

关于 HList:

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

...

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

于 2012-11-18T07:01:52.657 回答
4

有人在评论中提到了这一点,但也许你应该使用列表而不是元组。你用:

data MyType = S String | D Double

someData :: [MyType]

然后你可以使用简单的转换列表map

convert :: MyType -> String
convert (S str) = str
convert _       = "NIL"

convertList :: [MyType] -> [String]
convertList = map convert

我也不明白你怎么不知道你的值源的元组大小是多少。你或许应该澄清一下。

于 2012-11-18T15:16:00.500 回答
3

在 haskell 中,每个元组都是另一种类型,所以我认为如果没有 TH 就无法以任何简单的方式编写函数。GHC 还对允许的最大元组大小施加了限制。Haskell 标准只说编译器应该允许元组至少达到 15 的大小。像

Prelude> let a = (1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0)

<interactive>:2:9:
    A 70-tuple is too large for GHC
      (max size is 62)
      Workaround: use nested tuples or define a data type

所以我认为你应该尝试使用其他数据类型,而不是使用元组。

于 2012-11-18T02:47:57.540 回答