操纵“任意”元组 [英] Manipulating "arbitrary" tuples
问题描述
我有简单的元组(例如从数据库中读取),因为我不知道元素的数量和内容。例如。
(String,Int,Int)
或(String,Float,String,Int)
p>
我想编写一个通用函数,它将采用各种元组并用字符串NIL替换所有数据。如果字符串NIL已经存在,它应该保持不变。
回到这个例子:
(something, 3,4.788)
应该导致(something,NIL,NIL)
(something else,Hello,NIL,(4,6))
应该导致(something其他,NIL,NIL,NIL)
我不知道从哪里开始,因为它不会用已知的元组来做这件事是个问题。是否有可能在没有Template Haskell的情况下达到我想要的结果? 使用 GHC可以实现这一点。泛型
,我想我会在这里记录它的完整性,但我不会推荐它在这里的其他建议。
这个想法是把你的元组转换成可以模式匹配的东西。典型的方式(我相信 HList
使用)是从一个n元组转换为嵌套元组:(,,,)
(,(,(,)))
。
GHC .Genericics通过将元组转换为产品
:*:
的构造函数的嵌套应用程序来做类似的事情。 到
和来自
是将值转换为其通用表示的功能。元组字段通常由 K1
newtypes表示,所以我们想要做的是通过元数据树( M1 $ c $直到我们找到
K1
叶子节点(常量),并替换(:*:
它们的内容是一个NIL字符串。
$ b $ Rewrite
类型函数描述了我们如何修改类型。 重写(K1 ic)= K1 i字符串
表示我们要替换每个值( c
类型参数)与一个字符串
。
给定一个小测试应用程序:
y0 ::(String,Int,Double)
y0 =(something,3,4.788)
y1 :: String,String,String,(Int,Int))
y1 =(其他,Hello,NIL,(4,6))
main :: IO ()
main = do
print(rewrite_ y0 ::(String,String,String))
print(rewrite_ y1 ::(String,String,String,String))
我们可以使用通用重写器来产生:
(something,NIL,NIL)
(其他,NIL,NIL,NIL )
使用内置的泛型
功能和一个类型类来执行实际转换:
{ - #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。重写False。从
类重写器f其中
类型重写f :: * - > *
rewrite :: Bool - > f a - > (重写f)a
实例重写器f =>重写器(M1 i c f)其中
类型重写(M1 i c f)= M1 i c(重写f)
重写x = M1。重写x。 unM1
实例类型c =>重写器(K1 i c)其中
类型重写(K1 i c)= K1 i字符串
重写假(K1 x)|只有val< - cast x = K1 val
重写_ _ = K1NIL
实例(重写器a,重写器b)=>重写器(a:*:b)其中
类型重写(a:*:b)=重写a:*:重写b
重写x(a:*:b)=重写xa:*:重写真正的b
这个例子中未使用的一些实例,它们对于其他数据类型是必需的:
实例重写器U1其中
类型重写U1 = U1
重写_ U1 = U1
实例(重写器a,重写器b)=>重写器(a:+:b)其中
类型重写(a:+:b)=重写a:+:重写b
重写x(L1 a)= L1(重写xa)
重写x(R1b)= R1(重写xb)
可以从 K1
实例中删除$ c> Typeable 约束条件,因为Overlapping / UndecidableInstances可能会引起争论。 GHC也不能推断出结果类型,尽管它看起来应该能够。无论如何,结果类型必须正确,否则您将很难看到错误消息。
I have simple tuples (e.g. read from a DB) from that I do not know the number of elements nor the content. E.g.
(String, Int, Int)
or (String, Float, String, Int)
.
I want to write a generic function that would take all sort of tuples and replace all data with the string "NIL". If the string "NIL" is already present it should stay untouched.
Coming back to the example:
("something", 3, 4.788)
should result in ("something", "NIL", "NIL")
("something else", "Hello", "NIL", (4,6))
should result in ("something else", "NIL", "NIL", "NIL")
I have obviously no idea where to start since it won't be a problem to do this with tuples that are known. Is it possible here to come to my desired result without Template Haskell?
It's possible using GHC.Generics
, I thought I'd document it here for completeness though I wouldn't recommend it over the other recommendations here.
The idea is to convert your tuples into something that can be pattern matched on. The typical way (which I believe HList
uses) is to convert from a n-tuple to nested tuples: (,,,)
-> (,(,(,)))
.
GHC.Generics
does something similar by converting the tuples to nested applications of the product :*:
constructor. to
and from
are functions that convert a value to and from their generic representation. The tuple fields are generically represented by K1
newtypes, so what we want to do is recurse down through the tree of metadata (M1
) and product (:*:
) nodes until we find the K1
leaf nodes (the constants) and replace their contents with a "NIL" string.
The Rewrite
type function describes how we're modifying the types. Rewrite (K1 i c) = K1 i String
states that we're going to replace each value (the c
type parameter) with a String
.
Given a little test app:
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))
We can use a generic rewriter to produce:
*Main> :main ("something","NIL","NIL") ("something else","NIL","NIL","NIL")
Using the built-in Generics
functionality and a typeclass to do the actual transformation:
{-# 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
And a few instances unused by this example, they'd be required for other data types:
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)
With a bit more effort the Typeable
constraint could be removed from the K1
instance, whether it's better or not is arguable due to Overlapping/UndecidableInstances. GHC also can't infer the result type, though it's seems like it should be able to. In any case, the result type needs to be correct or you'll get a hard to read error message.
这篇关于操纵“任意”元组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!