haskell 元组和记录之间的自动转换

eqfvzcg8  于 2022-11-14  发布在  其他
关注(0)|答案(5)|浏览(144)

haskell中的Record或简单ADT几乎等同于 boxed tuple。是否有一种方法(理想情况下是一些花哨的扩展或来自haksell平台的库)允许在此类类型和tuple之间进行转换?
我是Haskell的新手,我正在尝试用Haskell构建一些报表工具。这涉及到读/写csv文件和数据库表。使用元组的时候事情很简单,但是在使用普通类的时候会涉及到一些样板。样板在两种方式上几乎是一样的,但是我没有找到一个好的方法来只做一次,除了可能做一个转换(数据<->元组)并使用从元组到CSV/表的本地转换。

更新

到目前为止,我得到的所有答案都是假设我需要完全通用的东西,我想要元组。我不想要元组,我元组,我不想要它们,因此需要转换它们。事实上,我只是想减少锅炉板(到0:-)),但我不需要函数对每个类型都有相同的名称。
例如,我可以通过取消元组的一个构造函数来轻松地将元组转换为任何内容。问题是我需要uncurryN,但我在任何地方都找不到它(除了在模板haskell教程中)。反之则更难做到。
我不是在寻求解决方案(尽管我得到的所有答案都很棒,因为我不熟悉Haskell中元编程的不同方式),而是更多,因为我不喜欢重新发明轮子,如果轮子已经存在的话(例如这个uncurryN,可以一直手写到20岁,然后装在漂亮的包里)

已更新2

显然,一个uncurry包存在,但它仍然解决了一半的问题。

tyu7yeag

tyu7yeag1#

你可能想看看GHC.Generics。它基本上把每个ADT编码为乘积((,))和和和(Either)。作为一个例子,下面是你如何使用泛型来显示这种表示:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
import GHC.Generics

class Tuple p where
  showRepresentation :: p -> String

  default showRepresentation :: (Generic p, GTuple (Rep p)) => p -> String
  showRepresentation = gshowRepresentation . from

class GTuple p where
  gshowRepresentation :: p x -> String

instance Tuple k => GTuple (K1 i k) where
  gshowRepresentation (K1 t) = showRepresentation t

instance GTuple f => GTuple (M1 i c f) where
  gshowRepresentation (M1 f) = gshowRepresentation f

instance (GTuple f, GTuple g) => GTuple (f :*: g) where
  gshowRepresentation (f :*: g) = gshowRepresentation f ++ " * " ++ gshowRepresentation g

-- Some instances for the "primitive" types
instance Tuple Int where showRepresentation = show
instance Tuple Bool where showRepresentation = show
instance Tuple () where showRepresentation = show

--------------------------------------------------------------------------------
data Example = Example Int () Bool deriving Generic
instance Tuple Example

main :: IO ()
main = putStrLn $ showRepresentation $ Example 3 () False
-- prints: 3 * () * False

你可以在GHC.Generics模块中找到更多的文档。我还发现关于它的论文A Generic Deriving Mechanism for Haskell相当具有可读性(这是我读过的为数不多的论文之一)。

f87krz0w

f87krz0w2#

lens库,在Control.Lens.IsoControl.Lens.Wrapped模块中,有几个实用程序可以简化这种转换。不幸的是,目前模板Haskell机制不处理记录,只处理新类型,所以你必须自己定义示例。例如:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}

import Control.Lens

data Foo = Foo { baz :: Int, bar :: Int } deriving Show

instance Wrapped Foo where
  type Unwrapped Foo = (Int,Int)
  _Wrapped' = iso (\(Foo baz' bar') -> (baz',bar')) (\(baz',bar') -> Foo baz' bar')

现在,我们可以轻松地打包和解包:

*Main> (2,3) ^. _Unwrapped' :: Foo
Foo {baz = 2, bar = 3}

*Main> Foo 2 3 ^. _Wrapped'
(2,3)

我们也可以使用一个作用于元组的函数来修改Foo

*Main> over _Wrapped' (\(x,y)->(succ x,succ y))  $ Foo 2 5
Foo {baz = 3, bar = 6}

反过来说:

*Main> under _Wrapped' (\(Foo x y)->(Foo (succ x) (succ y)))  $ (2,5)
(3,6)
omjgkv6w

omjgkv6w3#

如果您想要真实的的n元组(而不仅仅是语义上等价的其他数据),那么没有Template Haskell将非常麻烦。
例如,如果要将

data Foo = Foo Int String Int
data Bar = Bar String String Int Int

进入

type FooTuple = (Int, String, Int)
type BarTuple = (String, String, Int, Int)

GHC.GenericsSYB都会有问题,因为结果类型需要根据数据类型的字段而不同。即使两者都被称为“元组”,(Int, String, Int)(String, String, Int, Int)也是完全独立的类型,并且没有方便的方法来以通用方式处理n元元组。下面是使用GHC.Generics实现上述目的的一种方法:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveGeneric #-}

-- Generic instance to turn generic g x into some n-tuple whose exact
-- type depends on g.
class GTuple g where
    type NTuple g

    gtoTuple :: g x -> NTuple g

-- Unwarp generic metadata
instance GTuple f => GTuple (M1 i c f) where
    type NTuple (M1 i c f) = NTuple f

    gtoTuple = gtoTuple . unM1

-- Turn individual fields into a Single type which we need to build up
-- the final tuples.
newtype Single x = Single x

instance GTuple (K1 i k) where
    type NTuple (K1 i k) = Single k

    gtoTuple (K1 x) = Single x

-- To combine multiple fields, we need a new Combine type-class.
-- It can take singular elements or tuples and combine them into
-- a larger tuple.
--
class Combine a b where
    type Combination a b
    combine :: a -> b -> Combination a b

-- It's not very convenient because it needs a lot of instances for different
-- combinations of things we can combine.

instance Combine (Single a) (Single b) where
    type Combination (Single a) (Single b) = (a, b)
    combine (Single a) (Single b) = (a, b)

instance Combine (Single a) (b, c) where
    type Combination (Single a) (b, c) = (a, b, c)
    combine (Single a) (b, c) = (a, b, c)

instance Combine (a,b) (c,d) where
    type Combination (a,b) (c,d) = (a,b,c,d)
    combine (a,b) (c,d) = (a,b,c,d)

-- Now we can write the generic instance for constructors with multiple
-- fields.

instance (Combine (NTuple a) (NTuple b), GTuple a, GTuple b) => GTuple (a :*: b) where
    type NTuple (a :*: b) = Combination (NTuple a) (NTuple b)

    gtoTuple (a :*: b) = combine (gtoTuple a) (gtoTuple b)

-- And finally the main function that triggers the tuple conversion.
toTuple :: (Generic a, GTuple (Rep a)) => a -> NTuple (Rep a)
toTuple = gtoTuple . from

-- Now we can test that our instances work like they should:
data Foo = Foo Int String Int deriving (Generic)
data Bar = Bar String String Int Int deriving (Generic)

fooTuple = toTuple $ Foo 1 "foo" 2
barTuple = toTuple $ Bar "bar" "asdf" 3 4

上面的工作,但它需要大量的工作(我不能很快弄清楚,如果它可以不使用UndecidableInstances)。
现在,您 * 真正 * 想要做的可能只是跳过元组,使用泛型直接转换为CSV。我假设您正在使用csv-conduit,并希望生成ToRecord类型类的示例。
这里有一个例子

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveGeneric #-}

import GHC.Generics
import Data.ByteString (ByteString)
import Data.CSV.Conduit.Conversion

class GRecord g where
    gToRecord :: g x -> [ByteString]

instance GRecord f => GRecord (M1 i c f) where
    gToRecord = gToRecord . unM1

instance ToField k => GRecord (K1 i k) where
    gToRecord (K1 x) = [toField x]

instance (GRecord a, GRecord b) => GRecord (a :*: b) where
    gToRecord (a :*: b) = gToRecord a ++ gToRecord b

genericToRecord :: (Generic a, GRecord (Rep a)) => a -> Record
genericToRecord = record . gToRecord . from

现在,您可以轻松地为自定义类型创建示例。

data Foo = Foo Int String Int deriving (Generic)
data Bar = Bar String String Int Int deriving (Generic)

instance ToRecord Foo where
    toRecord = genericToRecord

instance ToRecord Bar where
    toRecord = genericToRecord

针对您更新后的问题:您可能会对tuple包(尤其是Curry)感兴趣,它包含uncurryNcurryN的实现,用于最多15个元素的元组。

9gm1akwq

9gm1akwq4#

在某些情况下,你可以使用unsafeCoerce。函数的名字应该是一个非常明确的警告,要非常小心。特别是,行为取决于编译器,甚至编译器的版本。

data Bar = Bar Text Text

tupleToBar :: (Text, Text) -> Bar
tupleToBar = unsafeCoerce
d5vmydt9

d5vmydt95#

generic-lens中的函数_Ctor将任何记录值转换为元组,反之亦然,不使用模板haskell。

相关问题