我有简单的元组(例如从数据库读取),因为我不知道元素的数量和内容。例如。
(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 :(得分:8)
有可能使用GHC.Generics
,我想我会在这里记录完整性,虽然我不会推荐它超过其他建议。
我们的想法是将您的元组转换为可以模式匹配的元素。典型的方式(我认为HList
使用)是从n元组转换为嵌套元组:(,,,)
- > (,(,(,)))
。
GHC.Generics
通过将元组转换为产品:*:
构造函数的嵌套应用程序来做类似的事情。 to
和from
是将值转换为通用表示形式的函数。元组字段通常由K1
newtypes表示,因此我们要做的是通过元数据树(M1
)和产品(:*:
)节点向下递归,直到找到{ {1}}叶节点(常量)并用“NIL”字符串替换它们的内容。
K1
类型函数描述了我们如何修改类型。 Rewrite
声明我们将使用Rewrite (K1 i c) = K1 i String
替换每个值(c
类型参数)。
给出一个小测试应用程序:
String
我们可以使用通用的重写器来生成:
*Main> :main ("something","NIL","NIL") ("something else","NIL","NIL","NIL")
使用内置的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
实例中删除,无论是否因为重叠/不可判断实例而更好。 GHC也无法推断结果类型,尽管它看起来应该能够。在任何情况下,结果类型都必须正确,否则您将收到难以阅读的错误消息。
答案 1 :(得分:5)
Vinyl需要GHC 7.6,HList很快就会有更新(根据最新的Haskell社区活动报告。)HList特别适合代表SQL查询的结果。
关于HList:
HList是一个全面的,通用的Haskell库,用于类型化的异构集合,包括可扩展的多态记录和变体。 HList类似于标准列表库,提供各种构造,查找,过滤和迭代原语。与常规列表相比,异构列表的元素不必具有相同的类型。 HList允许用户制定静态可检查约束:例如,集合中没有两个元素可能具有相同的类型(因此元素可以按其类型明确地索引)。
...
2012年10月版的HList库标志着重要的重写,以利用GHC 7.4+提供的更高级的类型。 HList现在依赖于类型级布尔值,自然数和列表,以及类型多态。许多操作被实现为类型函数。另一个值得注意的补充是针对异构列表展开的。现在许多操作(投射,分裂)都是以展开的方式实现的。这样的重构将更多计算转移到类型级别,没有运行时开销。
答案 2 :(得分: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
我也不明白你怎么不知道你的值元组的元组大小是多少。你应该澄清一下。
答案 3 :(得分:3)
在haskell中,每个元组都属于另一种类型,所以我认为如果没有这样做,我认为你不能以任何简单的方式编写函数。此外,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
所以我认为不应该使用元组,而应该尝试使用其他数据类型。