我正在两个模型之间构建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
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'
为什么?什么是完成我在这里尝试做的“正确”方式?
我想要完成的是隐藏调用者的实现。实际的数据结构可能是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)
答案 0 :(得分: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]
现在,如果用户导入了您的模块,它可以折叠ListResult,因为它是可折叠的,但它无法解压缩以获取列表,因为构造函数未导出。因此,如果您稍后将ListResult
的定义更改为data ListResult a = ListResult (Seq a)
而list
也使用Seq
而不是列表,则该更改对用户完全不可见。< / p>
答案 1 :(得分: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
您可以看到这些方法的返回类型从不具有“t foo”类型。因此,您无法构造一个多态的值,您可以选择Foldable
个实例。
但是,是基于类型构造函数的类,其构造函数出现在至少一个方法的返回类型中。例如,有
class Pointed p where
point :: a -> p a
由pointed包提供。还有Monoid
提供的base
课程:
class Monoid m where
mempty :: m
mappend :: m -> m -> m
mconcat :: [m] -> m
你可以像这样组合这两个类:
points :: (Pointed p, Monoid (p a)) => [a] -> p a
points = mconcat . map point
例如,在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]
等
答案 2 :(得分:5)
listFree :: Foldable f => a -> f Int
这样做可以保证您可以生成用户可能需要的任何可折叠文件。当然,你永远不会遵守这个承诺,因为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]
此处我提供了值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
现在,我们可以使用Foldable
执行foo
项内容,例如:
> Data.Foldable.sum foo
6
但除了Foldable
曝光之外,我们不能对它做任何事情:
> print foo
No instance for (Show (SomeFoldable Int)) blah blah blah
根据需要调整代码很容易:
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]
但请记住,存在量化有其缺点。无法打开SomeFoldable
来获取下面的具体Foldable
。原因与你的函数签名在开头是错误的原因相同:它承诺结果类型多态:它无法保留的承诺。
unwrap :: Foldable f => SomeFoldable a -> f a -- impossible!
unwrap (F xs) = xs -- Nope. Keep dreaming. This won't work.
答案 3 :(得分:0)
更新的问题有一个非常不同的答案。 (因为我的另一个答案是好的,但回答不同的问题,我将离开它。)
创建abstract data type。缺点是在模块中定义一个新类型
newtype Struct a = Struct [a]
这里我假设[a]
是您要隐藏的具体实现。然后,添加Foldable
实例。
deriving instance Foldable Struct
-- could do this on the newtype definition line above,
-- but it doesn't fit with the flow of the answer
在模块边界处,您可以通过导出Struct
类型而不是Struct
构造函数来隐藏实际实现。您的来电者可以使用Struct a
进行的唯一操作就是调用Foldable
方法。
答案 4 :(得分:0)
正如sepp2k在他的回答中所说, 问题是我们必须有一个单形返回类型。 就像他答案中的清单一样。
但是,我们仍然可以返回一个具有命名类型的包装类型,
FoldableContainer
,但它是Foldable
。
为此,我们需要GADTts。
{-# LANGUAGE GADTs #-}
module Main where
import Prelude hiding (foldr)
import Data.Foldable
data Struct = Struct
data FoldableContainer a where
F :: Foldable f => f a -> FoldableContainer a
instance Foldable FoldableContainer where
foldMap g (F f) = foldMap g f
main = do
print $ foldr (+) 0 $ list Struct
print $ foldr (+) 0 $ listFree Struct
listFree :: a -> FoldableContainer Int
listFree s = F [10]
class TestClass a where
list :: a -> FoldableContainer Int
instance TestClass Struct where
list s = F [10]
但请注意,虽然这可行,但有些人说可能确实更好
只返回一个清单。为什么?首先,我们不创建额外的类型,
第二,每个构建的F
都需要携带
Foldable
字典,因为它在编译时是未知的。
我不知道这是多少性能损失,但不能忘记。
另一方面,我们不再需要列表作为中间类型,
在sum
上使用Set Int
的情况无需先转换为[Int]
。