이전 1.1 The Elements of Programming
이전 1.2 Procedures and the Processes They Generate
func cube(x: Int) -> Int {
return x * x * x
}
func cube(x: Double) -> Double {
return x * x * x
}
이 프로시저는 정수와 실수 모두 세제곱을 계산하도록 구현한 것이다. 워낙 간단한 계산이기 때문에 이렇게 선언한 것을 사용하지 않고, 3 * 3 * 3
또는 x * x * x
처럼 기본 연산으로 풀어서 쓸 수도 있다. 같은 세제곱을 계산하더라도 언어로 세제곱이라는 계산(또는 연산)을 나타내는 단어를 선언하는 게 좋다. 프로그래밍 언어는 되풀이되는 계산을 간추려서 대표하는 의미있는 이름으로 붙이는 게 중요하다. 프로시저를 선언하는 것도 그 중에 한 가지 방법이다.
단순한 계산을 하는 경우만 봐도 프로시저가 매개변수로 숫자만 받을 수 있다면, 새로운 표현으로 추상화해서 정의하는 능력은 제한될 수 밖에 없다. 이런저런 프로그램에서 여러 프로시저를 사용하는 방식이 비슷한 경우를 종종 볼 수 있다. 그래서 반복하는 계산 방식을 간추리려면, 숫자는 물론 다른 프로시저를 인자로 받거나 결과로 되돌려 주는 프로시저를 만들 수 있어야 한다. 이처럼 프로시저를 데이터처럼 사용하는 프로시저를 고차 프로시저( 또는 고차 함수) high-order function 라고 한다.
다음 두 함수는 각각 a부터 b까지 정수의 합을 구하는 프로시저와 정해진 넓이 속 정수를 세제곱 하는 프로시저다.
func sum_intergers(a: Int, b: Int) -> Int {
if a > b {
return 0
}
else {
return a + sum_intergers(a + 1, b)
}
}
func sum_cubes(a: Int, b: Int) -> Int {
if a > b {
return 0
}
else {
return a + sum_cubes(a + 1, b)
}
}
세 번째 프로시저는 다음 수열에서 모든 마디를 더하는 프로시저다.
func pi_sum(a: Double, b: Double) -> Double {
if a > b {
return 0
}
else {
return 1.0 / (a * (a + 2)) + pi_sum(a + 4, b)
}
}
여기까지 프로시저를 읽고나면 세 프로시자가 같은 계산 방법을 사용하고 있다는 것을 눈치챌 수 있다. 프로시저의 이름, 마디 값 a를 받아서 계산하는 함수, a 다음 값을 계산하는 함수만 다를 뿐이다. 따라서 다음 프로시저 틀에서 name, term, next 빈 칸만 채우면 세 가지 모든 프로시저를 표현할 수 있다.
func name(a: Double, b: Double) -> Double {
if a > b {
return 0
}
else {
return term(a) + name(next(a), b)
}
}
이렇게 여러 프로시저에 같은 계산 방법을 적용할 수 있다는 것은 이를 간추릴 때 사용할 수 있는 프로시저가 나올 수 있다는 의미다. 수학자들은 아주 오래 전부터 수열 마디를 더하는 일을 같은 계산 방법으로 표현할 수 있다는 것을 알았다. 그렇게 시그마 표현식
을 만들었다.
name, term, next 빈 자리를 인자 이름오 바꿔서 표현해보면 다음과 같다.
func sum(_ term: (Double) -> Double, _ a: Double, _ next: (Double) -> Double, _ b: Double) -> Double {
if a > b {
return 0
}
else {
return term(a) + sum(term, next(a), next, b)
}
}
위에 선언한 sum 프로시저는 수의 범위를 나타내는 a, b 뿐만 아니라 프로시저를 인자로 받는 term과 next가 있다는 것을 주의해야 한다. sum을 사용하는 방식은 보통 프로시저와 동일하다.
인자에 1을 더하는 프로시저 inc()와 sum_cubes를 정의하면 다음과 같다.
func inc(_ n: Double) -> Double {
return n + 1
}
func sum_cubes(_ a: Double, _ b: Double) -> Double {
return sum(cube, a, inc, b)
}
sum_cubes(1, 10)
또 다른 예제를 살펴보자. 받은 대로 그대로 돌려주는 함수로 identity가 정의되어 있다면, 프로시저 sum으로 sum_integers를 만들어보자.
func identity(_ x: Double) -> Double {
return x
}
func sum_integers(_ a: Double, _ b: Double) -> Double {
return sum(identity, a, inc, b)
}
sum_integers(1, 10)
위에서 구현했던 pi_sum() 프로시저를 다시 한 번 선언해보자.
func pi_sum(_ a: Double, _ b: Double) -> Double {
func pi_term(_ x: Double) -> Double {
return 1.0 / (x * (x + 2))
}
func pi_next(_ x: Double) -> Double {
return x + 4
}
return sum(pi_term, a, pi_next, b)
}
8 * pi_sum(1, 1000)
훨씬 복잡한 아주 작은 값 dx가 있을 때 함수 f를 a부터 사이에서 적분한 값은 다음과 같다.
func integral(_ f : (Double) -> Double, _ a: Double, _ b: Double, _ dx: Double) -> Double {
func add_dx(_ x: Double) -> Double {
return x + dx
}
return sum(f, a + dx / 2, add_dx, b) * dx
}
integral(cube, 0, 1, 0.01)
integral(cube, 0, 1, 0.005)
func simpsons_rule_integral(f: (Double) -> Double, a: Double, b: Double, n: Double) -> Double {
func helper(h: Double) -> Double {
func y(_ k: Double) -> Double {
return f((k*h) + a)
}
func term(k: Double) -> Double {
if k == 0 || k == n {
return y(k)
}
else if k.remainder(dividingBy: 2) == 0 {
return 2 * y(k)
}
else {
return 4 * y(k)
}
}
return sum(term, 0, inc, n)
}
return helper(h: (b-a) / n )
}
simpsons_rule_integral(f: cube, a: 0, b: 1, n: 100)
simpsons_rule_integral(f: cube, a: 0, b: 1, n: 1000)
func sum(term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double) -> Double {
func iter(a: Double, result: Double) -> Double {
if a > b {
return result
}
else {
return iter(a: next(a), result: result+term(a))
}
}
return iter(a: a, result: 0)
}
func product_r(term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double) -> Double {
if a > b {
return 1
}
else {
return term(a) * product_r(term: term, a: next(a), next: next, b: b)
}
}
func product_i(term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double) -> Double {
func iter(_ a: Double, _ result : Double) -> Double {
if a > b {
return result
}
else {
return iter(next(a), term(a) * result)
}
}
return iter(a, 1)
}
product_r(term: identity, a:1, next:inc, b:5)
product_i(term: identity, a:1, next:inc, b:5)
재귀 방식
func accumulate_r(combiner: (Double, Double) -> Double, null_value: Double, term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double) -> Double
{
if a > b {
return null_value
}
else {
return combiner(term(a), accumulate_r(combiner: combiner, null_value: null_value,
term: term, a: next(a), next: next, b: b))
}
}
반복 방식
func accumulate_i(combiner: (Double, Double) -> Double, null_value: Double, term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double) -> Double
{
func iter(_ a: Double, _ result: Double) -> Double {
if a > b {
return result
}
else {
return iter(next(a), combiner(term(a), result))
}
}
return iter(a, null_value)
}
func sum_i(term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double) -> Double
{
func plus(_ x: Double, _ y: Double) -> Double {
return x + y
}
return accumulate_i(combiner: plus, null_value: 0, term: term, a: a, next: next, b: b)
}
func product_r(term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double) -> Double {
func times(_ x: Double, _ y: Double) -> Double {
return x * y
}
return accumulate_i(combiner: times, null_value: 1, term: term, a: a, next: next, b: b)
}
func filtered_accumulate(combiner: (Double, Double) -> Double, null_value: Double, term: (Double) -> Double, a: Double, next: (Double) -> Double, b: Double, filter: (Double) -> Bool) -> Double {
if a > b {
return null_value
}
else if filter(a){
return combiner(term(a), filtered_accumulate(combiner: combiner, null_value: null_value, term: term, a: next(a), next: next, b: b, filter: filter))
}
else {
return filtered_accumulate(combiner: combiner, null_value: null_value, term: term, a: next(a), next: next, b: b, filter: filter)
}
}
1.3.1절에서 sum()을 만들 때처럼 pi_term() 이나 pi_next() 같은 프로시저를 인자로 전달하기 위해서 매번 선언하는 것은 귀찮은 일이다. 그래서 항상 프로시저를 선언하는 대신 '인자에 4를 더하고 돌려주는 프로시저' 또는 '인자와 인자에 2를 더한 값을 곱해서 역수를 계산하는 프로시저'를 바로 선언하는 게 편리하다. 클로저라는 형식이 이런 문제를 푸는 데 도움이 된다.
스위프트에서 클로저는 다음과 같이 선언한다. 그렇지만 클로저를 어딘가에 담지 않는다면 이 상태 그대로 사용하지는 못한다.
{ x in x + 4 }
{ x in 1 / (x * (x + 2)) }
클로저를 다음과 같이 상수와 함께 선언할 수 있다.
let k = { x in x + 4 }
let z = { x in 1 / (x * (x + 2)) }
또는 integral() 같은 다른 함수를 선언할 때 보조 함수를 선언하지 않고 직접 선언할 수 있다.
func pi_sum(a: Double, b: Double) -> Double {
return sum({x in 1 / (x * (x + 2))},
a,
{x in x + 4},
b
)
}
func integral(_ f : (Double) -> Double, _ a: Double, _ b: Double, _ dx: Double) -> Double {
return sum(f, a + dx / 2, { x in x + dx }, b) * dx
}
이처럼 함수를 선언하는 것과 큰 차이는 없지만, 이름을 붙이지 않고 매개 변수 자리에 바로 넣을 수 있다.
func plus4(x: Double) -> Double { return x + 4 }
로 선언한 함수가 있다고 가정하자.
이 프로시저는 let plus4 = { x in x + 4 }
처럼 클로저를 상수에 선언한 것과 같다.
클로저 문법 내용을 풀어서 설명하면 다음과 같다.
함수 대신 값을 처리하는 연산자가 있는 표현식처럼, 연산자 대신 클로저를 선언할 수도 있다.
let k = {(x, y, z) in x + y + square(z)}(1,2,3)
func f(x: Double, y: Double) -> Double {
func f_helper(_ a: Double, _ b: Double) -> Double {
return x * square(a) + y*b + a*b
}
return f_helper(1 + x*y, 1-y)
}
func f_closure(x: Double, y: Double) -> Double {
return { a, b in x * square(a) + y*b + a*b }(1 + x*y, 1-y)
}
func f_local(x: Double, y: Double) -> Double {
let a = 1 + x*y
let b = 1 - y
return x * square(a) + y*b + a*b
}
func f(g: (Double) -> Double) -> Double {
return g(2)
}
f(g: square)
> 4
f(g: { z in z * (z + 1)})
> 6
위에 코드를 실행이 되지만, f(g: f)
형식은 동작하지 않는다. 스위프트는 f() 타입이 g() 타입과 다르기 때문에 컴파일 에러가 발생한다.
1.1.4절에서는 계산하는 방법에만 집중해서 프로시저로 요약하는 방법을 다루었다. 1.3.1절에서는 만든 integral 적분 계산식처럼 차수 높은 프로시저를 만들어서 쓰임새를 늘릴 수 있었다.
이분법 half-interval method는 어떤 너비를 반으로 나누는 방법으로, 연속 함수 f에서 f(x)=0을 만족하는 근을 찾는 방법이다. 계산을 반복할 때마다 값을 찾아야 할 넓이가 절반으로 줄어든다. L이 처음 넓이, T가 허용오차 error tolerance일 때 계산 단계는 𝜣(log(L / T)) 정도가 된다.
func search(_ f: (Double)-> Double, _ neg_point: Double, _ pos_point: Double) -> Double {
let midpoint = average(neg_point, pos_point)
if close_enough(neg_point, pos_point) {
return midpoint
}
let test_value = f(midpoint)
if positive(test_value) {
return search(f, neg_point, midpoint)
}
else if negative(test_value) {
return search(f, midpoint, pos_point)
}
return midpoint
}
func close_enough(_ x: Double, _ y: Double) -> Bool {
return abs(x - y) < 0.001
}
func average(_ x: Double, _ y: Double) -> Double {
return (x + y) / 2.0
}
func positive(_ x: Double) -> Bool {
return x > 0
}
func negative(_ x: Double) -> Bool {
return x < 0
}
func half_interval_method(_ f:(Double)->Double, _ a: Double, _ b: Double) -> Double? {
let a_value = f(a)
let b_value = f(b)
if negative(a_value) && positive(b_value) {
return search(f, a, b)
}
else if negative(b_value) && positive(a_value) {
return search(f, b, a)
}
print("Values are not of opposite sign \(a), \(b)")
return nil
}
print(half_interval_method(sin, 2.0, 4.0))
print(half_interval_method({ (x:Double) in (x*x*x) - (2*x) - 3 }, 1.0, 2.0))
특정한 수 x에 대해 f(x) = x가 참이면 x를 f의 고정점 fixed point라고 한다. 함수 f에 임시값을 주고 반복해서 f를 계산하다가 그 값이 크게 바뀌지 않으면 f의 고정점을 찾을 수 있다.
let tolerance = 0.0001
func fixed_point(_ f:(Double)->Double, _ first_guess:Double) -> Double {
func close_enough(_ x: Double, _ y: Double) -> Bool {
return abs(x - y) < tolerance
}
func try_with(guess: Double) -> Double {
let next = f(guess)
if close_enough(guess, next) {
return next
}
else {
return try_with(guess: next)
}
}
return try_with(guess: first_guess)
}
fixed_point(cos, 1.0)
fixed_point( { y in sin(y) + cos(y)} , 1.0)
1.1.7절에서 제곱근을 찾던 방법을 고정점 찾는 방식으로 표현하면 y = x / y
가 되는 고정점을 찾으면 된다.
func sqrt_y(x: Double) -> Double {
return fixed_point({ y in x / y}, 1.0)
}
하지만 이렇게 구현하면 제급근에 가까워지지 않고, 고정점 근처를 왔다갔다 반복할 뿐이다.
y 다음 값이 x/y 대신 (y + x/y)/2
가 되도록 y와 x/y 평균값을 구하면 된다.
func sqrt(x: Double) -> Double {
return fixed_point({ y in average(y, (x / y))}, 1.0)
}
fixed_point( { x in 1 + (1 / x)}, 1.0 )
출력하는 버전과 평균값으로 찾아가는 버전
let tolerance = 0.0001
func fixed_point(_ f:(Double)->Double, _ first_guess:Double) -> Double {
func close_enough(_ x: Double, _ y: Double) -> Bool {
return abs(x - y) < tolerance
}
func try_with(guess: Double) -> Double {
print("guess = \(guess)")
let next = f(guess)
if close_enough(guess, next) {
return next
}
else {
return try_with(guess: next)
}
}
return try_with(guess: first_guess)
}
func fixed_point_average(_ f:(Double)->Double, _ first_guess:Double) -> Double {
func close_enough(_ x: Double, _ y: Double) -> Bool {
return abs(x - y) < tolerance
}
func try_with(guess: Double) -> Double {
print("guess = \(guess)")
let next = (guess + f(guess)) / 2
if close_enough(guess, next) {
return next
}
else {
return try_with(guess: next)
}
}
return try_with(guess: first_guess)
}
반복 프로시저
func continue_frac_r(n: (Double)->Double, d: (Double)->Double, k: Double) -> Double {
func fraction(i: Double) -> Double {
if i > k {
return 0
}
return n(i) / (d(i) + fraction(i: i+1))
}
return fraction(i: 1)
}
continue_frac_r(n: { i in 1.0 }, d: { i in 1.0 }, k: 20)
절차 프로시저
func continue_frac_i(n: (Double)->Double, d: (Double)->Double, k: Double) -> Double {
func fraction(_ i: Double, _ current: Double) -> Double {
if i == 0 {
return current
}
return fraction(i - 1, n(i) / (d(i) + current))
}
return fraction(k, 0)
}
continue_frac_i(n: { i in 1.0 }, d: { i in 1.0 }, k: 20)
print(2 + continue_frac_r(n: { i in 1.0 },
d: { i in ((i + 1).remainder(dividingBy: 3) < 1) ? 2 * (i+1) / 3 : 1.0},
k: 20))
func tan_cf(x: Double, k: Double) -> Double {
return continue_frac_i(n: { i in i == 1 ? x : -x * x },
d: { i in 2 * i - 1}, k: k)
}
print(tan_cf(x: 3.14 / 2.0, k: 20))
프로시저를 매개 변수로 받아 사용하면 언어 표현력이 좋아지는 것처럼, 프로시저를 결과 값으로 돌려줄 수 있으면 표현력을 한층 더 끌어 올릴 수 있다.
앞서 1.3.3절에서 고정값을 찾는 문제를 되짚어보자. 이 때 어림값을 평균내서 잦아드는 방법을 사용했었다. 평균 잦아들기 방식을 표현하면 다음 예제 코드와 같다.
func average_damp(f: @escaping (Double)->Double) -> (Double) -> Double {
return { x in average(x, f(x)) }
}
average_damp(f: square)(10)
average_damp() 프로시저는 f라는 함수 f를 인자로 전달받아서, x와 f(x)에 평균값을 구하는 클로저로 만든 프로시저를 내놓는다. 이 프로시저를 활용해서 제곱근 프로시저를 다시 작성하면 다음과 같다.
func sqrt(x: Double) -> Double {
fixed_point(average_damp(f: { y in x / y}), 1.0)
}
sqrt(x: 5)
1.1.7절에서 만든 제곱근 프로시저와 비교해보면, 같은 계산 과정을 나타내지만 지금처럼 더 간추릴수록 계산 방법이 더 명확하게 드러난다는 것이다. 계산 방법 자체는 하나라도 이를 프로시저로 나타내는 방법은 여러 가지가 있고, 경험이 많은 좋은 프로그래머라면 그 중에서도 계산 방법을 명확하게 표현할 수 있는 프로시저를 작성하려고 한다. 제곱근을 응용해보기 위해서, x의 세제곱근은 함수 y → x/y2 고정점이라는 사실을 바탕으로 세제곱근 프로시저를 작성하면 다음과 같다.
func cube_root(x: Double) -> Double {
fixed_point(average_damp(f: { y in x / square(y)}), 1.0)
}
cube_root(x: 27)
x → g(x)가 미분되는 함수라면, g(x)=0 방정신 근은 다음과 같은 f(x)의 정점과 같다. f(x) = x - g(x) / Dg(x)
미분
은 평균 잦아들기와 마찬가지로 어떤 함수를 다른 함수로 바꾸는 것이다. x → x3 에 대한 미분은 함수 x → 3x2 이다.
g가 함수이고 dx가 아주 작은 값이면, x에서 g를 미분한 Dg는 다음과 같다.
[미분 수식]
let dx = 0.00001
func deriv(g: @escaping (Double) -> Double) -> (Double) -> Double {
return { x in (g(x + dx) - g(x)) / dx }
}
deriv(g: cube)(5)
deriv() 미분 프로시저를 선언하고 x → x3을 5에서 미분한 값을 계산할 수 있다. deriv를 기반으로 뉴튼 방법을 고정점 찾는 방법으로 나타내보자.
func newton_transform(g: @escaping (Double)->Double) -> (Double) -> Double {
return { x in x - g(x) / deriv(g: g)(x)}
}
func newtons_method(g: @escaping (Double)->Double, guess: Double) -> Double {
return fixed_point(newton_transform(g: g), guess)
}
func sqrt_newton(x: Double) -> Double {
return newtons_method(g: { y in square(y) - x}, guess: 1.0)
}
sqrt_newton(x: 9)
고정점 찾기나 뉴튼 방법처럼 일반적인 수학 계산 방법을 제곱근 찾기
문제 해결을 위해서 어떻게 활용하는 지 살펴봤다. 뉴튼 방법 내부도 고정점 찾기로 해결할 수 있으니까 결국 고정점 찾기를 응용해서 제곱근을 찾을 수 있었다. 이 과정을 요약해보자.
func fixed_point_of_transform(g: @escaping (Double)->Double, transform: (@escaping (Double)->Double) -> (Double)->Double, guess: Double) -> Double {
fixed_point(transform(g), guess)
}
func sqrt_transform(x: Double) -> Double {
return fixed_point_of_transform(g: { y in x / y }, transform: average_damp, guess: 1.0)
}
sqrt_transform(x: 25)
이렇게 복잡한 계산을 요약하기 위해서 묶음 프로시저 compound procedure가 얼마나 중요한 지 알 수 있다. 이렇게 차수 높은 프로시저가 프로시저를 받거나 되돌려주는 방식으로 일반적인 계산 방법을 구현할 수 있다.
프로그래머는 프로그래밍 과정에서 작성한 절차를 놓고, 그 중에서 간추릴 게 무엇인지 찾아내고 더 높은 표현 수단으로 만들기 위해 노력해야 한다. 더 이상 요약하지 못할 때까지 모든 프로그램을 요약하라는 게 아니다. 어느 정도까지 요약해야 다른 문제를 풀 때 다시 쓸 수 있도록 일반 표현 수단이 되는지도 신경써야 한다. 차수 높은 프로시저가 프로시저를 값(데이터)로 사용하는 것이 그래서 중요하다.
프로그래밍 언어에서는 계산 과정에서 어떤 기능을 우선적으로 어떻게 다루어야 하는지 제약을 걸어둔다. 가장 제약이 적은 것을 일등급 first-class에 속한다고 부른다.
일등급이 누리는 특권은 다음과 같다.
- 변수 이름을 붙여서 변수의 값이 될 수 있다. (referred to name)
- 프로시저 인자로 사용할 수 있다. (passed as arguments)
- 프로시저의 결과로 만들어질 수 있다. (returned as the result)
- 데이터 구조 속에 집어 넣을 수 있다. (included in data structures)
스위프트도 함수(프로시저)를 일등급으로 다룰 수 있다.
func cubic(a: Double, b: Double, c: Double) -> (Double) -> Double {
return { x in cube(x: x) + a * square(x) + b * x + c }
}
func double(f: @escaping (Double) -> Double) -> (Double) -> Double {
return { x in f(f(x)) }
}
func compose(f: @escaping (Double) -> Double, g: @escaping (Double) -> Double) -> (Double) -> Double {
return { x in f(g(x))}
}
func compose(f: @escaping (Double) -> Double, g: @escaping (Double) -> Double) -> (Double) -> Double {
return { x in f(g(x))}
}
func repeated(f: @escaping (Double) -> Double, n: Double) -> (Double) -> Double {
if n == 0 {
return { x in x }
}
return compose(f: f, g: repeated(f: f, n: n-1))
}
func compose(f: @escaping (@escaping (Double) -> Double) -> (Double) -> Double, g: @escaping (@escaping (Double) -> Double) -> (Double) -> Double) -> (@escaping (Double) -> Double) -> (Double) -> Double {
return { x in f(g(x))}
}
func repeated(f: @escaping (@escaping (Double) -> Double) -> (Double) -> Double, n: Double) -> (@escaping (Double) -> Double) -> (Double) -> Double {
if n == 0 {
return { x in x }
}
return compose(f: f, g: repeated(f: f, n: n-1))
}
let dx = 0.00001
func smooth(f: @escaping (Double) -> Double) -> (Double) -> Double {
return { x in (f(x - dx) + f(x) + f(x + dx)) / 3 }
}
func n_fold_smooth(f: @escaping (Double) -> Double, n: Double) -> (Double) -> Double {
return repeated(f: smooth, n: n)(f)
}
func fast_expt_iter(_ a: Double, _ b: Double, _ n: Double) -> Double{
if n == 0 {
return a
}
else if n.remainder(dividingBy: 2) == 0 {
return fast_expt_iter(a, b*b, n/2)
}
else {
return fast_expt_iter(a*b, b, n-1)
}
}
func fast_expt(b: Double, n: Double) -> Double {
return fast_expt_iter(1, b, n)
}
func nth_root(n: Double, x: Double) -> Double {
return fixed_point(repeated(f: average_damp,
n: floor(log(n)))({ y in x / fast_expt(b: y, n: n-1)}),
1.0)
}
nth_root(n: 3, x: 2)
func good_enough(_ guess: Double, _ x: Double) -> Bool {
return abs(square(guess) - x) < 0.0001
}
func improve(_ guess: Double, _ x: Double) -> Double {
return average(guess, x / guess)
}
func iterative_improve(good_enough: @escaping (Double) -> Bool,
improve: @escaping (Double) -> Double) -> (Double) -> Double {
func iterate(guess: Double) -> Double {
if good_enough(guess) {
return guess
}
return iterate(guess: improve(guess))
}
return iterate
}
func sqrt_improve(x: Double) -> (Double) -> Double {
return iterative_improve(good_enough: { y in good_enough(y, x)}, improve: { y in improve(y, x)})
}
sqrt_improve(x: 49)(1.0)