假设我们在Haskell中有一个函数f,它返回类型R的结果,如果我们得到了C类实例的长元组t,那么我们如何才能很好地获得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]发布于 2017-03-09 14:06:13
这是通用编程的一个相当标准的用例。这是一个非常常见的用例,有一些库不需要编写泛型实现。
单衬垫
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声明为将A和B映射到R的一种Default方式。
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您可能不想键入类型签名。这是不能推断的,因为类型类恶作剧。但是,它可以根据输入的类型来计算。因此,您可以定义一个类型族(一个类型级别的函数),它将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乘积-终止子,二
您还可以通过选择正确的终止符来获得列表。
正如它的名称所暗示的那样,产品终止函数一般地与ProductProfunctor一起工作。这个终止函数收集列表中的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风格。
发布于 2017-03-09 11:47:45
我会沿着……做一些事情
{-# 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没有正确的方法来抽象它们。
发布于 2017-03-09 21:07:40
除了李姚夏的答案中列出的方法之外,这里还有一个使用仿制药-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实例,这个解决方案就可以处理元组、记录和和类型。
https://stackoverflow.com/questions/42692384
复制相似问题