2012-11-18 34 views
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")

我明明不知道从哪里开始,因为它不会与元组做这是一个问题这是已知的。如果没有Template Haskell,可以在这里得到我想要的结果吗?

+0

你想让第一个项目独立,但所有其他项目都是“NIL”? – AndrewC

+4

你确定元组是正确的数据类型吗?有一些方法可以做到这一点,但如果您将数据转化为更好的类型,那么这种方法就不那么笨拙了。另外,你确定你想要第一个元素得到这样的特殊待遇吗? – shachaf

+0

使用像SYB这样的泛型库比Template Haskell更好 - 根据user5402的回答,你应该看看HList。 –

回答

8

这是可能的使用GHC.Generics,我想我会在这里记录它的完整性,虽然我不会推荐它在这里的其他建议。

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

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

Rewrite类型函数描述了我们如何修改类型。 Rewrite (K1 i c) = K1 i String指出我们将用String替换每个值(c类型参数)。

给定一个小的测试应用程序:

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)) 

我们可以使用一个通用的重写产生:

 
*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 

而这个例子没有使用的一些实例,他们会需要为其它数据类型:

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也不能推断出结果类型,尽管它看起来应该能够。无论如何,结果类型必须是正确的,否则您将很难看到错误消息。

+0

谢谢,我需要消化这个。问题是,虽然HList可能是我需要的,但我必须用()来表示。我曾经阅读过Haskell,有些方法可以将这些表示方式更改为您的喜好,但无法找回它。 –

+0

+1,我来到类似的解决方案,但与OverlappingInstances而不是Typeable –

+0

@JFritsch我已经添加了几个评论。阅读“GHC.Generics”教程也可能是一个好主意。 –

3

在haskell中,每一个元组都是另一种类型的,所以我不认为你可以用任何简单的方法写一个没有TH的函数来做到这一点。另外,GHC对允许的元组的最大大小施加限制。哈斯克尔标准只是说编译器应该允许的元组ATLEAST高达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 

所以我想,而不是使用元组,你应该尝试使用一些其他的数据类型。

5

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

乙烯需要GHC 7.6,并且预计很快就会有更新(根据最新的Haskell社区活动报告).HList尤其看起来非常适合代表SQL查询的结果。

关于HList:

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

...

在2012年10月版HList库标志着显著重写采取由GHC 7.4+提供的票友类型的优势。现在,HList依赖于类型级布尔值,自然数和列表以及类型多态性。一些操作被实现为类型函数。另一个值得注意的补充是展示异构列表。许多操作(投影,分割)现在都在展开。这样的重构把更多的计算转移到了类型级别上,没有运行时间的开销。

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 

我也想不明白你怎么会不知道你的价值源的元组大小。你应该澄清一点。