Why does map return an additional element when usi

2020-04-03 10:49发布

I've just started learning Haskell and found a strange thing.

Let we have a list:

ghci> [0,2..5]
[0,2,4]

It has 3 elements. When I use map with this list I get 3 element as output, for example:

ghci> map (+ 1) [0,2..5]
[1,3,5]
ghci> map (* 2) [0,2..5]
[0,4,8]
ghci> map (`div` 2) [0,2..5]
[0,1,2]

But when I use fractional division I get 4 elements in output list:

ghci> map (/ 2) [0,2..5]
[0.0,1.0,2.0,3.0]
ghci> length (map (/ 2) [0,2..5])
4

Could you please explain why map may return more elements then it was?

Thank you!

1条回答
beautiful°
2楼-- · 2020-04-03 11:30

It's due to the implementation of Enum for Float and Double:

> [0,2..5] :: [Float]
[0.0,2.0,4.0,6.0]

It's not map doing it, but Float. Specifically, if you call enumFromThenTo 0 2 5 :: [Float], you'll get the same list. You'll see the same results for Double.

This is hinted at in the haskell report, but the behavior is definitely non-obvious. Essentially, it comes down to the implementation of numericEnumFromThenTo (we're getting into some Haskell internals here), which is used by the Enum Float instance:

numericEnumFromThenTo n n' m = takeWhile p (numericEnumFromThen n n')  
    where  
        p | n' >= n   = (<= m + (n' - n) / 2)  
          | otherwise = (>= m + (n' - n) / 2) 

numericEnumFromThen n m = iterate (+ (m - n)) n

So you have numericEnumFromThen 0.0 2.0 generating the list [0.0,2.0,4.0,6.0,8.0,...], then you do takeWhile p on that, which in this case is equivalent to the function \x -> x <= 5.0 + (2.0 - 0.0) / 2, or more simply \x -> x <= 6.0, which is why 6.0 is included in the output list of [0.0,2.0..5.0].

I can't explain why it's implemented this way, that's pretty baffling to me too, but hopefully I've answered the how for its implementation.

查看更多
登录 后发表回答