patternMinor
Square root calculation with Newton's method
Viewed 0 times
calculationmethodwithnewtonrootsquare
Problem
I am currently learning Haskell in parallel of following the Scala course proposed by Coursera.
To practice Haskell I try to implement exercices and/or course example from this course.
So there is a simpler square root estimation implementation:
I welcome any feedback on this, I mostly aim for clarity and simplicity (one-liner welcome, but I do not specificaly aim for that ;) ).
To practice Haskell I try to implement exercices and/or course example from this course.
So there is a simpler square root estimation implementation:
sqrt' :: Double -> Double -> Double
sqrt' x guess
| guessIsGoodEnough = guess
| otherwise = sqrt' x improvedGuess
where
-- Test the precision of the guess
precision = 0.001
guessIsGoodEnough = abs (guess * guess - x) < precision * x
-- Improve our guessed square
improvedGuess = (guess + x / guess) / 2I welcome any feedback on this, I mostly aim for clarity and simplicity (one-liner welcome, but I do not specificaly aim for that ;) ).
Solution
Somewhere between primitive recursion and grossly clever one-liners we can find a happy medium using functions from the Prelude.
To start though I'll look at the cosmetic. Immediately I'm confused by name and signature of the function, the square root is a function of a single number, not two. It's confusing for your function to be named
Next, I would rewrite the function so that
Also those comments are unnecessary, I think it's fairly clear from your descriptive names what's going on.
Alright now for the fun stuff. Let's begin by identifying the individual components of what your function is doing.
Your function is a very imperative, monolithic implementation of those three components, but in Haskell-land composition is king. Let's try tackling each separately and see how they can be composed.
Number one is easy since we already know the math we're going to be doing, just roll up the
Number two requires we know a bit about creating sequences in Haskell, which of course are usually lists, so we can take a look at the documentation for
Now the third bullet on my list should really be broken in two, one bullet point for the predicate, and another for searching the sequence with it. But that's alright of course, our personal design documents don't have to be perfect from the get-go to be useful.
The predicate is of course the
And if we look back at the documentation for
By using higher order functions to handle the iterative and recursive aspects of the problem, we end up with a solution that almost reads like prose.
Appendix: Edge Cases & Errors
There's a lot that could go in this section, so I'll just point out a few things so this doesn't get tedious.
This could happen if we changed our code to not produce an infinite list of guesses (
Appendix: Two-liner
After writing your function in terms of higher order functions it's usually trivial to then turn it into a one-liner, just inline all of the function calls. In this particular case we've got a two-liner unless your terminal happens to be very wide.
I wouldn't use that version, I think it's a little opaque with all of that inlin
To start though I'll look at the cosmetic. Immediately I'm confused by name and signature of the function, the square root is a function of a single number, not two. It's confusing for your function to be named
sqrt' when typically the tick mark is used in Haskell to distinguish strict functions from lazy ones, or to name successive iterations on some value. When you're browsing your Haddock documentation later or coming back to this code in three years you'll be stupefied by sqrt', but probably have a pretty good idea what refineSqrtGuess might do.Next, I would rewrite the function so that
x isn't copied around everywhere. Since its value doesn't change, make that clear.refineSqrtGuess :: Double -> Double -> Double
refineSqrtGuess x = refine
where
refine guess
| guessIsGoodEnough = guess
| otherwise = refine improvedGuess
where
-- Test the precision of the guess
precision = 0.001
guessIsGoodEnough = abs (guess * guess - x) < precision * x
-- Improve our guessed square
improvedGuess = (guess + x / guess) / 2Also those comments are unnecessary, I think it's fairly clear from your descriptive names what's going on.
Alright now for the fun stuff. Let's begin by identifying the individual components of what your function is doing.
- It's applying a mathematical function to a value.
- It's producing a sequence by applying that function recursively.
- It's choosing the first value from that sequence which matches a predicate.
Your function is a very imperative, monolithic implementation of those three components, but in Haskell-land composition is king. Let's try tackling each separately and see how they can be composed.
Number one is easy since we already know the math we're going to be doing, just roll up the
improvedGuess value into a function.next :: Double -> Double -> Double
next x guess = (guess + x / guess) / 2Number two requires we know a bit about creating sequences in Haskell, which of course are usually lists, so we can take a look at the documentation for
Data.List. As it turns out there's a function that repeatedly applies a function f to a value x for us, iterate. Here's how we'd use it.guesses :: Double -> Double -> [Double]
guesses x initial = iterate (next x) initialNow the third bullet on my list should really be broken in two, one bullet point for the predicate, and another for searching the sequence with it. But that's alright of course, our personal design documents don't have to be perfect from the get-go to be useful.
The predicate is of course the
guessIsGoodEnough value, expressed as a function.acceptGuess :: Double -> Double -> Bool
acceptGuess x guess = abs (guess * guess - x) < precision * x
where precision = 0.001And if we look back at the documentation for
Data.List, sure enough under the section heading "Searching with a predicate" find looks like it does what we want, just note that it returns a Maybe a value. This is of course because the predicate we provide might not match any element of the given list, which now obligates me to point out that you should always code defensively and consider the totality of your functions on any given input. I'll address some concerns in iterations on my solution but here's how we'd put it all together.import Data.List (find)
import Data.Maybe (fromJust)
refineSqrtGuess :: Double -> Double -> Double
refineSqrtGuess x initial = fromJust $ find (acceptGuess x) (guesses x initial)By using higher order functions to handle the iterative and recursive aspects of the problem, we end up with a solution that almost reads like prose.
Appendix: Edge Cases & Errors
There's a lot that could go in this section, so I'll just point out a few things so this doesn't get tedious.
fromJust should be avoided, it isn't total and if we did call it on a Nothing value we'd get a big fat *** Exception: .... Consider changing the signature of the function to return a Maybe Double.This could happen if we changed our code to not produce an infinite list of guesses (
guesses x initial = take 500 $ iterate ...). What if precision was changed to 0 by another programmer (or future you) or floating point math throws you a curveball?Appendix: Two-liner
After writing your function in terms of higher order functions it's usually trivial to then turn it into a one-liner, just inline all of the function calls. In this particular case we've got a two-liner unless your terminal happens to be very wide.
refineSqrtGuess :: Double -> Double -> Maybe Double
refineSqrtGuess x initial = find (\guess -> abs (guess^2 - x) (guess + x / guess) / 2) initialI wouldn't use that version, I think it's a little opaque with all of that inlin
Code Snippets
refineSqrtGuess :: Double -> Double -> Double
refineSqrtGuess x = refine
where
refine guess
| guessIsGoodEnough = guess
| otherwise = refine improvedGuess
where
-- Test the precision of the guess
precision = 0.001
guessIsGoodEnough = abs (guess * guess - x) < precision * x
-- Improve our guessed square
improvedGuess = (guess + x / guess) / 2next :: Double -> Double -> Double
next x guess = (guess + x / guess) / 2guesses :: Double -> Double -> [Double]
guesses x initial = iterate (next x) initialacceptGuess :: Double -> Double -> Bool
acceptGuess x guess = abs (guess * guess - x) < precision * x
where precision = 0.001import Data.List (find)
import Data.Maybe (fromJust)
refineSqrtGuess :: Double -> Double -> Double
refineSqrtGuess x initial = fromJust $ find (acceptGuess x) (guesses x initial)Context
StackExchange Code Review Q#63743, answer score: 3
Revisions (0)
No revisions yet.