7

in this Haskell example the result is sometimes one longer than the base list. Any clue for this?

Prelude> let x3 = [1,3..10]
Prelude> x3
[1,3,5,7,9]
Prelude> length x3
5
Prelude> length $ map (+1) x3
5
Prelude> length $ map (*3) x3
5
Prelude> length $ map (/2) x3
6
Prelude> length $ map (/1) x3
6
Prelude> length $ map (`div`1) x3
5
Prelude> map log x3
   [0.0,1.0986122886681098,1.6094379124341003
   ,1.9459101490553132,2.1972245773362196,2.3978952727983707]
Prelude> length it
6
duplode
  • 33,731
  • 7
  • 79
  • 150
  • 7
    This must be due to the desugaring of `..`-defined lists, and how it behaves oddly with floating point numbers. See [Haskell ranges and floats](http://stackoverflow.com/q/7290438/2751851). – duplode Sep 24 '15 at 19:58
  • 2
    `[1,3..10]` is a bad idea, especially using floating point numbers. Use `[1,3..9]` instead, or `[1,3..11]`. Number `10` falls exactly in the middle of the last "step" (from 9 to 11), so rounding will decide whether the closest approximation to 10 is 9 or 11. – chi Sep 24 '15 at 20:11
  • 2
    @chi, in my opinion, that puts the blame in the wrong place. Fractional numbers (and *especially* floating point representations) have no business being in the `Enum` class. The sane way to get a list of floats like this is `map fromIntegral [1,3..whatever]`. – dfeuer Sep 24 '15 at 20:55
  • ok I got it, the wole list is type converted (as a range list, not per element) – Leopold Faschalek Sep 25 '15 at 14:09
  • great question! `Prelude> length (map log x3) - length x3` => `1`. Very funny looking. :) Compare with `Prelude> map (`asTypeOf` 1) x3` => `[1,3,5,7,9]` ; `Prelude> map (`asTypeOf` 1.0) x3` => `[1.0,3.0,5.0,7.0,9.0,11.0]`. **No**, the list is not "converted". It does not exist at all, until its use. What exists, is the definition: `enumFromThenTo 1 3 10`. It is taken to be polymorphic, i.e. can have elements of any type which is in Num class. So until type is known, list doesn't exist. So at each use site it is built anew, esp. if types are different (if same types, it might get cached). – Will Ness Sep 25 '15 at 21:40
  • ("can have elements of any type which is in Num class." all elements must be of the same type, of course). – Will Ness Sep 25 '15 at 22:00

1 Answers1

4

As @duplode suggested, it has to do with how .. behaves with floating point vs. integers:

Prelude> let x3 = [1,3..10] :: [Double]
Prelude> length x3
6
Prelude> let x3 = [1,3..10] :: [Int]
Prelude> length x3
5

Update in response to the comment...

A definition like this in ghci:

let xs = [1,3..11]

is polymorphic. The .. is a shortcut for the enumFromThenTo function:

let xs = enumFromThenTo 1 3 11

The result is polymorphic - the above expression has type:

ghci> :t xs
x3 :: (Enum t, Num t) => [t]

If you just print it out, Haskell chooses to type is as [Integer], and you get:

[1,3,5,7,9]

Here the Integer version of enumFromThenTo is being used.

However, if you apply a floating point operation to the list, Haskell interprets it as [Double], and then it becomes one element longer for the reasons explained above:

ghci> map (+ 0.0) x3
[1.0,3.0,5.0,7.0,9.0,11.0]  -- length 6 now!

So the map operation "changes" the length of the list only because it changes the interpretation of its type which changes which enumFromThenTo function is called to construct it.

Upshot: A list defined like [1,3..11] does not have known length until its type is determined.

Onyxite
  • 951
  • 7
  • 12
ErikR
  • 51,541
  • 9
  • 73
  • 124