>>>flip fix (0 :: Int) (\a b -> putStrLn "abc")
Output: "abc"
这是使用
flip fix
的简化版本。我在某些youtube视频中看到了这种使用方式,这些视频可能来自Google技术讲座或其他一些讲座。
有人可以给我一些指针(不是某些内存地址,谢谢!),确切地说是
fix
是什么。我从官方网站上的文档中了解一般定义。而且我浏览了互联网上的许多内容,只是找不到一个全面且易于理解的答案。flip fix
对我来说似乎是个谜。在那个特定的函数调用中实际上发生了什么?顺便说一句,我只在2个月前才选择Haskell。而且我不太擅长数学:(
这是完整的代码,如果有兴趣的话,由进行演示的人员共享:
(哦,这是Wiki链接,解释了游戏
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!"
最佳答案
fix
是fixed-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
永远不会递归。它实际上仅在此处用于混淆代码。关于haskell-翻转修复/修复,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15523093/