问题
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?
回答1:
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.
回答2:
Have a look at the HList or Vinyl packages as alternatives to using tuples.
Vinyl requires GHC 7.6, and there is an update expected soon for HList (according the latest Haskell Community Activities Report.) HList in particular seems like it well-suited to represent the results of SQL queries.
About HList:
HList is a comprehensive, general purpose Haskell library for typed heterogeneous collections including extensible polymorphic records and variants. HList is analogous to the standard list library, providing a host of various construction, look-up, filtering, and iteration primitives. In contrast to the regular lists, elements of heterogeneous lists do not have to have the same type. HList lets the user formulate statically checkable constraints: for example, no two elements of a collection may have the same type (so the elements can be unambiguously indexed by their type).
...
The October 2012 version of HList library marks the significant re-write to take advantage of the fancier types offered by GHC 7.4+. HList now relies on type-level booleans, natural numbers and lists, and on kind polymorphism. A number of operations are implemented as type functions. Another notable addition is unfold for heterogeneous lists. Many operations (projection, splitting) are now implemented in terms of unfold. Such a refactoring moved more computations to type-level, with no run-time overhead.
回答3:
Somebody mentioned this in the comments, but perhaps you should use a list instead of a tuple. You use:
data MyType = S String | D Double
someData :: [MyType]
Then you could convert the list using a simple map
:
convert :: MyType -> String
convert (S str) = str
convert _ = "NIL"
convertList :: [MyType] -> [String]
convertList = map convert
I also don't understand how you would not know what the tuple size of your source of values is. You should perhaps clarify that.
回答4:
In haskell every tuple is of another type so I don't think you can write a function in any easy way without TH to do that. Also GHC imposes a limit on maximum size of tuple allowed. Haskell standard only says compilers should allow tuples atleast upto size 15. Like
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
So I think instead of using tuples you should try using some other datatype.
来源:https://stackoverflow.com/questions/13436366/manipulating-arbitrary-tuples