Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is it possible to have a set comprehension in haskell?

In Haskell we have list generators, such as:

[x+y | x<-[1,2,3], y<-[1,2,3]]

with which we get

[2,3,4,3,4,5,4,5,6]

Is it possible to have a set generator which doesn't automatically add an element if it is already in the list?

In our example we would obtain:

[2,3,4,5,6]

If so, how? If it is not already implemented, how would you implement it?

like image 400
elena Avatar asked Feb 18 '18 09:02

elena


People also ask

What are list comprehensions in Haskell?

Haskell has list comprehensions, which are a lot like set comprehensions in math and similar implementations in imperative languages such as Python and JavaScript. At their most basic, list comprehensions take the following form. However, x in the generator expression is not just variable, but can be any pattern.

How to create a set in Haskell?

We have one simplest way to create the set in Haskell by the use of existing list we have, also we can create it by using the empty and singleton method available in Haskell set. Let’s take a closer look at the import packages which is required to import in the program:

How to store heterogeneous values in a list in Haskell?

In Haskell, lists are homogeneous -- they can only store one kind of value ( Num, Bool, Char, etc.). If you want to store heterogeneous values, you need to use a tuple (created using parentheses): Haskell makes no distinction -- type-wise -- between lists of varying lengths, so long as they contain the same kind of data.

What are Haskell classes?

Haskell classes (also called typeclasses) are sort of like Java interfaces in that any child class derived from a particular parent class is guaranteed to implement some specific behaviour. Classes which implement Eq can be tested for equality.


2 Answers

Haskell can do this, but not quite out-of-the-box.

The basic underpinning is that a list comprehension can also be written as a monadic binding chain, in the list monad:

Prelude> [x+y | x<-[1,2,3], y<-[1,2,3]]
[2,3,4,3,4,5,4,5,6]
Prelude> [1,2,3] >>= \x -> [1,2,3] >>= \y -> return (x+y)
[2,3,4,3,4,5,4,5,6]

...or, with better readable do-syntax (which is syntactic sugar for monadic binding)

Prelude> do x<-[1,2,3]; y<-[1,2,3]; return (x+y)
[2,3,4,3,4,5,4,5,6]

In fact, there's a language extension that also turns all list comprehensions into syntactic sugar for such a monadic chain. Example in the tuple (aka writer) monad:

Prelude> :set -XMonadComprehensions 
Prelude> [x+y | x <- ("Hello", 4), y <- ("World", 5)] :: (String, Int)
("HelloWorld",9)

So really, all we need is a set monad. This is sensible enough, however Data.Set.Set is not a monad on Hask (the category of all Haskell types) but only only the subcategory that satisfies the Ord constraint (which is needed for lookup / to avoid duplicates). In the case of sets, there is however a hack that allows hiding that constraint from the actual monad instance; it's used in the set-monad package. Et voilà:

Prelude Data.Set.Monad> [x+y | x<-fromList[1,2,3], y<-fromList[1,2,3]]
fromList [2,3,4,5,6]

The hack that's needed for instance Monad Set comes at a price. It works like this:

{-# LANGUAGE GADTs, RankNTypes #-}
import qualified Data.Set as DS

data Set r where
  Prim :: (Ord r => DS.Set r) -> Set r
  Return :: a -> Set a
  Bind   :: Set a -> (a -> Set b) -> Set b
  ...

What this means is: a value of type Data.Set.Monad.Set Int does not really contain a concrete set of integers. Rather, it contains an abstract syntax expression for a computation that yields a set as the result. This isn't great for performance, in particular it means that values won't be shared. So don't use this for big sets.

There's a better option: use it directly as a monad in the proper category (which only contains orderable types to begin with). This unfortunately requires even more language-bending, but it's possible; I've made an example in the constrained-categories library.

like image 153
leftaroundabout Avatar answered Oct 19 '22 06:10

leftaroundabout


If your values can be put in Data.Set.Set (i.e. they are in class Ord) you can just apply Data.Set.toList . Data.Set.fromList to your list:

Prelude> import Data.Set
Prelude Data.Set> Data.Set.toList . Data.Set.fromList $ [x+y | x<-[1,2,3], y<-[1,2,3]]
[2,3,4,5,6]

The complexity of this would be O(n log n).

If the type obeys (Eq a, Hashable a), you can use Data.HashSet in much the same way. The average complexity is O(n).

If all you have is Eq, you have to get by with something like Data.List.nub:

Prelude> import Data.List
Prelude Data.List> nub [x+y | x<-[1,2,3], y<-[1,2,3]]
[2,3,4,5,6]

but the complexity is inherently quadratic.

like image 5
n. 1.8e9-where's-my-share m. Avatar answered Oct 19 '22 05:10

n. 1.8e9-where's-my-share m.