Fábrica de funciones trigonométricas
Estoy tratando de aprender algo de trigonometría. Una de las cosas que aprendí es que
$$ cos(90^\circ)=cos(\pi/2)=0 $$
La mayor parte de los ejercicios del libro que estoy usando usan grados en vez de radianes, pero las funciones trigonométricas de R usan radianes:
cos(pi/2) # casi 0
## [1] 6.123234e-17
cos(90) # nada que ver!
## [1] -0.4480736
¿Cómo podemos adaptar las funciones de R para que acepten grados en vez de radianes? Lo primero que encontré fue esta respuesta en stackoverflow.
coss <- function(a){
a=cos(a*pi/180)
return(a)
}
sinn <- function(a){
a= sin(a*pi/180)
return(a)
}
tann <- function(a){
a= tan(a*pi/180)
return(a)
}
library(testthat)
test_that("Solución de SO", {
# Coseno de 90° es 0
expect_equal(coss(90), 0, tolerance=1e-1)
# Seno de 90° es 1
expect_equal(sinn(90), 1, tolerance=1e-1)
# Tangente de 45° es 1
expect_equal(tann(45), 1, tolerance=1e-1)
})
## Test passed 🎉
Lo que hace esta solución es crear un wrapper
en torno a las funciones nativas de R (sin
, cos
y tan
). La única diferencia entre (tann
, sinn
y coss
) es que cada una llama a su función correspondiente en base R.
Para reducir la repetición entre coss
, sinn
y tann
podemos reescribirlas usando un idioma funcional que se llama fábrica de funciones.
Como R es un lenguaje funcional, las funciones son objetos. Se pueden asignar a variables, pasarse como argumentos a funciones y devolverse de funciones.
Para implemetar esta solución, creamos una función accept_degrees
que toma como argumento otra función que acepta radianes (sin
, cos
o tan
) y devuelve una función que acepta grados como argumento.
Después la usamos para crear las funciones que necesito: (sin2
, cos2
o tan2
).
accept_degrees <- function(f) {
function(angle) {
f(angle * pi / 180)
}
}
sin2 <- accept_degrees(sin)
cos2 <- accept_degrees(cos)
tan2 <- accept_degrees(tan)
Para entender como funciona este código, es importante tener en mente un concepto importante, que es el tipado de una función. Esto es el tipo
o clase
de los argumentos que recibe y de los resultados que devuelve.
library(testthat)
test_that("Solucion 1", {
# Coseno de 90° es 0
expect_equal(cos2(90), 0, tolerance=1e-1)
# Coseno de 0° es 1
expect_equal(cos2(0), 1, tolerance=1e-1)
# Seno de 90° es 1
expect_equal(sin2(90), 1, tolerance=1e-1)
# Seno de 0° es 0
expect_equal(sin2(0), 0, tolerance=1e-1)
# Tangente de 0° es 0
expect_equal(tan2(0), 0, tolerance=1e-1)
# Tangente de 45° es 1
expect_equal(tan2(45), 1, tolerance=1e-1)
})
## Test passed 🥇
Otra ventaja que tiene esto es que permite cambiar rápidamente la implementación del código que convierte los ángulos de grados a radianes. Primero factoreamos una función (deg_to_rad
) para hacer la conversión:
deg_to_rad <- function(angle) {
angle * pi / 180
}
accept_degrees <- function(f) {
function(angle) {
f(deg_to_rad(angle))
}
}
sin2 <- accept_degrees(sin)
cos2 <- accept_degrees(cos)
tan2 <- accept_degrees(tan)
Si encontramos otra implementación de deg_to_rad_
que nos gusta más, podemos usarla fácilmente:
library(units)
## udunits database from /Library/Frameworks/R.framework/Versions/4.1/Resources/library/units/share/udunits/udunits2.xml
deg_to_rad <- function(angle) {
angle <- set_units(angle, degree)
units(angle) <- make_units(radian)
return(angle)
}
accept_degrees <- function(f) {
function(angle) {
f(deg_to_rad(angle))
}
}
sin2 <- accept_degrees(sin)
cos2 <- accept_degrees(cos)
tan2 <- accept_degrees(tan)
library(testthat)
test_that("Solución final", {
# Coseno de 90° es 0
expect_equal(cos2(90), set_units(0, 1), tolerance=1e-1)
# Coseno de 0° es 1
expect_equal(cos2(0), set_units(1, 1), tolerance=1e-1)
# Seno de 90° es 1
expect_equal(sin2(90), set_units(1, 1), tolerance=1e-1)
# Seno de 0° es 0
expect_equal(sin2(0), set_units(0, 1), tolerance=1e-1)
# Tangente de 0° es 0
expect_equal(tan2(0), set_units(0, 1), tolerance=1e-1)
# Tangente de 45° es 1
expect_equal(tan2(45), set_units(1, 1), tolerance=1e-1)
})
## Test passed 🥇