首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >哈斯克尔的可展示OOP风格的列表?

哈斯克尔的可展示OOP风格的列表?
EN

Stack Overflow用户
提问于 2015-11-07 19:13:06
回答 8查看 3.5K关注 0票数 41

我想要构建一个有一个共同属性的不同事物的列表,即可以将它们转换为字符串。面向对象的方法很简单:定义接口Showable并使感兴趣的类实现它。第二点在原则上可能是一个问题,当您不能更改类时,让我们假设情况并非如此。然后创建一个Showable列表,并使用这些类的对象填充它,而不需要任何额外的噪声(例如,向上转换通常是隐式的)。概念在Java 就在这里中的证明。

我的问题是我在Haskell有什么选择?下面我将讨论我尝试过的方法,这些方法并不能真正让我满意。

方法1:存在敏感词。但很丑。

代码语言:javascript
复制
{-# LANGUAGE ExistentialQuantification #-}
data Showable = forall a. Show a => Sh a

aList :: [Showable]
aList = [Sh (1 :: Int), Sh "abc"]

在这里,我的主要缺点是在填写列表时必须使用Sh。这与在OO语言中隐式执行的向上转换操作非常相似。

更普遍的情况是,已经在语言中的对象的虚拟包装器Showable -- Show类型类--在我的代码中添加了额外的噪声。不是很好。

方法2:非谓语。渴望但不起作用。

对于我和我真正想要的清单来说,最直接的类型是:

代码语言:javascript
复制
{-# LANGUAGE ImpredicativeTypes #-}
aList :: [forall a. Show a => a]
aList = [(1 :: Int), "abc"]

此外,(如我所知)ImpredicativeTypes“最好是脆弱的,最坏的是坏的”,它没有编译:

代码语言:javascript
复制
Couldn't match expected type ‘a’ with actual type ‘Int’
  ‘a’ is a rigid type variable bound by
      a type expected by the context: Show a => a

同样的错误也适用于"abc"。(注: 1:如果没有它,我会收到更奇怪的消息:Could not deduce (Num a) arising from the literal ‘1’)。

方法3:秩-N类型和某种函数列表(差异列表?)。

与有问题的ImpredicativeTypes不同,人们可能更喜欢更稳定和更广泛接受的RankNTypes。这基本上意味着:将所需的forall a. Show a => a从类型构造函数(即[])移到普通函数类型。因此,我们需要将列表表示为普通函数。我几乎没有听说过有这样的陈述。我听到的是差异列表。但在套餐中,主要类型是好的、老的data,因此我们返回到非预测词。我没有进一步研究这一行,因为我怀疑它会产生比在方法1中更冗长的代码,但是如果您认为它不会,请给我一个例子。

底线:在Haskell,您将如何攻击这样的任务?您能给出比OO语言更简洁的解决方案(尤其是在填写列表时,请参阅方法1中的代码注释)。你能评论一下上面列出的方法有多相关吗?

UPD (基于第一个注释):问题当然是为了可读性而简化的。真正的问题是如何存储共享相同类型类的东西,即以后可以通过多种方式进行处理(Show只有一种方法,而其他类可以有多个方法)。这就排除了建议在填充列表时正确应用show方法的解决方案。

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2015-11-07 21:13:20

HList-style解决方案可以工作,但如果只需要处理受约束的存在元素列表,而不需要其他HList机器,则可以降低复杂性。

下面是我如何在我的套餐中处理这个问题

代码语言:javascript
复制
{-# LANGUAGE ConstraintKinds, ExistentialQuantification, RankNTypes #-}

data ConstrList c = forall a. c a => a :> ConstrList c
                  | Nil
infixr :>

constrMap :: (forall a. c a => a -> b) -> ConstrList c -> [b]
constrMap f (x :> xs) = f x : constrMap f xs
constrMap f Nil       = []

这样就可以这样使用:

代码语言:javascript
复制
example :: [String]
example
  = constrMap show
              (( 'a'
              :> True
              :> ()
              :> Nil) :: ConstrList Show)

如果您有一个很大的列表,或者可能需要对一个受约束的存在元素列表进行大量操作,那么它可能是有用的。

使用这种方法,您也不需要在类型(或元素的原始类型)中编码列表的长度。这可能是一件好事,也可能是一件坏事,视情况而定。如果您想要保留所有原始类型的信息,那么HList可能是最好的选择。

此外,如果只有一个类方法(如Show的情况),我建议的方法是将该方法直接应用于列表中的每个项目,就像ErikR的答案或phadej答案中的第一个技术一样。

听起来实际的问题比仅仅列出Show-able值要复杂得多,因此,如果没有更具体的信息,很难明确地推荐其中哪一个是最合适的。

不过,这些方法中的一种可能效果很好(除非代码本身的体系结构可以简化,这样它就不会首先遇到问题)。

泛化为包含在高级类型中的存在性

这可以推广到更高的种类,如:

代码语言:javascript
复制
data AnyList c f = forall a. c a => f a :| (AnyList c f)
                 | Nil
infixr :|

anyMap :: (forall a. c a => f a -> b) -> AnyList c f -> [b]
anyMap g (x :| xs) = g x : anyMap g xs
anyMap g Nil       = []

使用它,我们可以(例如)创建一个具有Show-able结果类型的函数列表。

代码语言:javascript
复制
example2 :: Int -> [String]
example2 x = anyMap (\m -> show (m x))
                    (( f
                    :| g
                    :| h
                    :| Nil) :: AnyList Show ((->) Int))
  where
    f :: Int -> String
    f = show

    g :: Int -> Bool
    g = (< 3)

    h :: Int -> ()
    h _ = ()

通过定义以下内容,我们可以看出这是一个真正的概括:

代码语言:javascript
复制
type ConstrList c = AnyList c Identity

(>:) :: forall c a. c a => a -> AnyList c Identity -> AnyList c Identity
x >: xs  = Identity x :| xs
infixr >:

constrMap :: (forall a. c a => a -> b) -> AnyList c Identity -> [b]
constrMap f (Identity x :| xs) = f x : constrMap f xs
constrMap f Nil                = []

这使得最初的example可以使用这个新的、更一般的公式,除了将:>更改为>:之外,没有对现有的:>代码进行任何更改(即使是这种小的更改也可以避免使用模式同义词)。不过,我并不完全确定,因为我没有尝试过,有时模式同义词以我不完全理解的方式与存在量化交互作用)。

票数 19
EN

Stack Overflow用户

发布于 2015-11-07 19:19:51

由于计算在Haskell中是懒惰的,那么创建一个实际字符串的列表如何?

代码语言:javascript
复制
showables = [ show 1, show "blah", show 3.14 ]
票数 38
EN

Stack Overflow用户

发布于 2015-11-07 20:51:42

如果您真的非常想要,可以使用异构列表。这种方法对Show并不有用,因为它只有一个方法,您所能做的就是应用它,但是如果您的类有多个方法,这可能很有用。

代码语言:javascript
复制
{-# LANGUAGE PolyKinds, KindSignatures, GADTs, TypeFamilies
   , TypeOperators, DataKinds, ConstraintKinds, RankNTypes, PatternSynonyms  #-} 

import Data.List (intercalate)
import GHC.Prim (Constraint)

infixr 5 :&
data HList xs where 
  None :: HList '[] 
  (:&) :: a -> HList bs -> HList (a ': bs) 

-- | Constraint All c xs holds if c holds for all x in xs
type family All (c :: k -> Constraint) xs :: Constraint where 
  All c '[] = () 
  All c (x ': xs) = (c x, All c xs) 

-- | The list whose element types are unknown, but known to satisfy
--   a class predicate. 
data CList c where CL :: All c xs => HList xs -> CList c  

cons :: c a => a -> CList c -> CList c
cons a (CL xs) = CL (a :& xs) 

empty :: CList c 
empty = CL None 

uncons :: (forall a . c a => a -> CList c -> r) -> r -> CList c -> r 
uncons _ n (CL None) = n 
uncons c n (CL (x :& xs)) = c x (CL xs) 

foldrC :: (forall a . c a => a -> r -> r) -> r -> CList c -> r 
foldrC f z = go where go = uncons (\x -> f x . go) z 

showAll :: CList Show -> String 
showAll l = "[" ++ intercalate "," (foldrC (\x xs -> show x : xs) [] l) ++ "]" 

test = putStrLn $ showAll $ CL $ 
  1 :& 
  'a' :& 
  "foo" :& 
  [2.3, 2.5 .. 3] :& 
  None 
票数 11
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33586720

复制
相关文章

相似问题

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