haskell - 翻转修复/修复

时间:2013-03-20 12:07:59

标签: haskell functional-programming

>>>flip fix (0 :: Int) (\a b -> putStrLn "abc")
Output: "abc"

这是使用flip fix的简化版本 我在一些youtube视频中看到了这种方式,可能来自Google技术谈话或其他一些谈话。

有人可以给我一些指针(不是一些内存地址,谢谢!),确切地说fix是什么。我知道官方网站上的文档的一般定义。我在互联网上浏览了很多东西,但却找不到一个全面且易于理解的答案。

flip fix对我来说只是一个谜。在那个特定的函数调用中实际发生了什么?

顺便说一下,我2个月前才选择了Haskell。而且我不擅长数学:(


这是完整的代码,由进行该演示的人共享,如果有人感兴趣的话:

(哦,这是解释游戏的维基链接mastermind Click

module Mastermind where

import Control.Monad
import Data.Function
import Data.List
import System.Random

data Score = Score
  { scoreRightPos :: Int
  , scoreWrongPos :: Int
  }
  deriving (Eq, Show)

instance Read Score where
  readsPrec _ r = [ (Score rp wp, t)
                  | (rp, s) <- readsPrec 11 r
                  , (wp, t) <- readsPrec 11 s
                  ]

calcScore :: (Eq a) => [a] -> [a] -> Score
calcScore secret guess = Score rightPos wrongPos
  where
    rightPos    = length [() | (a, b) <- zip secret guess, a == b]
    wrongPos    = length secret - length wrongTokens - rightPos
    wrongTokens = guess \\ secret

pool :: String
pool = "rgbywo"

universe :: [String]
universe = perms 4 pool

perms :: Int -> [a] -> [[a]]
perms n p = [s' | s <- subsequences p, length s == n, s' <- permutations s]

chooseSecret :: IO String
chooseSecret = do
  i <- randomRIO (0, length universe - 1)
  return $ universe !! i

guessSecret :: [Score] -> [String]-> [String]
guessSecret _      []    = []
guessSecret ~(s:h) (g:u) = g : guessSecret h [g' | g' <- u, calcScore g' g == s]

playSecreter :: IO ()
playSecreter = do
  secret <- chooseSecret
  flip fix (0 :: Int) $ \loop numGuesses -> do
    putStr "Guess: "
    guess <- getLine
    let
      score       = calcScore secret guess
      numGuesses' = numGuesses + 1
    print score
    case scoreRightPos score of
      4 -> putStrLn $ "Well done, you guessed in " ++ show numGuesses'
      _ -> loop numGuesses'

playBoth :: IO ()
playBoth = do
  secret <- chooseSecret
  let
    guesses     = guessSecret scores universe
    scores      = map (calcScore secret) guesses
    history     = zip guesses scores
  forM_ history $ \(guess, score) -> do
    putStr "Guess: "
    putStrLn guess
    print score
  putStrLn $ "Well done, you guessed in " ++ show (length history)

playGuesser :: IO ()
playGuesser = do
  input <- getContents
  let
    guesses     = guessSecret scores universe
    scores      = map read $ lines input
    history     = zip guesses scores
  forM_ guesses $ \guess -> do
    putStrLn guess
    putStr "Score: "
  case snd $ last history of
    Score 4 0 -> putStrLn $ "Well done me, I guessed in " ++ show (length history)
    _         -> putStrLn "Cheat!"

2 个答案:

答案 0 :(得分:15)

fixfixed-point operator。正如您可能从它的定义中知道的那样,它计算函数的固定点。这意味着,对于给定的函数f,它会搜索x的值f x == x

如何为任意函数找到这样的值?

我们可以将x视为无限期f (f (f ... ) ...))的结果。显然,由于它是无限的,因此在其前面添加f不会改变它,因此f x将与x相同。当然,我们无法表达无限期,但我们可以将fix定义为表达该想法的fix f = f (fix f)

有意义吗?

会不会终止?是的,它会,但只是因为Haskell是一种懒惰的语言。如果f不需要它的参数,它就不会对它进行求值,因此计算将终止,它不会永远循环。如果我们在一个总是使用其参数的函数上调用fix(它是严格的),它将永远不会终止。所以有些功能有一个固定点,有些则没有。而Haskell的懒惰评估确保我们计算它,如果存在的话。

为什么fix有用?

它表示递归。任何递归函数都可以使用fix表示,而不需要任何额外的递归。所以fix是一个非常强大的工具!假设我们有

fact :: Int -> Int
fact 0 = 1
fact n = n * fact (n - 1)

我们可以使用fix消除递归,如下所示:

fact :: Int -> Int
fact = fix fact'
  where
    fact' :: (Int -> Int) -> Int -> Int
    fact' _ 0 = 1
    fact' r n = n * r (n - 1)

这里,fact'不是递归的。递归已移至fix。这个想法是fact'接受一个函数,它将用于递归调用,如果需要的话。如果您使用fix fact'的定义展开fix,则会看到它与原始fact的内容相同。

因此,您可以使用仅具有原始fix运算符的语言,否则不允许任何递归定义,并且您可以使用递归定义表达所有内容。

回到您的示例

让我们查看flip fix (0 :: Int) (\a b -> putStrLn "abc"),它只是fix (\a b -> putStrLn "abc") (0 :: Int)。现在让我们来评估一下:

fix (\a b -> putStrLn "abc") =
(\a b -> putStrLn "abc") (fix (\a b -> putStrLn "abc")) =
\b -> putStrLn "abc"

因此整个表达式的评估结果为(\b -> putStrLn "abc") (0 :: Int),而putStrLn "abc"只是\a b -> putStrLn "abc"。因为函数fix忽略了它的第一个参数,{{1}}永远不会递归。它实际上仅用于混淆代码。

答案 1 :(得分:4)

这只是编写递归lambda的一种有趣方式,我可以想到为什么这样做的两种可能性:

  • 程序员想要混淆新手。
  • 他来自一种对递归更具限制性的语言(如某些LISP,或者ML可能?)

你可以更清楚地重写代码:

    loop secret 0
where
    loop secret numGuesses = do
         putStr "Guess: "
         guess <- getLine
         let
             score       = calcScore secret guess
             numGuesses' = numGuesses + 1
         print score
         case scoreRightPos score of
           4 -> putStrLn $ "Well done, you guessed in " ++ show numGuesses'
           _ -> loop secret numGuesses'

区别在于您必须手动传递secret,这是递归lambda所避免的(这可能是用fix编写它的另一个原因)

为了更深入地理解修复,goog为“y-combinator”