【问题标题】:PointFree programming无点编程
【发布时间】:2013-11-10 09:52:00
【问题描述】:

我想知道是否可以将递归函数转换为无点定义。

如果我们采用简单的递归函数。

factorial :: int-> int
factorial 0=1
factorial n+1= (n+1) *factorial n

如果我们有非递归定义。

factorial :: int-> int
factorial n= product [1..n]  
   <=> factorial n = product.enumFromTo 1 n 
   <=> factorial   = product.enumFromTo 1  

但是我怎样才能对递归定义做同样的事情呢?

我问的原因是我想让transformationsApply无积分。

transformationsApply :: Eq a => a -> ([a] -> [a]) -> [([a], [a])] -> [a] -> Maybe [a]
transformationsApply _ _ [] _= Nothing
transformationsApply wc func ((a,b):xs) (y:ys)
   = orElse (transformationApply wc func (y:ys) (a,b)) 
            (transformationsApply wc func xs (y:ys))

上面使用的transformationApply定义为

transformationApply :: Eq a => a -> (([a] -> [a]) -> ([a] -> (([a], [a]) -> Maybe [a])))

transformationApply wc func xs (a,b) 
   = mmap ((substitute wc b).func) (match wc a xs)

【问题讨论】:

  • 保持代码可读性比编写一些复杂的无点形式更好。
  • orelse 是什么(类型)? substitute? mmap? match?

标签: haskell pointfree


【解决方案1】:

我建议让您的代码更具可读性,而不是尝试将其转换为难以理解的无点形式。

将函数转换为无点形式的最简单方法是询问 lambdabot。现在,当您拥有递归函数时,您可以使用 fix 将其转换为非递归函数。这是fact 函数的示例(从 lambdabot 给出的直接转换),您可以看到它的可读性。

import Control.Monad
import Data.Function


if' :: Bool -> a -> a -> a
if' True  x _ = x
if' False _ y = y

fact = fix $ ap (flip if' 1 . (0 ==)) . ap (*) . (. subtract 1)

【讨论】:

  • cabal install pointfree,直接使用pointfree。在这种情况下,我不觉得转换后的版本如此令人不快。 flip if' 1 设置 then 情况,(0 ==) 是(部分应用的)条件,(. subtract 1) 选择第二个参数并从中减去一个,ap (*) 与第一个参数重复出现并将结果与(*).
  • @JonPurdy 是的,但我不确定您如何将模式匹配转换为无点。这将需要一个额外的 Eq 约束。
【解决方案2】:

虽然您可以以自动方式将大多数递归函数转换为无点形式,但结果可能很丑陋而且毫无用处,正如其他人所展示的那样。

但是,您在这里所拥有的并不是真正的一般递归。您在列表上有一个 折叠,通过将函数应用于每个列表元素然后组合它们来构建结果。

Haskell 具有用于折叠和以其他方式在列表上递归的构建块,这将产生更漂亮的结果。 (尽管您仍然需要判断它是否是一种改进。)最常见的此类构建块是函数foldrmap。事实上,你的例子可以重写为:

transformationsApply :: Eq a => a -> ([a] -> [a]) -> [([a], [a])] -> [a] -> Maybe [a]
transformationsApply wc func xs (y:ys) =
    foldr orElse Nothing (map (transformationApply wc func (y:ys)) xs)
transformationsApply _ _ [] [] = Nothing

(最后一行是因为一个极端情况:您的原始函数在所有情况下检查最后一个参数是否为空 except 当 xs 列表为时。也许你不需要这个。)

【讨论】:

    【解决方案3】:

    factorial开始,首先我们需要一个类型区分器,为我们封装数字上的模式匹配,

    num :: (Num a) => b -> (a -> b) -> a -> b
    num z _  0 = z
    num _ nz x = nz x
    

    现在,使用身份(g =&lt;&lt; f) x = g (f x) x,我们写

    import Control.Applicative
    import Data.Function (fix)
    
    fact :: (Num c, Enum c) => c -> c
    fact = num 1 ((*) =<< (fact.pred))
    

    要获得真正的无点形式,我们需要将fact 向右推,然后fix 它:

         = num 1 . ((*) =<<) . (.pred) $ fact
         = fix (num 1 . ((*) =<<) . (.pred))
    

    继续您的第二个函数,作为Ørjan Johansen points out,它本质上是一个正确的折叠(如果我们忽略参数强制顺序的复杂性,由您使用的显式模式确定):

    transformationsApply :: Eq a => a -> ([a] -> [a]) -> [([a], [a])] -> [a] -> Maybe [a]
    transformationsApply a b c d
       = foldr (orElse . transformationApply a b d) Nothing c
    

    看起来已经很不错了combinatory。所以这里recursionfoldr封装,而不是被fix显式表达。

    我们可以在参数顺序上多做一些调整,但这就没那么有趣了:

       = flip (foldr . (orElse .) . transformationApply a b) Nothing d c
       = flip (flip (foldr . (orElse .) . transformationApply a b) Nothing) c d
       = ...
    

    【讨论】:

      【解决方案4】:

      我们需要一个内联分支操作,而

      if' True  t _ = t
      if' False _ e = e
      

      流行另一种形式更有用。我就叫它p

      p check a | check a   = Right a
                | otherwise = Left  a
      

      足以定义if'

      if' b t e = either (const e) (const t) . p (const b)
      

      但有更好的属性

      (\a -> if' (check a) (t a) (e a)) == either e t . p check
      

      它让我们摆脱了factorial中的第一个分支

      factorial' = either (const 1) (\n -> n * factorial (n-1)) . p (==0)
      

      这意味着我们只需要以某种方式消除(\n -&gt; n * factorial (n-1)) 位。使用来自Control.Arrow(&amp;&amp;&amp;)“扇出”组合器

      (&&&) :: (a -> b) -> (a -> c) -> (a -> (b, c))
      

      我们有

      (\n -> n * factorial (n-1)) == uncurry (*) . (id &&& factorial . (+ negate 1))
      

      这有点烦人,因为(-) 切片含糊不清。我先把它留下来,但我们的最终功能是

      factorial'' :: Int -> Int
      factorial'' = 
        either (const 1) 
               (uncurry (*) 
                . (id &&& factorial'' . (+ negate 1))) 
        . p (==0)
      

      并且定义的递归部分工作得很好。从技术上讲,该类型比那更通用

      factorial'' :: (Eq a, Num a) => a -> a
      

      这可能有点令人不满意,因为我们现在需要Eq 约束,而模式匹配版本不需要。如果我们的数字有自然的析构函数可供我们使用,我们可以做得更好。例如,如果我们使用“自然”构造函数和析构函数定义我们自己的自然变量

      data Nat = Z | S Nat
      
      cons :: Maybe Nat -> Nat
      cons Nothing  = Z
      cons (Just n) = S n
      
      uncons :: Nat -> Maybe Nat
      uncons Z     = Nothing
      uncons (S n) = Just n
      

      我们可以在没有进一步的模式匹配的情况下完成所有其他工作。让我们得到一个plus 和一个mult

      --  Z   + n = n
      --  S m + n = S (m + n)    
      plus n = maybe n (S . plus n) . uncons
      
      --  Z   * n = Z
      --  S m * n = n + (m * n)
      mult n = maybe Z (plus n . mult n) . uncons
      

      有了这些,我们就有了

      naturalFact :: Nat -> Nat
      naturalFact = maybe (S Z) (uncurry mult . (S &&& naturalFact)) . uncons
      

      这是相当令人满意的。在不消除递归步骤并完全进入 Squiggol 风格的无点编程或 Church-encoding 我们所有的模式匹配完全消失的情况下,这可能会尽可能令人满意。

      【讨论】:

        猜你喜欢
        • 2020-03-03
        • 2010-10-25
        • 2023-02-03
        • 1970-01-01
        • 2018-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-30
        相关资源
        最近更新 更多