如何映射同一类的实例的长元组?

时间:2017-03-09 10:08:34

标签: list class haskell tuples

假设我们在Haskell中有一个C类,它有一个返回R类型结果的函数f。 如果我们给出了C类实例的长元组,那么我们怎样才能很好地获得t成员的f结果列表(或至少是元组)?

请注意,元组很长,因此包含每个元组成员键入内容的解决方案并不完美。我们不想输入那么多。

-- GIVEN --

data R = R  -- more constructors here

class C a where
  f :: a -> R

data A = A
instance C A where
  f _ = R  -- some fancy f here

data B = B
instance C B where
  f _ = R  -- some fancy f here

-- some other instances of C here

t = (A, B, B, A, B, A, B) -- a long tuple of instances of C


-- QUESTION: How to obtain l as below, but in the nicest way? --

l :: [R]
l = let (t1, t2, t3, t4, t5, t6, t7) = t
    in [f t1, f t2, f t3, f t4, f t5, f t6, f t7]

3 个答案:

答案 0 :(得分:3)

这是通用编程的一个非常标准的用例。这是一个常见的用例,有些库不必编写通用实现。

one-liner

import Generics.OneLiner

l :: [R]
l = gfoldMap (For :: For C) (pure . f) t :: [R]

gfoldMap是数据类型字段的折叠,假设它们是给定类型类的所有实例,允许通常收集结果,这里是C

gfoldMap (For :: For C) :: (forall a. C a => a -> [R]) -> (A, B, B, A, B, A, B) -> [R]

请注意,这需要Generic的实例,由基本包派生的最多只有7个元组。 您可以定义自己的元组,并为其导出Generic

由于各种原因,您可能希望保留数据类型的结构,而不是在列表中收集结果。在撰写本文时,one-liner在这方面仍然有点严格,因为它不处理“类型更改遍历”(从(A, B, B, A, B, A, B)(R, R, R, R, R, R, R))。

product-profunctors

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}

import Data.Profunctor
import Data.Profunctor.Product
import Data.Profunctor.Product.Default

我们定义一个新类型,以便不用孤儿污染环境。

newtype P a b = P { unP :: a -> b } deriving
  (Profunctor, ProductProfunctor)

我们将f声明为Default映射ABR的方式。

instance Default P A R where def = P f
instance Default P B R where def = P f

库隐式地将其扩展为元组,将AB的任何元组映射到相应的R元组。

-- Type signature required
t'_ :: (R, R, R, R, R, R, R)
t'_ = unP def t

您可能不想输入类型签名。由于类型类shenanigans,无法推断它。但是,它可以根据输入的类型计算。因此,您可以定义一个类型族(一个类型级函数),它在元组类型中用A替换BR的出现。实际上,任何看起来像F a b c d e f F类型构造函数的类型都将转换为F R R R R R R

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UndecidableInstances #-}

type family Rify (a :: k) where
  Rify (f a) = Rify f a
  Rify a = a

使用此系列专门化def(具有类型推断不友好类型):

defP :: Default P a (Rify a) => P a (Rify a)
defP = def

-- Type signature now optional
t' :: (R, R, R, R, R, R, R)
t' = unP defP t

product-profunctors,bis

您还可以通过选择合适的发送者来获取列表。

顾名思义,product-profunctors以一般方式与ProductProfunctor一起使用。此profunctor在列表中收集R个值。这是Const [R]中等同于Control.Applicative的合并者。

newtype Q a b = Q { unQ :: a -> [R] }

instance Profunctor Q where dimap f _ (Q q) = Q (q . f)

instance ProductProfunctor Q where
  purePP _ = Q (const [])
  Q x **** Q y = Q (\a -> x a ++ y a)

  -- Older versions of product-profunctor use these two instead.
  empty = Q (const [])
  Q x ***! Q y = Q (\(a, b) -> x a ++ y b)

-- or
--
-- newtype Q a b = Q (Star (Const [R]) a b)
--   deriving (Profunctor, ProductProfunctor)
--
-- unQ :: Q a b -> a -> [R]

定义默认操作。

instance Default Q A b where def = Q (pure . f)
instance Default Q B b where def = Q (pure . f)

再一次,这些隐含地构成了“遍历”元组。

-- The second parameter doesn't actually matter, but
-- the type-checker doesn't know it so we put something for it
-- to infer. Could be `Q a ()`, anything that's not ambiguous.
defQ :: Default Q a a => Q a a
defQ = def

t'' :: [R]
t'' = unQ defQ t

这实际上与one-liner内部工作方式非常相似,但目前它使用了自己的ProductProfunctor风格。

答案 1 :(得分:2)

我会按照

的方式做点什么
{-# LANGUAGE TypeFamilies, DefaultSignatures #-}

class MultiC cs where
  type MultiR cs :: *
  type MultiR cs = R
  multif :: cs -> MultiR cs
  default multif :: cs -> R
  multif = f

instance MultiC A
instance MultiC B
-- ...

instance (MultiC x, MultiC y) => MultiC (x,y) where
  type MultiR (x,y) = (MultiR x, MultiR y)
  multif (x,y) = (multif x, multif y)

然后你可以做

t :: ((A, (B, B)), ((A, B), (A, B)))
t = ((A, (B, B)), ((A, B), (A, B)))

l :: ((R, (R, R)), ((R, R), (R, R)))
l = multif t

原则上你也可以将它扩展为(伪代码)

instance (MultiC α, MultiC β, MultiC γ ... MultiC ω)
             => MultiC (α,β,γ ... ω) where
  type MultiC (α,β,γ ... ω) = (MultiR α, MultiR β ... MultiR ω)
  multif (α,β,γ...ω) = (multif α, multif β, multif γ ... multif ω)

但是正如我评论的那样,大扁平元组并不是一个好主意,因为Haskell没有适当的方法来抽象它们。

答案 2 :(得分:2)

除了Li-yao Xia的答案中列出的方法之外,这里有一个使用generics-sop的解决方案:

{-# language DeriveGeneric #-}
{-# language FlexibleContexts #-}
{-# language TypeFamilies #-}
{-# language DataKinds #-}
{-# language TypeApplications #-} -- for the Proxy

import Generics.SOP

tTol :: (Generic r, All2 C (Code r)) => r -> [R]
tTol = hcollapse . hcliftA (Proxy @C) (\(I a) -> K (f a)) . from

此解决方案将使用元组,记录和求和类型,只要该类型具有Generics.SOP.Generic实例,并且所有字段都具有C实例。