How can I return a non-bad value from a function that supports some API?

I am creating an API between two models. I don't care if he returns [Seq] or Seq or anything complicated. But if I try to do this, I get errors.

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 

Both listFree and list parameters give the same error:

 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' 

Why? And what is the β€œright” way to accomplish what I'm trying to do here?

What I'm trying to do is hide the implementation from the caller. The actual data structure may be Seq, IntMap, or something else, and most likely not a list.

I get answers that say "just return the list." But that means a transformation, right? What if it is a structure of 1,000,000 elements? Converting it to an intermediate data structure just because of API limitations seems like a bad solution.

And this is a common problem. How to get return value corresponding to some API? To hide a specific implementation, the developer can choose any structure that best suits them, and can change it without changing the API users.

Another way to express this: how can I return an interface instead of a specific type?

Final note:

Haskell community on StackOverflow (SuperlativeCompliment c => forall c. C)

Existential quantification seems to be a common solution to this situation.

Another possibility to consider, which is not a general solution, but could work in this particular case, which can avoid the additional shell value required by the existential solution, is to return the folding of the fold for the client:

 list :: a -> ((Int -> b -> b) -> b -> b) list = \f a0 -> foldr f a0 (singleton 10) 
+7
source share
5 answers

sepp2k already provided a good answer, but let me take a similar but slightly different angle. What you did is result type polymorphism. You wrote:

 listFree :: Foldable f => a -> f Int 

This means that you can create any folding that the user may need. Of course, you could never fulfill this promise because Foldable does not provide any constructor-like functions.

So what are you trying to do with generics. You want to make weak promises: the listFree function will produce some Foldable , but it may change in the future. You can implement it using a regular list today, but later you can re-implement it with something else. And you want this implementation detail to be just that: implementation detail. You want the contract for this function (type signature) to remain the same.

Sounds like work for another weird and confusing Haskell extension! Existential quantification!

 {-# LANGUAGE ExistentialQuantification #-} import Prelude hiding (foldr, foldl, foldr1, foldl1) import Data.Foldable data SomeFoldable a = forall f. Foldable f => F (fa) foo :: SomeFoldable Int foo = F [1,2,3] 

Here I specify the value of foo , but it is of type SomeFoldable Int . I am not telling you that it is t21, just that it is some kind of folding. SomeFoldable can easily be made a Foldable instance for convenience.

 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 

Now we can do Foldable things with foo , for example:

 > Data.Foldable.sum foo 6 

But we can do nothing with this, except that Foldable provides:

 > print foo No instance for (Show (SomeFoldable Int)) blah blah blah 

Easily adapt your code to work as you wish:

 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] 

But remember, existential quantification has its drawbacks. Unable to deploy SomeFoldable to get Foldable concrete Foldable . The reason for this is the same reason that your function signature was incorrect at the beginning: it promises a polymorphism of the result type: a promise that it cannot keep.

 unwrap :: Foldable f => SomeFoldable a -> fa -- impossible! unwrap (F xs) = xs -- Nope. Keep dreaming. This won't work. 
+5
source

Why is this?

The type Foldable f => a -> f Int does not mean that the function can return any folding that it wants. This means that the function will return no matter what the user wants. That is, if the user uses this function in a context where a list is required to work, and if he uses it in a context where Seq is required, which should also work. Since this clearly does not match your definition, it does not match its type.

And what is the β€œright” way to accomplish what I'm trying to do here?

The easiest way is to simply return the function to the list.

However, if you need to hide the fact that you are using lists from your users, the easiest way would be to create a wrapper type around the list, rather than export this type constructor. That is, something like:

 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] 

Now, if the user imports your module, he can drop the ListResult list because it is complex, but he cannot unpack it to get into the list because the constructor is not exported. Therefore, if you later change the definition of ListResult to data ListResult a = ListResult (Seq a) and list to use Seq instead of the list, this change will be completely invisible to the user.

+8
source

The Foldable class provides only methods for destroying Foldable instances, but not for building instances. A complete list of class methods is given below:

 class Foldable t where fold :: Monoid m => tm -> m foldMap :: Monoid m => (a -> m) -> ta -> m foldr :: (a -> b -> b) -> b -> ta -> b foldl :: (a -> b -> a) -> a -> tb -> a foldr1 :: (a -> a -> a) -> ta -> a foldl1 :: (a -> a -> a) -> ta -> a 

You can see that the return type of these methods is never of type "t foo". Thus, you cannot create a value that is polymorphic in which you select the Foldable instance.

However, there are classes on type constructors for which the constructor appears in the return type of at least one method. For example, there is

 class Pointed p where point :: a -> pa 

provided by pointed package. There is also the Monoid class provided by base :

 class Monoid m where mempty :: m mappend :: m -> m -> m mconcat :: [m] -> m 

You can combine these two classes as follows:

 points :: (Pointed p, Monoid (pa)) => [a] -> pa points = mconcat . map point 

For example, in 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] 

and etc.

+5
source

The updated question has a completely different answer. (Since my other answer is good, but answering another question, I am going to leave it.)

Create an abstract abstract type . In short, this is a definition of a new type in your module

 newtype Struct a = Struct [a] 

Here, I suggested that [a] is the specific implementation that you want to hide. Then you add a Foldable instance.

 deriving instance Foldable Struct -- could do this on the newtype definition line above, -- but it doesn't fit with the flow of the answer 

At the border of the module, you hide the actual implementation by exporting the Struct type, but not the Struct constructor. The only thing your callers can do with Struct a is call Foldable methods on it.

0
source

As sepp2k said in his answer, the problem is that we must have a monomorphic return type. Like a list in his answer.

However, we can still return the wrappertype type with the named type, FoldableContainer , but it is Foldable . For this we need GADTts.

 {-# LANGUAGE GADTs #-} module Main where import Prelude hiding (foldr) import Data.Foldable data Struct = Struct data FoldableContainer a where F :: Foldable f => fa -> FoldableContainer a instance Foldable FoldableContainer where foldMap g (F f) = foldMap gf 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] 

Note that while this works, it is perhaps, as some say, really better to simply return the list. What for? First of all, we do not create an extra type, and, secondly, each built F must carry a Foldable dictionary, since it is unknown at compile time. I do not know how much this punishment is, but it must not be forgotten. On the other hand, we no longer need lists as intermediate types that sum uses on Set Int , no need to convert to [Int] .

0
source

All Articles