Manipulating “arbitrary” tuples

天涯浪子 提交于 2020-01-01 05:44:13

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!