如何从支持某些API的函数返回非具体值?

men*_*ics 7 haskell

我正在两个模型之间构建API.我不在乎它是否返回[]或Seq或任何可折叠的东西都没问题.但如果我尝试这样做,我会收到错误.

module Main where
import Prelude hiding (foldr)
import Data.Foldable
import Data.Sequence

data Struct = Struct

main = do
  print $ foldr (+) 0 $ list Struct
  print $ foldr (+) 0 $ listFree Struct


listFree :: Foldable f => a -> f Int
listFree s = singleton 10

class TestClass a where
  list :: Foldable f => a -> f Int

instance TestClass Struct where
  list s = singleton 10
Run Code Online (Sandbox Code Playgroud)

listFree和列表定义都给出了相同的错误:

TestFoldable.hs:19:12:
Could not deduce (f ~ [])
from the context (Foldable f)
  bound by the type signature for
             list :: Foldable f => Struct -> f Int
  at TestFoldable.hs:19:3-15
  `f' is a rigid type variable bound by
      the type signature for list :: Foldable f => Struct -> f Int
      at TestFoldable.hs:19:3
In the expression: [10]
In an equation for `list': list s = [10]
In the instance declaration for `TestClass Struct'
Run Code Online (Sandbox Code Playgroud)

这是为什么?什么是"正确"的方式来完成我在这里尝试做的事情?

我想要完成的是隐藏调用者的实现.实际的数据结构可能是Seq,IntMap或其他任何东西,很可能不是列表.

我得到的回答是"只返回一个列表".但这意味着转换,不是吗?如果它是一个1,000,000元素结构怎么办?仅仅因为API的限制将其转换为中间数据结构似乎是一个糟糕的解决方案.

这是一个普遍的问题.如何具有符合某些API的返回值?隐藏具体实现,以便实现者可以自由选择最适合他们的结构,并且可以在不必更改API用户的情况下对其进行更改.

放置它的另一种方法是:如何返回接口而不是具体类型?

结束注意:

StackOverflow上的Haskell社区是(SuperlativeCompliment c => forall c.c)

存在量化似乎是这种情况的一般解决方案.

另一种考虑的可能性,即不是一般解决方案,但可能适用于这种特定情况,可能避免存在性解决方案所需的额外包装值,即为客户端返回折叠的闭包:

list :: a -> ((Int -> b -> b) -> b -> b)
list = \f a0 -> foldr f a0 (singleton 10)
Run Code Online (Sandbox Code Playgroud)

sep*_*p2k 8

这是为什么?

该类型Foldable f => a -> f Int并不意味着该功能可能会返回它想要的任何可折叠.这意味着该函数将返回用户想要的任何类型.即,如果用户在需要列表的上下文中使用该函数,并且如果他在需要Seq的上下文中使用它也应该工作.由于您的定义显然不是这种情况,因此与其类型不匹配.

什么是"正确"的方式来完成我在这里尝试做的事情?

最简单的方法是让你的函数返回一个列表.

但是,如果您确实需要隐藏使用用户列表的事实,最简单的方法是在列表周围创建包装类型而不导出该类型的构造函数.就像这样:

module Bla (ListResult(), list) where
data ListResult a = ListResult [a]

instance Foldable (ListResult a) where
    foldr op s (ListResult xs) = foldr op s xs

list s = ListResult [10]
Run Code Online (Sandbox Code Playgroud)

现在,如果用户导入您的模块,它可以折叠ListResult,因为它是可折叠的,但它无法解压缩以获取列表,因为构造函数未导出.所以,如果你以后改变ListResult的定义,data ListResult a = ListResult (Seq a)list还使用Seq而不是一个名单,这一变化将是用户完全不可见.


Dan*_*ner 5

Foldable类只规定了破坏实例的方法Foldable,并没有构建实例.完整的课程方法列表如下:

class Foldable t where
    fold :: Monoid m => t m -> m
    foldMap :: Monoid m => (a -> m) -> t a -> m
    foldr :: (a -> b -> b) -> b -> t a -> b
    foldl :: (a -> b -> a) -> a -> t b -> a
    foldr1 :: (a -> a -> a) -> t a -> a
    foldl1 :: (a -> a -> a) -> t a -> a
Run Code Online (Sandbox Code Playgroud)

您可以看到这些方法的返回类型从不具有"t foo"类型.因此,您无法在Foldable您选择的实例中构造多态的值.

然而,存在类以上的量,构造显示在至少一个方法的返回类型类型构造函数.例如,有

class Pointed p where
    point :: a -> p a
Run Code Online (Sandbox Code Playgroud)

尖头包提供.还Monoid提供以下课程base:

class Monoid m where
    mempty :: m
    mappend :: m -> m -> m
    mconcat :: [m] -> m
Run Code Online (Sandbox Code Playgroud)

你可以像这样组合这两个类:

points :: (Pointed p, Monoid (p a)) => [a] -> p a
points = mconcat . map point
Run Code Online (Sandbox Code Playgroud)

例如,在ghci中:

> points [7,3,8] :: Set Int
fromList [3,7,8]
> points [7,3,8] :: First Int
First { getFirst = Just 7 }
> points [7,3,8] :: Last Int
Last { getLast = Just 8 }
> points [7,3,8] :: [Int]
[7,3,8]
> points [7,3,8] :: Seq Int
fromList [7,3,8]
Run Code Online (Sandbox Code Playgroud)

等等


Dan*_*ton 5

sepp2k已经提供了一个很好的答案,但允许我采取类似但略有不同的角度.你所做的是提供结果类型多态.你写了:

listFree :: Foldable f => a -> f Int
Run Code Online (Sandbox Code Playgroud)

这样做可以保证您可以生成用户可能需要的任何折叠.当然,你永远不会遵守这个承诺,因为Foldable它不提供任何类似构造函数的功能.

所以你要做的就是处理泛型.你想做一个弱的承诺:这个函数listFree会产生一些 Foldable,但将来它可能会改变.您今天可以使用常规列表来实现它,但稍后,您可能会使用其他内容重新实现它.并且您希望此实现细节就是:实现细节.您希望该函数的合同(类型签名)保持不变.

听起来像是另一个奇怪而令人困惑的Haskell扩展的工作!存在量化!

{-# LANGUAGE ExistentialQuantification #-}

import Prelude hiding (foldr, foldl, foldr1, foldl1)
import Data.Foldable

data SomeFoldable a = forall f. Foldable f => F (f a)

foo :: SomeFoldable Int
foo = F [1,2,3]
Run Code Online (Sandbox Code Playgroud)

这里我提供了一个值foo,但它有类型SomeFoldable Int.我不告诉你 Foldable是,简单地认为这是一些可折叠的.为方便起见,SomeFoldable可以很容易地成为一个实例Foldable.

instance Foldable SomeFoldable where
  fold (F xs) = fold xs
  foldMap f (F xs) = foldMap f xs
  foldr step z (F xs) = foldr step z xs
  foldl step z (F xs) = foldl step z xs
  foldr1 step (F xs) = foldr1 step xs
  foldl1 step (F xs) = foldl1 step xs
Run Code Online (Sandbox Code Playgroud)

现在我们可以做Foldable一些事情foo,例如:

> Data.Foldable.sum foo
6
Run Code Online (Sandbox Code Playgroud)

但除了Foldable暴露的内容外,我们无法做任何事情:

> print foo
No instance for (Show (SomeFoldable Int)) blah blah blah
Run Code Online (Sandbox Code Playgroud)

您可以根据需要调整代码以使其更加轻松:

data Struct = Struct

main = do
  print $ foldr (+) 0 $ list Struct
  print $ foldr (+) 0 $ listFree Struct


listFree :: a -> SomeFoldable Int
listFree s = F [10]

class TestClass a where
  list :: a -> SomeFoldable Int

instance TestClass Struct where
  list s = F [10]
Run Code Online (Sandbox Code Playgroud)

但请记住,存在量化有其缺点.没有办法解开SomeFoldable以获得Foldable下面的混凝土.原因与你的函数签名在开头是错误的原因相同:它承诺结果类型多态:它无法保留的承诺.

unwrap :: Foldable f => SomeFoldable a -> f a   -- impossible!
unwrap (F xs) = xs    -- Nope. Keep dreaming. This won't work.
Run Code Online (Sandbox Code Playgroud)