将“为啥函数式编程很重要”翻译成 Haskell

Posted

技术标签:

【中文标题】将“为啥函数式编程很重要”翻译成 Haskell【英文标题】:Translating "Why Functional Programming Matters" into Haskell将“为什么函数式编程很重要”翻译成 Haskell 【发布时间】:2009-06-16 08:15:45 【问题描述】:

为了丰富文化和知识,我决定学习一点 Haskell。我一直在阅读 Hughes' "Why Functional Programming Matters" 并试图将其代码翻译成真正的 Haskell。我在下面附上了我的一些尝试(对于论文的数字部分;alpha-beta 算法更有趣,但我还必须从头开始编写游戏评估器!)。

在这一点上,它更像是对 Haskell 语法的练习,而不是其他任何东西。我已经完成了一些简单的事情,比如将 repeat 翻译成原生的 Haskell iterate,翻译一些使用大量括号来进行函数组合的函数(使它们在过程中更加无点)等等。

但我的代码确实有效,我想知道它是否足够“Haskell-ish”。有哪位高手能给我一些提示吗?

-- 4.1 Newton-Raphson square roots
next n x = (x + n/x)/2.0

-- -- this is "iterate::(a->a)->a->[a]"
-- repeat f a  = a : iterate f (f a)

within eps (a:b:rest) = 
  if abs(a-b) <= eps 
    then b
    else within eps (b:rest)

sqroot a0 eps n = within eps (iterate (next n) a0)

relative eps (a:b:rest) = 
  if abs(a-b) <= eps*abs(b)
    then b
    else relative eps (b:rest)

relativesqrt a0 eps n = relative eps (iterate (next n) a0)

-- 4.2 numerical differentiation

easydiff f x h = (f (x+h) - f x) / h

differentiate h0 f x = map (easydiff f x) (iterate (/2) h0)

-- diff1a h0 eps f x = within eps (differentiate h0 f x)
diff1 h0 eps f = within eps . differentiate h0 f 

elimerror n (a:b:rest) = (b*(2**n)-a)/(2**n-1) : elimerror n (b:rest)

-- need fromIntegral to make a non-integer out of the Int which comes out of round
order (a:b:c:rest) =  fromIntegral (round (logBase 2 ((a-c)/(b-c)-1)))

improve s = elimerror (order s) s 

--diff2a h0 eps f x = within eps (improve (differentiate h0 f x))
diff2 h0 eps f = within eps . improve . differentiate h0 f 

--super s = map second (iterate improve s) -- how can we make this point-free?
super :: (RealFrac t, Floating t) => [t] -> [t] 
           -- w/o this it wants to be [double]->[double]
super = map second . iterate improve

-- second (a:b:rest) = b
second = head . tail

diff3 h0 eps f = within eps . super . differentiate h0 f

-- 4.3 integration

easyintegrate f a b = (f a + f b)*(b-a)/2

-- addpair becomes (uncurry (+))

integrate f a b = integ f a b (f a) (f b) 

integ f a b fa fb = 
  (fa+fb)*(b-a)/2 : map (uncurry (+)) (zip (integ f a m fa fm) (integ f m b fm fb))
  where m = (a+b)/2 
        fm = f m 

-- test: following should be about pi
approxpi eps = within eps (improve (integrate (\x -> 4/(1+x*x)) 0 1))
superpi eps = within eps (super (integrate (\x -> 4/(1+x*x)) 0 1))

-- is there any way to keep track of the number of iterations? state monad, but seems like a lot of work...\

【问题讨论】:

@Juliet 的最后一次编辑似乎从 inside (inside :) inside 开始搞砸了这个例子。 是的,已注意到并已修复。我现在删除了“pre”,因为它搞砸了文本;将不得不忍受糟糕的语法着色。这是一个错误??? 是的,*** 的语法高亮在 Haskell 上很烂。查看 Uservoice,似乎像往常一样,codinghorror (Jeff Atwood) 很快将所有错误报告关闭为“已拒绝”。可能解决此问题的唯一方法是直接向上游报告:code.google.com/p/google-code-prettify 当你在这里完成后,最好将你所做的任何事情发布到(比如说)Haskell 邮件列表——Haskell 版本的“为什么函数式编程很重要”将非常感谢很多! 使用
时,仍需在文本中使用html实体引用,例如“<”而不是“
                
              
                
            
【参考方案1】:

4.1 牛顿-拉夫森平方根

这两行

sqroot a0 eps n = within eps (iterate (next n) a0)
relativesqrt a0 eps n = relative eps (iterate (next n) a0)

几乎相同,所以你可以进一步抽象一些东西:

sqroot method a0 eps n = method eps (iterate (next n) a0)
relativesqrt = sqroot relative
withinsqrt   = sqroot within

4.2 数值微分

我认为将h0 作为differentiate 函数的参数没有意义,因为它只是0 限制序列的起点。 (在 Newton-Rhapson 案例中,a0 并非如此,起点可能很重要)。

我认为抽象出这个极限接近零的速率同样合适:

differentiate rate f x = map (easydiff f x) (iterate rate 1)

当然可以两者兼得:

differentiate rate h0 f x = map (easydiff f x) (iterate rate h0)

无论如何,这是一个相当武断的决定。

4.2 集成

你可以使用

zipWith (+) (integ f a m fa fm) (integ f m b fm fb)

而不是

map (uncurry (+)) (zip (integ f a m fa fm) (integ f m b fm fb))

我认为更具可读性。

【讨论】:

【参考方案2】:

对于withinrelative,我会使用保护符号:

within eps (a:b:rest)
  | abs(a-b)<=eps = b
  | otherwise = within eps (b:rest)

对于second,您可以写成!! 1。特别是最后一个是个人喜好,我猜。

您还应该给出类型签名。

编辑:如果您想混淆,请尝试:

within :: (Ord a, Num a) => a -> [a] -> a
within eps l@(_:xs) = snd. head . filter ((<= eps) . fst) $ zip zs xs
   where
    zs = zipWith (\ a b -> abs (a-b)) l xs

(类型已检查,未测试——而且我永远不知道我是否正确使用了过滤器,或者它是否必须被否定;)

【讨论】:

【参考方案3】:

Roger Costello 编写了 John Hughes paper 将原始 Miranda 代码翻译成 Haskell 的两部分摘要。这是他的文章中的part one 和part two。

【讨论】:

以上是关于将“为啥函数式编程很重要”翻译成 Haskell的主要内容,如果未能解决你的问题,请参考以下文章

Scala中的Haskell“forall”翻译

从 Haskell 翻译的 Python 中,Count 懒惰地运行

卡尔达诺即将推出翻译器兼容大部分语言于Haskell-Plutus协议

haskell列表理解通过列表monad理解

第1453期理解JavaScript的柯里化

在 Haskell 中生成 .wav 声音数据