假设我们在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]
答案 0 :(得分:3)
这是通用编程的一个非常标准的用例。这是一个常见的用例,有些库不必编写通用实现。
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)
)。
{-# 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
映射A
和B
到R
的方式。
instance Default P A R where def = P f
instance Default P B R where def = P f
库隐式地将其扩展为元组,将A
和B
的任何元组映射到相应的R
元组。
-- Type signature required
t'_ :: (R, R, R, R, R, R, R)
t'_ = unP def t
您可能不想输入类型签名。由于类型类shenanigans,无法推断它。但是,它可以根据输入的类型计算。因此,您可以定义一个类型族(一个类型级函数),它在元组类型中用A
替换B
和R
的出现。实际上,任何看起来像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以一般方式与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
实例。