Haskell 枚举列表时的奇数值
作为更大的函数定义的一部分,我需要允许函数的域(I,n)以不同的速率从I增加到n。于是我写道:Haskell 枚举列表时的奇数值,haskell,Haskell,作为更大的函数定义的一部分,我需要允许函数的域(I,n)以不同的速率从I增加到n。于是我写道: f (i, n) k = [i, (i+k)..n] 进入GHC。这返回了奇怪的结果: *Main> f (0.0, 1.0) 0.1 [0.0,0.1,0.2,0.30000000000000004,0.4000000000000001,0.5000000000000001,0.6000000000000001,0.7000000000000001,0.8,0.9,1.0] 为什么GHC返
f (i, n) k = [i, (i+k)..n]
进入GHC。这返回了奇怪的结果:
*Main> f (0.0, 1.0) 0.1
[0.0,0.1,0.2,0.30000000000000004,0.4000000000000001,0.5000000000000001,0.6000000000000001,0.7000000000000001,0.8,0.9,1.0]
为什么GHC返回值,例如0.300000000000000004而不是0.3?因为IEEE浮点算法通常不能精确表示十进制数。二进制表示法中始终存在舍入错误,有时在显示数字时会渗入表面 根据GHC将浮点数转换为十进制表示的方式,您可能会发现在Windows上,它将结果显示为预期的
0.3
。这是因为微软的运行库在如何呈现浮动方面比Linux和Mac更聪明
编辑:情况可能并非如此。当使用IEEE浮点数时,数字0.3
将编码为整数3fd3333
,而0.1+0.1+0.1
将生成编码为3fd3334
的数字,我不知道微软的运行库是否有足够的容错性,可以在显示此数据时返回到0.3
无论如何,不同处理的一个好例子是在Python交互式shell中键入
0.3
。如果是Python2.6,您将返回0.29999999999999
,如果是2.7,它将显示0.3
更好的方法是按照
map (/10) [0 .. 10]
int numerator = 1, denominator = 10;
printf("%.1f\n", (double) numerator / (double) denominator);
这需要整数,从而避免了浮点问题,并将每个数除以10。如果i、n和k是有理数,则可以使用无限精度路径:
f :: (Rational, Rational) -> Rational -> [Rational]
f (i, n) k = [i, (i+k) .. n]
符号可能需要一点时间来适应:
ghci> f (0%1, 1%1) (1%10)
[0 % 1,1 % 10,1 % 5,3 % 10,2 % 5,1 % 2,3 % 5,7 % 10,4 % 5,9 % 10,1 % 1]
上面的代码是以命令式风格编写的,带有完整的类型注释。将其类型理解为将有理数列表转换为某些I/O操作。Control.Monad
中的组合符为列表中的每个值(我们要近似的理性值)计算一个动作(putRationalToOnePlaceLn
)。你可以把它看作是一种for
循环,甚至还有一个与mapM
相同的组合符,只是参数的顺序相反。结尾的下划线是一个Haskell约定,表明它丢弃运行操作的结果,并注意有mapM
和forM
收集这些结果
为了通过安排近似输出,我们必须生成一个字符串。如果您是用C语言编写的,那么代码应该是
map (/10) [0 .. 10]
int numerator = 1, denominator = 10;
printf("%.1f\n", (double) numerator / (double) denominator);
上面的Haskell代码在结构上类似。Haskell的/
运算符的类型为
(/) :: (Fractional a) => a -> a -> a
这意味着,对于typeclassFractional
的某些实例a
,当给定相同类型的两个值a
时,您将返回该类型的另一个值
我们可以要求ghci告诉我们有关分数的信息:
ghci> :info Fractional
class (Num a) => Fractional a where
(/) :: a -> a -> a
recip :: a -> a
fromRational :: Rational -> a
-- Defined in GHC.Real
instance Fractional Float -- Defined in GHC.Float
instance Fractional Double -- Defined in GHC.Float
或
printAbrox
的定义似乎与所有有用的路标(如函数和参数的名称或类型注释)相一致。随着您对Haskell越来越有经验,也越来越熟悉,这样的命令式定义将开始显得杂乱无章
Haskell是一种函数式语言:它的优势在于通过将简单函数组装成更复杂的函数来指定什么,而不是如何。有人曾经建议Haskell处理函数的能力和Perl处理字符串的能力一样强大
在中,参数消失,留下计算结构。学习阅读和这种风格确实需要练习,但您会发现它有助于编写更清晰的代码
通过对导入进行调整,我们可以定义一个无点等效项,例如
import Control.Arrow ((***), (&&&))
import Control.Monad (join, mapM_)
import Data.Ratio (Rational, (%), denominator, numerator)
import Text.Printf (printf)
printApproxPointFree :: [Rational] -> IO ()
printApproxPointFree =
mapM_ $
putStrLn .
toOnePlace .
uncurry (/) .
join (***) fromIntegral .
(numerator &&& denominator)
where toOnePlace = printf "%.1f" :: Double -> String
我们看到了一些熟悉的部分:我们的新朋友mapM\uu
,putStrLn
,printf
,分子
,分母
还有一些奇怪的东西。Haskell的$
操作符是编写函数应用程序的另一种方法。它的定义是
f $ x = f x
(f . g) x = f (g x)
在你尝试之前,它可能不会非常有用
Prelude> show 1.0 / 2.0
<interactive>:1:0:
No instance for (Fractional String)
arising from a use of `/' at :1:0-13
Possible fix: add an instance declaration for (Fractional String)
In the expression: show 1.0 / 2.0
In the definition of `it': it = show 1.0 / 2.0
或
因此,您可以将$
视为编写括号的另一种方法
还有
这意味着函数组合。它的定义是
f $ x = f x
(f . g) x = f (g x)
我们也可以这样写
(f . g) x = f $ g x
如您所见,我们应用右手函数,然后将结果输入左手函数。您可能还记得数学教科书中的定义,例如
选择名称
,是因为其外观与凸起的圆点相似
因此,对于一系列函数组合,通常最容易通过背对背阅读来理解它
(分子和分母)
位使用from控件。箭头
。例如:
ghci> (numerator &&& denominator) $ 1%3
(1,3)
在哪里
但是从功能上考虑!如果我们能够以某种方式将/
转换成一个接受元组并使用其组件作为除法参数的函数,会怎么样?这正是我们要做的
你和Haskell迈出了伟大的第一步。祝你旅途愉快 有趣。显然我有很多维基百科要读!谢谢。@danportin十年后,我发现你的评论最有价值。@danportin不客气!我很高兴这些信息是有用的。事实上,它没有。仅仅因为你没有在数字中加小数并不意味着它不是一。因此,虽然数字可能是“完整的”,但您并没有避免浮点问题。事实上,由于您编写的方式,您可能会使问题变得更糟(因为您试图说服自己这些数字没有使用浮点)。一个更好的方法是使用
Rational
s(或某种Ratio
)类型。基本问题源于浮动二进制表示。0.1十进制
show $ 1.0 / 2.0
(f . g) x = f (g x)
(f . g) x = f $ g x
ghci> (numerator &&& denominator) $ 1%3
(1,3)
(fst tuple) / (snd tuple)
fst (a,_) = a
snd (_,b) = b