首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何映射同一个类的一个长的实例元组?

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

Stack Overflow用户
提问于 2017-03-09 10:08:33
回答 3查看 113关注 0票数 0

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

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

代码语言:javascript
复制
-- 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]
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-03-09 14:06:13

这是通用编程的一个相当标准的用例。这是一个非常常见的用例,有一些库不需要编写泛型实现。

单衬垫

代码语言:javascript
复制
import Generics.OneLiner

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

gfoldMap是数据类型字段中的一个折叠,假设它们都是给定类型类型的实例,允许一个人一般地收集结果,这里是C

代码语言:javascript
复制
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))。

乘积-功能函数

代码语言:javascript
复制
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}

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

我们定义了一种新的类型,以避免孤儿污染环境。

代码语言:javascript
复制
newtype P a b = P { unP :: a -> b } deriving
  (Profunctor, ProductProfunctor)

我们将f声明为将AB映射到R的一种Default方式。

代码语言:javascript
复制
instance Default P A R where def = P f
instance Default P B R where def = P f

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

代码语言:javascript
复制
-- Type signature required
t'_ :: (R, R, R, R, R, R, R)
t'_ = unP def t

您可能不想键入类型签名。这是不能推断的,因为类型类恶作剧。但是,它可以根据输入的类型来计算。因此,您可以定义一个类型族(一个类型级别的函数),它将AB的出现替换为元组类型中的R。实际上,任何类似于F a b c d e f ( F是类型构造函数)的类型都将被转换为F R R R R R R

代码语言:javascript
复制
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UndecidableInstances #-}

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

专门化def (它具有类型推断-不友好类型)来使用这个家族:

代码语言:javascript
复制
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中的一个等价的函数。

代码语言:javascript
复制
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]

定义默认操作。

代码语言:javascript
复制
instance Default Q A b where def = Q (pure . f)
instance Default Q B b where def = Q (pure . f)

再一次,这些元素被隐式地组成为“遍历”元组。

代码语言:javascript
复制
-- 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风格。

票数 4
EN

Stack Overflow用户

发布于 2017-03-09 11:47:45

我会沿着……做一些事情

代码语言:javascript
复制
{-# 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)

那你就可以

代码语言:javascript
复制
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

原则上,您也可以将其扩展为有(伪代码)。

代码语言:javascript
复制
instance (MultiC α, MultiC β, MultiC γ ... MultiC ω)
             => MultiC (α,β,γ ... ω) where
  type MultiC (α,β,γ ... ω) = (MultiR α, MultiR β ... MultiR ω)
  multif (α,β,γ...ω) = (multif α, multif β, multif γ ... multif ω)

但是正如我所说的,大扁元组并不是一个好主意,因为Haskell没有正确的方法来抽象它们。

票数 2
EN

Stack Overflow用户

发布于 2017-03-09 21:07:40

除了李姚夏的答案中列出的方法之外,这里还有一个使用仿制药-sop的解决方案

代码语言:javascript
复制
{-# 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实例,这个解决方案就可以处理元组、记录和和类型。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42692384

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档