Комбинатор с фиксированной точкой в 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 параметр) вместо этого не используется?
Ответы
Ответ 1
Комбинатор с фиксированной точкой находит наименее определенную неподвижную точку функции, которая является ⊥ в вашем случае (не-окончание действительно является значением undefined).
Вы можете проверить, что в вашем случае
(\x -> x * x) ⊥ = ⊥
то есть. ⊥
действительно является фиксированной точкой \x -> x * x
.
Что касается того, почему fix
определяется таким образом: главная точка fix
- позволить вам использовать анонимную рекурсию, и для этого вам не нужно больше сложное определение.
Ответ 2
В вашем примере нет даже 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
И это дает понять, почему он не работает так, как вы ожидаете. x
в вашей анонимной функции предполагается как функция, а не число. Причина этого заключается в том, что, по мнению Витуса, комбинатор фиксированных точек является способом записи рекурсии без фактической записи рекурсии. Общая идея состоит в том, что рекурсивное определение типа
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
что не имеет смысла.
Ответ 3
Я не вполне способен говорить о том, что такое "комбинатор исправлений" или что такое "наименее фиксированная точка", но можно использовать технику fix
-esque для аппроксимации определенных функций.
Перевод Scala в соответствии с примером в разделе 4.4 в Haskell:
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
уже существует? Остановитесь... Время Google!
Hoogling (a -> a) -> (a -> Bool) -> a -> a
, самый первый удар - until
.
until p f
дает результат применения f
до тех пор, пока не будет p
.
Хорошо, у вас это есть. Как оказалось, myFix = flip until
.
Ответ 4
Вероятно, вы имели в виду 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]
Здесь у вас есть вся история выполнения, явно доступная для вашего анализа. Вы можете попытаться обнаружить неподвижную точку с помощью
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 потребует более тщательного изучения.
Ответ 5
Вы не можете определить 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)