Probably your confusion comes from the fact that you are used to eager evaluation, whereas Haskell uses lazy evaluation.
For example, if you were to use the definition
repeat' x = x : repeat' x
to evaluate the expression repeat' 10
eagerly, then you would get
repeat' 10 ==>
10 : repeat' 10 ==>
10 : 10 : repeat' 10 ==>
10 : 10 : 10 : repeat' 10 ==>
...
and this would loop forever.
With lazy evaluation it is different. If you have the expression repeat' 10
in a certain context, this is not evaluated until the result of repeat' 10
is required.
As soon as you take values from the list, the above steps are executed, but only as many of them get executed as requested.
So, in Haskell applying your function to some value does not create an infinite data structure that is completely loaded in memory at some point in time: this is impossible because there is only a finite amount of memory and a computation that terminates can only take a finite amount of time. It rather creates a program from which you can pull any finite number of elements, i.e. any finite prefix of the infinite list.
Note that the finite prefix is not represented as a plain list
10 : 10 : 10 : []
but as a term like
10 : 10 : 10 : repeat' 10
So, suppose you want to compute with a finite list, e.g. take 2 [1, 2, 3]
:
take 2 (1 : 2 : 3 : []) ==>
1 : take 1 (2 : 3 : []) ==>
1 : 2 : take 0 (3 : []) ==>
1 : 2 : []
Now, the same but with your infinite list:
take 2 (repeat' 10) ==> -- repeat' x = x : repeat' x
take 2 (10 : repeat' 10) ==> -- take n (x : xs) = x : take (n - 1) xs
10 : take 1 (repeat' 10) ==> -- repeat' x = x : repeat' x
10 : take 1 (10 : repeat' 10) ==> -- take n (x : xs) = x : take (n - 1) xs
10 : 10 : take 0 (repeat' 10) ==> -- take 0 _ = []
10 : 10 : []
repeat
could be defined in an imperative language asdef repeat(x) = { let xs = new LinkedListNode(x); xs.next = xs; xs }
, which models the recursion in the data rather than the control flow. Where does that linked list end? Nowhere, never, there is no end node.repeat' x = let xs = x : xs in xs
. The subtle difference is that this implementation creates one self-referential node because the recursion is in the data, whereas the code in the question actually creates a (lazy) infinite list because the recursion is in the function call.