操纵“任意”元组 [英] Manipulating "arbitrary" tuples

查看:117
本文介绍了操纵“任意”元组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有简单的元组(例如从数据库中读取),因为我不知道元素的数量和内容。例如。
(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 K1 叶子节点(常量),并替换( c>)和产品(:*:它们的内容是一个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))

我们可以使用通用重写器来产生:

 $ main $:
(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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆