Комбинатор неподвижной точки в Haskell
комбинатор с фиксированной точкой не всегда дает правильный ответ с учетом определения:
fix f = f (fix f)
следующий код не прекращает:
fix (x->x*x) 0
конечно, fix
не всегда может дать правильный ответ, но мне было интересно, можно ли это улучшить?
конечно, для приведенного выше примера можно реализовать некоторое исправление, которое выглядит как
fix f x | f x == f (f x) = f x
| otherwise = fix f (f x)
и дает правильный выход.
в чем причина выше определение (или что-то еще лучше, так как это только функция дескриптора с 1 параметром) не используется вместо этого?
5 ответов
fixed point combinator находит наименее определенную фиксированную точку функции, которая в вашем случае ⊥ (non-termination действительно является неопределенным значением).
вы можете увидеть, что в вашем случае
(\x -> x * x) ⊥ = ⊥
то есть ⊥
действительно является фиксированной точкой \x -> x * x
.
Что касается почему fix
определено таким образом: основная точка fix
- разрешить использовать анонимная рекурсия и для этого вам не нужно более сложное определение.
ваш пример даже не typecheck:
Prelude> fix (\x->x*x) 0
<interactive>:1:11:
No instance for (Num (a0 -> t0))
arising from a use of `*'
Possible fix: add an instance declaration for (Num (a0 -> t0))
In the expression: x * x
In the first argument of `fix', namely `(\ x -> x * x)'
In the expression: fix (\ x -> x * x) 0
и что дает ключ к тому, почему он не работает, как вы ожидаете. The x
в вашей анонимной функции должна быть функция, а не число. Причина этого, как предполагает Витус, в том, что комбинатор fixpoint-это способ записи рекурсии без фактического написания рекурсии. Общая идея заключается в том, что рекурсивное определение типа
f x = if x == 0 then 1 else x * f (x-1)
можно записать как
f = fix (\f' x -> if x == 0 then 1 else x * f' (x-1))
ваш пример
fix (\x->x*x) 0
таким образом соответствует выражению
let x = x*x in x 0
что не имеет смысла.
я не совсем квалифицирован, чтобы говорить о том, что такое" комбинатор фиксированной точки "или что такое" наименее фиксированная точка", но можно использовать fix
- esque техника для примерное определенные функции.
перевод Scala по примеру раздел 4.4 Хаскеллу:
sqrt' :: Double -> Double
sqrt' x = sqrtIter 1.0
where sqrtIter guess | isGoodEnough guess = guess
| otherwise = sqrtIter (improve guess)
improve guess = (guess + x / guess) / 2
isGoodEnough guess = abs (guess * guess - x) < 0.001
эта функция работает путем многократного " улучшения "догадки, пока мы не определим, что она"достаточно хороша". Эта картина может быть отвлеченно:
myFix :: (a -> a) -- "improve" the guess
-> (a -> Bool) -- determine if a guess is "good enough"
-> a -- starting guess
-> a
fixApprox improve isGoodEnough startGuess = iter startGuess
where iter guess | isGoodEnough guess = guess
| otherwise = iter (improve guess)
sqrt'' :: Double -> Double
sqrt'' x = myFix improve isGoodEnough 1.0
where improve guess = (guess + x / guess) / 2
isGoodEnough guess = abs (guess * guess - x) < 0.001
см. также Scala на примере раздела 5.3. fixApprox
может использоваться для аппроксимации фиксированной точки improve
функция передана в него. Он неоднократно вызывает improve
на входе до выхода isGoodEnough
.
в самом деле, вы можете использовать myFix
не только для приближений, но и для точных ответов.
primeAfter :: Int -> Int
primeAfter n = myFix improve isPrime (succ n)
where improve = succ
isPrime x = null [z | z <- [2..pred x], x `rem` z == 0]
это довольно глупый способ генерации простых чисел, но он иллюстрирует суть. Хм...теперь я удивляюсь...делает что-то вроде myFix
уже существует? Остановка...Время Hoogle!
Hoogling (a -> a) -> (a -> Bool) -> a -> a
самый первый хит until
.
until p f
дает результат примененияf
доp
держит.
Ну вот и все. Как оказалось,myFix = flip until
.
вы, вероятно, имели в виду iterate
:
*Main> take 8 $ iterate (^2) (0.0 ::Float)
[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]
*Main> take 8 $ iterate (^2) (0.001 ::Float)
[1.0e-3,1.0000001e-6,1.0000002e-12,1.0000004e-24,0.0,0.0,0.0,0.0]
*Main> take 8 $ iterate (^2) (0.999 ::Float)
[0.999,0.99800104,0.9960061,0.9920281,0.9841198,0.96849173,0.93797624,0.8797994]
*Main> take 8 $ iterate (^2) (1.0 ::Float)
[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]
*Main> take 8 $ iterate (^2) (1.001 ::Float)
[1.001,1.002001,1.0040061,1.0080284,1.0161213,1.0325024,1.0660613,1.1364866]
здесь у вас есть вся история выполнения, явно доступная для вашего анализа. Ты можешь!--8-->попытка для обнаружения фиксированной точки с
fixed f from = snd . head
. until ((< 1e-16).abs.uncurry (-).head) tail
$ _S zip tail history
where history = iterate f from
_S f g x = f x (g x)
а то
*Main> fixed (^2) (0.999 :: Float)
0.0
но fixed (^2) (1.001 :: Float)
будет петля бесконечно, поэтому вам нужно будет разработать отдельное тестирование для сходимости, и даже тогда обнаружение репеллентных фиксированных точек, таких как 1.0, потребует более тщательного исследования.
вы не можете определить fix
Как вы упомянули с f x
может даже не быть сопоставимым. Например, рассмотрим пример ниже:
myFix f x | f x == f (f x) = f x
| otherwise = myFix f (f x)
addG f a b =
if a == 0 then
b
else
f (a - 1) (b + 1)
add = fix addG -- Works as expected.
-- addM = myFix addG (Compile error)