【问题标题】:Minimal interface over Data.List and Data.Sequence in Haskell for prependable listsHaskell 中 Data.List 和 Data.Sequence 的最小接口,用于前置列表
【发布时间】:2016-02-12 18:27:34
【问题描述】:

我发现我正在编写重复的函数来处理 Data.List 和 Data.Sequence,为了对它们进行概括,我写道:

import qualified Data.Sequence as Seq

class PrependableList pl where
  empty :: pl a
  first ::  pl a -> a
  prepend :: a -> pl a -> pl a
  len :: pl a -> Int

instance PrependableList [a] where
  empty = []
  first = head
  prepend = (:)
  len = length

instance PrependableList (Seq.Seq a) where
  empty = Seq.empty
  first seq = Seq.index seq 0
  prepend = (Seq.<|)
  len = Seq.length

上面的实现没有编译,说明 kind 不匹配:

Kind mis-match
The first argument of `PrependableList' should have kind `* -> *',
but `[a]' has kind `*'
In the instance declaration for `PrependableList [a]'

Kind mis-match
The first argument of `PrependableList' should have kind `* -> *',
but `Seq.Seq a' has kind `*'
In the instance declaration for `PrependableList (Seq.Seq a)'

从我在其他条目(例如In Haskell, why isn't there a TypeClass for things that can act like lists?)中看到的情况来看,可能无法完全概括多个类似列表的数据结构。

但是以上 4 个函数至少可以泛化为 Data.List 和 Data.Sequence 吗?

【问题讨论】:

  • 只需将[a] 更改为[]
  • 正如错误消息指出的那样,PrependableList 需要一个一元类型构造函数 (* -&gt; *),而不是一个空类型构造函数 (*)。因此,您应该编写instance PrependableList [] 和实例PrependableList Seq.Seq。此外,除非您使用 &lt;| 运算符的限定名称,否则您的代码 sn-p 不会编译,即 Seq.&lt;|
  • 感谢@MathematicalOrchid,确实克服了编译错误。接下来的问题是如何在函数的类型定义中使用 PrependableList。当我添加“PrependableList a”时,编译器会指出另一种不匹配:“Kind mis-match The first argument of PrependableList' should have kind * -> ', but a' has kind '”(不能使星号正确显示)
  • 感谢@Jubobs——修复了序列。
  • @AlbertCardona 你想要PrependableList pl =&gt; pl a这样的东西。

标签: haskell


【解决方案1】:

我知道您已经自己回答了这个问题,但我想指出 Haskell 近年来一直在发展,以尝试更广泛地封装这些想法,并且您的类已经是其他两个现有类的组合:

import Control.Monad (MonadPlus, mzero, mplus);
import Data.Foldable (Foldable, length, toList);
import Data.Sequence (Seq)
import qualified Data.Sequence as S

-- same as your PrependableList
class Prependable p where
    empty :: p a
    first ::  p a -> a
    prepend :: a -> p a -> p a
    len :: p a -> Int

-- generic newtype wrapper
newtype WrapP p x = WrapP { unwrapP :: p x } deriving (Show, Eq, Ord)

-- any foldable monadplus is prependable.
instance (MonadPlus m, Foldable m) => Prependable (WrapP m) where
    empty = WrapP mzero
    prepend = (WrapP .) . (. unwrapP) . mplus . return
    first = head . toList . unwrapP
    len = length . unwrapP 

-- specific cases
type SeqP x = WrapP Seq x
type ListP x = WrapP [] x

此外,由于WrapPnewtype,您通常可以将WrapPunwrapP 替换为id,只要您还调整类型签名即可;特别是我们可以直接把你的四个函数写成:

empty   :: (Foldable m, MonadPlus m) => m x
empty = mzero
first   :: (Foldable m, MonadPlus m) => m x -> x
first = head . toList
prepend :: (Foldable m, MonadPlus m) => x -> m x -> m x
prepend = mplus . return
len     :: (Foldable m, MonadPlus m) => m x -> Int
len = length

这些现在可以在列表和 Data.Sequence 上使用。 (如果 Prelude.lengthData.Foldable.length 不同,您可能需要更新版本的 GHC,否则您必须导入 Prelude 隐藏 length,然后从 Data.Foldable 显式导入。

由于Data.Sequence 已经为Seq 定义了MonadPlusFoldable,因此您最好在没有新类或实例声明的情况下编写8 行代码;此外,其中两个是直接替换(len 用于lengthempty 用于mzero),如果您只使用已经为您定义的版本,则可以多节省 4 行代码。

(稍微绕道,first 也是来自Control.Comonadextract,其中(非空!)列表的extracthead,其duplicatetakeWhile (not . null) . tails(使用@ 987654346@,但忽略了最后一个元素[])。我认为您不能纯粹基于Comonad 基础架构来定义len;为此,您确实需要Foldable 类型。非空列表可以通过将其写为newtype NEList = NEList x (Maybe (NEList x)) 将其置于明显的comonad 形式中,然后我们可以将NEList 替换为Cofree Maybe 以查看它是comonad。)

【讨论】:

    【解决方案2】:

    感谢大家,这是正确的代码和最小的使用示例:

        import qualified Data.Sequence as Seq
    
        class PrependableList pl where
            empty :: pl a
            first ::  pl a -> a
            prepend :: a -> pl a -> pl a
            len :: pl a -> Int
    
        instance PrependableList [] where
            empty = []
            first = head
            prepend = (:)
            len = length
    
        instance PrependableList Seq.Seq where
            empty = Seq.empty
            first seq = Seq.index seq 0
            prepend = (Seq.<|)
            len = Seq.length
    
        -- Example usage
        prependAll :: (PrependableList pl) => pl a -> [a] -> pl a
        prependAll plist xs = foldr prepend plist xs
    
        main = do
            print $ prependAll Seq.empty [1..5]
            print $ prependAll [] [1..5]
    

    哪些打印:

    $ ./pl 
    fromList [1,2,3,4,5]
    [1,2,3,4,5]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-20
      • 1970-01-01
      相关资源
      最近更新 更多