Skip to content

Instantly share code, notes, and snippets.

@Fiser12
Created February 7, 2024 20:48
Show Gist options
  • Save Fiser12/8b16091bf4b153e9e6cffcefcb3129a8 to your computer and use it in GitHub Desktop.
Save Fiser12/8b16091bf4b153e9e6cffcefcb3129a8 to your computer and use it in GitHub Desktop.
Swift y programación funcional: Contramap

Swift y programación funcional: Contramap

La programación funcional ha introducido varios conceptos poderosos que pueden mejorar significativamente la claridad, concisión y expresividad del código. Entre estos, los predicados y la función contramap ofrecen una manera elegante de construir lógica de validación y filtrado reutilizable y componible. En este artículo, exploraremos cómo estos conceptos pueden aplicarse en Swift para resolver problemas complejos de manera eficiente. Predicados y contramap: Una Introducción

Un Predicate<A> es una estructura que encapsula una condición que los elementos de tipo A deben cumplir. Esta condición se representa mediante un closure que toma un elemento de tipo A y devuelve un valor booleano, indicando si el elemento cumple o no con la condición especificada. La función contramap, por otro lado, permite transformar un Predicate<B> en un Predicate<A> , dada una función que convierte de A a B. Esto es particularmente útil cuando queremos aplicar un predicado existente a un tipo de datos diferente, sin necesidad de duplicar o reescribir la lógica del predicado.

 struct Predicate<A> {
    let evaluate: (A) -> Bool

    func contramap<B>(_ transform: @escaping (B) -> A) -> Predicate<B> {
        Predicate<B> { b in self.evaluate(transform(b)) }
    }
}

Ejemplo 1: Validación de Formularios

Imaginemos una aplicación con un formulario de registro de usuario. Necesitamos validar diferentes campos del formulario, como el nombre, el correo electrónico y la edad, para asegurarnos de que cumplen con ciertos criterios antes de permitir que el usuario envíe el formulario.

Definiendo Predicados de Validación

Creamos predicados específicos para validar cada campo del formulario:

  • isValidName: Verifica que el nombre tenga al menos 2 caracteres.
  • isValidEmail: Verifica que el correo electrónico siga un patrón específico.
  • isAdult: Verifica que el usuario sea mayor de edad.

Aplicación de Predicados

Usamos contramap para adaptar estos predicados a un formulario completo, permitiendo una validación compuesta que evalúa todos los campos a la vez.

struct Predicate<A> {
    let evaluate: (A) -> Bool

    func contramap<B>(_ transform: @escaping (B) -> A) -> Predicate<B> {
        Predicate<B> { b in evaluate(transform(b)) }
    }

    func and(_ other: Predicate<A>) -> Predicate<A> {
        Predicate<A> { a in self.evaluate(a) && other.evaluate(a) }
    }

    func or(_ other: Predicate<A>) -> Predicate<A> {
        Predicate<A> { a in self.evaluate(a) || other.evaluate(a) }
    }
}

struct UserForm {
    var name: String
    var email: String
    var age: Int
}

// Aquí definimos los predicates para cada una de las propiedades internas del UserForm
let isValidName = Predicate<String> { $0.count >= 2 }
let isValidEmail = Predicate<String> { email in
    let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
    return NSPredicate(format: "SELF MATCHES %@", emailRegex).evaluate(with: email)
}
let isAdult = Predicate<Int> { $0 >= 18 }

// Aquí finalmente definimos el Predicate para evaluar el UserForm
let isValidUserForm = Predicate<UserForm> { form in
    isValidName.evaluate(form.name) &&
    isValidEmail.evaluate(form.email) &&
    isAdult.evaluate(form.age)
}

// Otra opción sería hacerlo usando los and y or que hemos creado en el predicate
let isValidUserForm = isValidName.contramap { $0.name }
    .and(isValidEmail.contramap { $0.email })
    .and(isAdult.contramap { $0.age })

let form = UserForm(name: "Jane Doe", email: "[email protected]", age: 20)
let formIsValid = isValidUserForm.evaluate(form)

// Esto imprimirá true si el formulario es válido según nuestros criterios.
print(formIsValid)

Ejemplo 2: Sistema de Búsqueda y Filtrado para una Biblioteca de Libros

En una aplicación de biblioteca de libros, queremos permitir a los usuarios filtrar libros por autor, año de publicación y género.

Creación de Predicados Específicos

Definimos predicados para cada uno de estos criterios de filtrado.

Combinando Predicados para Búsqueda Avanzada

Los usuarios pueden combinar estos predicados para crear consultas de búsqueda específicas.

struct Book {
    var title: String
    var author: String
    var year: Int
    var genre: String
}

let byAuthor = { (author: String) in Predicate<Book> { $0.author == author } }
let byYear = { (year: Int) in Predicate<Book> { $0.year == year } }
let byGenre = { (genre: String) in Predicate<Book> { $0.genre == genre } }

let searchQuery = byAuthor("Isaac Asimov")
    .and(byYear(2000))
    .and(byGenre("Science Fiction"))

let library: [Book] = [
    Book(title: "Foundation", author: "Isaac Asimov", year: 1951, genre: "Science Fiction"),
    Book(title: "I, Robot", author: "Isaac Asimov", year: 1950, genre: "Science Fiction"),
    Book(title: "2001: A Space Odyssey", author: "Arthur C. Clarke", year: 1968, genre: "Science Fiction"),
    Book(title: "The Martian", author: "Andy Weir", year: 2000, genre: "Science Fiction")
]

let filteredBooks = library.filter(searchQuery.evaluate)

Conclusiones

El uso de Predicate<A> y contramap en Swift ilustra la potencia y flexibilidad de la programación funcional para construir soluciones elegantes a problemas complejos. Estos conceptos no solo mejoran la reusabilidad y la composibilidad del código, sino que también promueven una mayor claridad y expresividad, permitiendo a los desarrolladores implementar lógicas de filtrado y validación complejas de manera concisa y mantenible.

Los dos casos de uso mostrados son casos típicos que pueden llegar a tener bastante complejidad técnica es ciertos tipos de aplicaciones y son casos perfectos para transformar a programación funcional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment