67

I'm sorry for a question like this. I'm not too sure about the difference of the : and ++ operator in Haskell.

x:y:[] = [x,y]  

also

[x] ++ [y] = [x,y]

as for the reverse function which arose this question for me,

reverse ::[a]->[a]
reverse [] = []
reverse (x:xs) = reverse(xs)++[x]

Why doesn't the following work?

reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs):x:[]

giving a type error.

Alfie J. Palmer
  • 827
  • 2
  • 15
  • 30
DarthVader
  • 52,984
  • 76
  • 209
  • 300
  • 4
    As a side note, you can (and should) call without using parentheses: `reverse (x:xs) = reverse xs ++ [x]`, or you'll get tripped up when you work with functions with multiple arguments. – Edward Kmett Apr 16 '12 at 03:04
  • 6
    Don't call functions like `func(arg)`. That is poor Haskell. Always call functions like `func arg`. Code with clear spaces makes for more confident and readable code. – AJF Jan 11 '15 at 12:07
  • 1
    @AJFarmar `func arg` is indeed more correct Haskell than `func(arg)` but I'd argue `f(x)` is more readable than `f x` in general because it matches the majority of other languages and also the mathematical way of specifying a function. So I would say 'more confident and readable' is a matter of opinion. – icc97 Apr 30 '19 at 13:20
  • 4
    @icc97, context. In the context of writing Haskell code, `func(arg)` is as confusing as using the ["'goes down to' operator" in C](https://stackoverflow.com/questions/1642028/what-is-the-operator-in-c) – sleblanc Oct 29 '19 at 19:11

5 Answers5

108

The : operator is known as the "cons" operator and is used to prepend a head element to a list. So [] is a list and x:[] is prepending x to the empty list making it the list [x]. If you then cons y:[x] you end up with the list [y, x] which is the same as y:x:[].

The ++ operator is the list concatenation operator which takes two lists as operands and "combines" them into a single list. So if you have the list [x] and the list [y] then you can concatenate them like this: [x]++[y] to get [x, y].

Notice that : takes an element and a list while ++ takes two lists.

As for your code that does not work.

reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs):x:[]

The reverse function evaluates to a list. Since the : operator does not take a list as its first argument then reverse(xs):x is invalid. But reverse(xs)++[x] is valid.

Jimmy
  • 2,805
  • 6
  • 43
  • 57
Vincent Ramdhanie
  • 102,349
  • 23
  • 137
  • 192
27

: conses an element onto a list.

++ appends two lists.

The former has type

a -> [a] -> [a]

whereas the latter has type

[a] -> [a] -> [a]
Brian
  • 117,631
  • 17
  • 236
  • 300
  • 9
    For the Lisp-vocabulary-challenged, "cons" constructs a new list node and adds it to the head of the list. – Nate C-K Nov 30 '09 at 04:49
13

Concatenation with (++)

Maybe I'm thinking to deep into this but, as far as I understand, if you try to concatenate lists using (++) for example:

[1, 2, 3] ++ [4, 5]

(++) has to traverse the complete left list. Taking a look at the code of (++) makes it all the more clear.

(++) :: [a] -> [a] -> [a]
(++) []     ys = ys
(++) (x:xs) ys = x : xs ++ ys

Thus, it would be desirable to avoid using (++), since with every call reverse(xs)++[x] the list is getting bigger (or smaller depending on the point of view. Anyways, the program simply has to traverse another list with every call)

Example:

Lets say I implement reverse as proposed through concatenation.

reversex ::[Int]->[Int]
reversex [] = []
reversex (x:xs) = reversex(xs)++[x]

Reversing a list [1, 2, 3, 4] would look somewhat like this:

reversex [1, 2, 3, 4]
reversex [2, 3, 4]               ++ [1]
reversex [3, 4]           ++ [2] ++ [1]
reversex [4]       ++ [3] ++ [2] ++ [1]
reversex [] ++ [4] ++ [3] ++ [2] ++ [1]
         [] ++ [4] ++ [3] ++ [2] ++ [1]
         [4]       ++ [3] ++ [2] ++ [1]
         [4, 3]           ++ [2] ++ [1]
         [4, 3, 2]               ++ [1]
         [4, 3, 2, 1]

Tail Recursion using the cons operator (:)!!!

One method to deal with call stacks is by adding an accumulator. (it's not always possible to just add an accumulator. But most of the recursive functions one deals with are primitive recursive and can thus be transformed into tail recursive functions.)

With the the help of the accumulator it is possible to make this example work, using the cons operator (:). The accumulator -- ys in my example -- accumulates the current result and is passed along as a parameter. Because of the accumulator we are now able to use the cons operator to accumulate the result by appending the head of our initial list each time.

reverse' :: (Ord a) => [a] -> [a] -> [a]
reverse' (x:xs) ys = reverse' xs (x:ys)
reverse' [] ys     = ys

There is one thing to note here.

The accumulator is an extra argument. I don't know if Haskell provides default parameters, but in this case it would be nice, because you would always call this function with an empty list as the accumulator like so: reverse' [1, 2, 3, 4] []

There is plenty of literature about tail recursion and I'm sure there are a lot of similar questions on StackExchange / StackOverflow. Please correct me if you find any mistakes.

Kind regards,

EDIT 1:

Will Ness pointed out some links to really good answers for those of you who are interested:

EDIT 2:

Ok. Thanks to dFeuer and his corrections I think I understand Haskell a little bit better.

1.The $! is beyond my understanding. In all my tests it seemed to make things worse.

2.As dFeuer pointed out: The thunk representing the application of (:) to x and y is semantically identical to x:y but takes more memory. So this is special to the cons operator (and lazy constructors) and there is no need to force things in any way.

3.If I instead sumUp Integers of a list using a very similar function, strict evaluation through BangPatterns or the seq function will prevent the stack from growing too large if used appropriately. e.g.:

sumUp' :: (Num a, Ord a) => [a] -> a -> a
sumUp' (x:xs) !y = reverse' xs (x + y)
sumUp' [] y      = y

Notice the bang in front of y. I tried it out in ghci and it takes less memory.

Community
  • 1
  • 1
Nima Mousavi
  • 1,601
  • 2
  • 21
  • 30
  • Haskell never lazily delays application of a lazy constructor (because there's never any advantage to doing so), so there's no need, and no advantage, to forcing those results manually. You may even end up slowing things down by forcing things that are already evaluated! – dfeuer Feb 05 '16 at 16:39
  • @dfeuer I'm not too sure about Haskell in terms of strictness. https://wiki.haskell.org/Performance/Accumulating_parameter though does suggest that one should consider using the strict evaluation for the accumulator: "We need to fix a small problem with regards to the stack overflow. The function would accumulate (1 + acc) thunks as we pass down the list. In general, it makes sense to make your accumulator parameters strict, since its value will be needed at the very end." – Nima Mousavi Feb 05 '16 at 21:07
  • 1
    GHC generally delays function application and pattern matching, but not lazy constructor application. The reason is that a *thunk* representing the application of `(:)` to `x` and `y` is semantically identical to `x:y` but takes more memory. Constructors are special. Strict constructors are not so special. – dfeuer Feb 05 '16 at 23:49
  • Ok... I'm getting confused here. Can you provide some source to read up on the topic? As far as I understand Haskell is non-strict and would not evaluate passed arguments... or is this GHC specific. – Nima Mousavi Feb 06 '16 at 15:52
  • It is GHC specific, but I would expect the same from any serious Haskell implementation. Applying a constructor doesn't actually compute anything. Neither `x` nor `y` needs to be evaluated to get `x:y`. – dfeuer Feb 06 '16 at 16:30
  • So your saying that all $! does is force lazy constructor application? – Nima Mousavi Feb 06 '16 at 16:37
  • Not in general, no. Let's take a step back. Until you get to micro-optimization, you only need to worry about thunks that "stack up" in front of a value. This is what can happen with `foldl (+)` without optimization--you get a result that's a big chain of thunks you need to force to get to the sum. With `reverse`, the worst you could ever get would be thunk-cons-thunk-cons.... That's a pretty good result on its own! – dfeuer Feb 06 '16 at 16:55
  • 1
    But the point I was making before is that if I call `f (g x y)` generally, GHC will create a thunk representing `g x y` and pass that thunk to `f`. But if `g` is a *lazy constructor* (i.e., a constructor whose definition doesn't use `!`), GHC will instead apply `g` directly and pass the result to `f`. The semantics are *identical*, but it's faster and saves memory. – dfeuer Feb 06 '16 at 16:58
  • have you seen https://stackoverflow.com/questions/13879260/why-are-difference-lists-more-efficient-than-regular-concatenation/13879693#13879693, http://stackoverflow.com/questions/14938584/haskell-foldl-poor-performance-with/14942678#14942678, etc.? – Will Ness Feb 07 '16 at 10:13
  • @Nimi another one is https://stackoverflow.com/questions/13042353/does-haskell-have-tail-recursive-optimization/13052612#13052612, about $! etc. --- to really test the memory consumption, compile with -O2 and test run the standalone executable, always (as `appname +RTS -s`). – Will Ness Feb 17 '16 at 15:26
  • 2
    I love that in Haskell it's so natural to point to the source code of the `++` function, it's rare that you'd see that with other languages in my opinion, e.g. I've never felt to look at the internal definition for `concat` in say JavaScript or seen anyone or any JS books ever recommend looking to the source – icc97 Apr 30 '19 at 13:24
3

cons tends to be a type constructor than an operator. the example here is : can be use in let..in.. expresion but ++ is not

let x : xs = [1, 2, 3] in x -- known as type deconstructing

will return 1 but

let [x] ++ [y, z] = [1, 2, 3] in x

will return an error Variable not in scope x

To make it easy, think of cons like this

data List a = Cons a (List a) -- is equvalent with `data [a] = a:[a]`

https://en.wikibooks.org/wiki/Haskell/Other_data_structures

Additionally, if you want to reverse an array using cons. Here is an example, the knowledge is taken from Prolog

import Data.Function

reversex1 [] = []
reversex1 arr = reversex arr []

reversex [] arr = arr
reversex (x:xs) ys = reversex xs (x:ys)

main = do
    reversex1 [1..10] & print
Clite Tailor
  • 438
  • 5
  • 14
0

you can change a little and get the right result.

reversex ::[Int]->[Int] # comment this line
reversex [] = []
reversex (x:xs) = reversex(xs) ++ x : []
Yan Ding
  • 1
  • 1