首页 > 解决方案 > 使用分数生成列表如何工作?

问题描述

如果我想用输入生成一个列表:

[3.1,5.1..8.1]

GHC 8.6.3 返回:

[3.1,5.1,7.1,9.099999999999998]

我的问题不是 的近似值9.1,而是为什么 GHC 列出的列表比以下解决方案多一个元素。 在我在 中找到的文档中GHC.Enum,将其enumFromThenTo转换为类似于以下内容:

    -- | Used in Haskell's translation of @[n,n'..m]@ with
    --   @[n,n'..m] = enumFromThenTo n n' m@, a possible implementation
    --   being @enumFromThenTo n n' m = worker (f x) (c x) n m@,
    --   @x = fromEnum n' - fromEnum n@, @c x = bool (>=) (<=) (x > 0)@
    --   @f n y
    --      | n > 0 = f (n - 1) (succ y)
    --      | n < 0 = f (n + 1) (pred y)
    --      | otherwise = y@ and
    --   @worker s c v m
    --      | c v m = v : worker s c (s v) m
    --      | otherwise = []@

所以下面的代码:

import Data.Bool
eftt n s m = worker (f x) (c x) n m
     where x = (fromEnum s) - (fromEnum n)
c x = bool (>=) (<=) (x > 0)
f n y 
    | n > 0 = f (n-1) (succ y)
    | n < 0 = f (n+1) (pred y)
    | otherwise = y
worker s c v m
    | c v m = v: worker s c (s v) m
    | otherwise = []

在与以前相同的输入上,这将返回此列表:

[3.1,5.1,7.1]

中定义的真正实现GHC.Enum如下:

enumFromThenTo x1 x2 y = map toEnum [fromEnum x1, fromEnum x2 .. fromEnum y]

但是没有实例化Enum DoubleEnum FloatGHC.Enum

因此,当我尝试使用以下代码重现此内容时:

import Prelude(putStrLn,show)
import GHC.Enum(toEnum,fromEnum,Enum,enumFromThenTo)
import GHC.Base(map)

main = putStrLn (show (_enumFromThenTo 3.1 5.1 8.1))

_enumFromThenTo :: (Enum a) => a -> a -> a -> [a]
_enumFromThenTo x1 x2 y = map toEnum [fromEnum x1, fromEnum x2 .. fromEnum y]

我编译:

$ ghc -package ghc -package base <file.hs>

结果又是:

[3.0,5.0,7.0]

这里发生了什么,使得输出变为:

[3.1,5.1,7.1,9.099999999999998]

?

标签: haskellghc

解决方案


嗯,这是instance Enum Double

instance Enum Double where
  enumFromThenTo = numericEnumThenFromTo

实现在这里

numericEnumFromThenTo   :: (Ord a, Fractional a) => a -> a -> a -> [a]
numericEnumFromThenTo e1 e2 e3
    = takeWhile predicate (numericEnumFromThen e1 e2)
                                where
                                 mid = (e2 - e1) / 2
                                 predicate | e2 >= e1  = (<= e3 + mid)
                                           | otherwise = (>= e3 + mid)

比实现更重要的是上面的注释:

-- These 'numeric' enumerations come straight from the Report

其中指的是(2010)报告中的这段话

对于Floatand DoubleenumFrom族的语义由上面的规则给出Int,除了当元素变得大于 e 3 + i∕2 对于正增量 i 或当它们变得小于 e 3 + i∕2时列表终止对于负 i。

(其中 e 3是指上限,而 i 是增量。)

您找到的评论Enum和其中的实现class Enum都无关紧要。注释只是详细说明如何实现实例的示例代码并且给出的实现在类内部,因此可以被任何东西覆盖。


推荐阅读