可显示的列表:OOP击败Haskell? [英] List of showables: OOP beats Haskell?

查看:103
本文介绍了可显示的列表:OOP击败Haskell?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想建立一个具有共同属性的不同事物的列表,即它们可以变成字符串。面向对象的方法很简单:定义接口 Showable ,并让感兴趣的类实现它。第二点原则上可以是一个问题,当你不能改变班级,但让我们假装情况并非如此。然后你创建一个 Showable s的列表,并用这些类的对象填充它,而没有任何额外的噪音(例如,上传通常是隐含的)。 Java中的概念验证此处给出



我的问题是我在Haskell中有哪些选项?下面我会讨论我尝试过的方法,这些方法并不能真正令我满意。

方法1 :existensials。

  { - #LANGUAGE ExistentialQuantification# - } 
data Showable = forall a。显示a => Sh a

aList :: [可显示]
aList = [Sh(1 :: Int),Shabc]

这里的主要缺点是填充列表时< Sh 的必要性。这与在OO语言中隐式执行的upcast操作非常类似。

更一般地,伪包装 Showable 用于已经在语言中的东西 - Show type class - 在我的代码中增加了额外的噪音。不好。

方法2 :impredicatives。



这种列表最直接的类型适合我,我真正渴望的是:

  { - #LANGUAGE ImpredicativeTypes# - } 
aList :: [forall a。显示a => a]
aList = [(1 :: Int),abc]

听说 ImpredicativeTypes 是脆弱的,最坏的是坏的
不能编译:

 无法与实际类型'Int'匹配的预期类型'a'
'a'是一个刚性类型变量,由
a类型限制:上下文期望值:Show a => a

以及abc。 (注意1的类型签名:如果没有它,我会收到更奇怪的消息:无法推断(数字1)来自字面值'1')。



方法3 :Rank-N类型与某种功能列表(差异列表?)。

而不是有问题的 ImpredicativeTypes 可能会更喜欢更稳定和被广泛接受的 RankNTypes 。这基本上意味着:移动
期望 forall a。显示a =>一个类型的构造函数(即 [] )为普通函数类型。因此,我们需要将列表作为普通函数进行表示。我几乎听不到有这样的表示。我听到的是差异列表。但在 Dlist package 的主要类型是旧的 data ,所以我们返回impandicatives。我没有进一步调查这条线,因为我怀疑它可能产生比方法1更详细的代码。但如果你认为它不会,请给我一个例子。



底线:你如何在Haskell中攻击这样的任务?你能给出比OO语言更简洁的解决方案吗?(尤其是代替列表 - 请参阅方法1中的代码注释)?您可以评论上述方法的相关程度吗?



UPD (基于第一条评论):问题当然是简化的可读性的目的。真正的问题更多的是如何存储共享相同类型的东西,即可以通过多种方式进行处理( Show 只有一个方法,但其他方法类可以有多个)。这个因素决定了在填写清单时适用 show 方法的解决方案。

解决方案

HList 风格的解决方案可以起作用,但如果您只需处理受限存储列表并且不需要其他 HList 机器。



以下是我在
pre > { - #LANGUAGE Con​​straintKinds,ExistentialQuantification,RankNTypes# - }

数据ConstrList c = forall a。 c a => a:> ConstrList c
|无
infixr:>

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

然后可以这样使用:

  example :: [String] 
示例
= constrMap显示
(('a'
:>真
:>()
:>无):: ConstrList Show )

如果您有大量列表或可能需要进行大量操作到一个受限制的存在列表。

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



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

听起来像实际问题比 Show -able值,所以很难给出明确的建议,哪些具体是最合适的,而不需要更具体的信息。



其中一种方法可能(除非代码本身的架构可以简化,以便它不会首先遇到问题)。



推广到包含存在在更高级的类型中



这可以推广到像这样的更高类型:

  data AnyList c f = forall a。 c a => f a:| (AnyList c f)
|无
infixr:|

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

使用这个,我们可以(例如)创建一个函数列表,其中包含显示结果类型。

  example2 :: Int  - > [String] 
example2 x = anyMap(\ m - > show(mx))
((f
:| g
:| h
:| Nil):: AnyList Show(( - >)Int))
其中
f :: Int - >字符串
f =显示

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

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

我们可以看到这是一个真正的泛化, :

 类型ConstrList c = AnyList c标识

(> :) :: forall c a 。 c a => a - > AnyList c身份 - > AnyList c标识
x>:xs =标识x:| xs
infixr>:

constrMap ::(全部a。c a => a - > b) - > AnyList c身份 - > [b]
constrMap f(Identity x:| xs)= fx:constrMap f xs
constrMap f Nil = []

这允许来自第一部分的原始示例使用这个新的更一般的公式,而不更改现有示例代码,除了将:>> 更改为>:(即使这种小小的变化可能可以通过模式同义词来避免)虽然我没有完全确定,但是我还没有完全确定,因为有时候模式同义词与存在量化以我不完全理解的方式相互作用) p>

I want to build a list of different things which have one property in common, namely, they could be turned into string. The object-oriented approach is straightforward: define interface Showable and make classes of interest implement it. Second point can in principle be a problem when you can't alter the classes, but let's pretend this is not the case. Then you create a list of Showables and fill it with objects of these classes without any additional noise (e.g. upcasting is usually done implicitly). Proof of concept in Java is given here.

My question is what options for this do I have in Haskell? Below I discuss approaches that I've tried and which don't really satisfy me.

Approach 1: existensials. Works but ugly.

{-# LANGUAGE ExistentialQuantification #-}
data Showable = forall a. Show a => Sh a

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

The main drawback for me here is the necessity for Sh when filling the list. This closely resembles upcast operations which are implicitly done in OO-languages.

More generally, the dummy wrapper Showable for the thing which is already in the language — Show type class — adds extra noise in my code. No good.

Approach 2: impredicatives. Desired but does not work.

The most straightforward type for such a list for me and what I really desire would be:

{-# LANGUAGE ImpredicativeTypes #-}
aList :: [forall a. Show a => a]
aList = [(1 :: Int), "abc"]

Besides that (as I heard)ImpredicativeTypes is "fragile at best and broken at worst" it does not compile:

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

and the same error for "abc". (Note type signature for 1: without it I receive even more weird message: Could not deduce (Num a) arising from the literal ‘1’).

Approach 3: Rank-N types together with some sort of functional lists (difference lists?).

Instead of problematic ImpredicativeTypes one would probably prefer more stable and wide-accepted RankNTypes. This basically means: move desired forall a. Show a => a out of type constructor (i.e. []) to plain function types. Consequently we need some representation of lists as plain functions. As I barely heard there are such representations. The one I heard of is difference lists. But in Dlist package the main type is good old data so we return to impredicatives. I didn't investigate this line any further as I suspect that it could yield more verbose code than in approach 1. But if you think it won't, please give me an example.

Bottom line: how would you attack such a task in Haskell? Could you give more succinct solution than in OO-language (especially in place of filling a list — see comment for code in approach 1)? Can you comment on how relevant are the approaches listed above?

UPD (based on first comments): the question is of course simplified for the purpose of readability. The real problem is more about how to store things which share the same type class, i.e. can be processed later on in a number of ways (Show has only one method, but other classes can have more than one). This factors out solutions which suggest apply show method right when filling a list.

解决方案

The HList-style solutions would work, but it is possible to reduce the complexity if you only need to work with lists of constrained existentials and you don't need the other HList machinery.

Here's how I handle this in my existentialist package:

{-# 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       = []

This can then be used like this:

example :: [String]
example
  = constrMap show
              (( 'a'
              :> True
              :> ()
              :> Nil) :: ConstrList Show)

It could be useful if you have a large list or possibly if you have to do lots of manipulations to a list of constrained existentials.

Using this approach, you also don't need to encode the length of the list in the type (or the original types of the elements). This could be a good thing or a bad thing depending on the situation. If you want to preserve the all of original type information, an HList is probably the way to go.

Also, if (as is the case of Show) there is only one class method, the approach I would recommend would be applying that method to each item in the list directly as in ErikR's answer or the first technique in phadej's answer.

It sounds like the actual problem is more complex than just a list of Show-able values, so it is hard to give a definite recommendation of which of these specifically is the most appropriate without more concrete information.

One of these methods would probably work out well though (unless the architecture of the code itself could be simplified so that it doesn't run into the problem in the first place).

Generalizing to existentials contained in higher-kinded types

This can be generalized to higher kinds like this:

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       = []

Using this, we can (for example) create a list of functions that have Show-able result types.

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 _ = ()

We can see that this is a true generalization by defining:

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                = []

This allows the original example from the first part of this to work using this new, more general, formulation with no changes to the existing example code except changing :> to >: (even this small change might be able to be avoided with pattern synonyms. I'm not totally sure though since I haven't tried and sometimes pattern synonyms interact with existential quantification in ways that I don't fully understand).

这篇关于可显示的列表:OOP击败Haskell?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆