Skip to content

Commit

Permalink
feat: add equivalence and predicate modules
Browse files Browse the repository at this point in the history
  • Loading branch information
furrycatherder committed Apr 20, 2024
1 parent 41c8d1f commit 56944d9
Show file tree
Hide file tree
Showing 8 changed files with 465 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml → .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
pull_request:

jobs:
test:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
70 changes: 66 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,80 @@

[![Package Version](https://img.shields.io/hexpm/v/ask)](https://hex.pm/packages/ask)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/ask/)
![CI](https://github.com/furrycatherder/gleam-ask/workflows/tests/badge.svg?branch=main)

`ask` is a Gleam library tailored for simplifying the management of equivalences
and predicates. Equivalences allow for the comparison of two values to
determine if they're equivalent, while predicates assess if a given value meets
specific conditions.

## Overview

`ask` provides developers with a set of functions to create, combine, and
transform equivalences and predicates. Whether you're performing basic value
comparisons or intricate logical operations, `ask` furnishes the necessary tools
to handle a variety of scenarios effectively.

## Installation

```sh
gleam add ask
```

## Usage

### Equivalences

```gleam
import ask
import ask/equivalence
// Defining custom equivalences
let eq1 = fn(x, y) -> Bool { x == y }
let eq2 = fn(x, y) -> Bool { x % 2 == y % 2 }
// Combining equivalences using logical operations
let combined_eq = equivalence.and(eq1, eq2)
pub fn main() {
// TODO: An example of the project in use
}
// Using equivalence to compare values
let result = combined_eq(4, 8) // Returns True
```

Equivalences are expected to follow these friendly rules:

- **Reflexivity**: Every value is equivalent to itself. It's like saying, "Hey,
you're always equal to yourself!"
- **Symmetry**: If one value is equivalent to another, then it's a two-way
street! If X is like Y, then Y is like X. It's all about fairness!
- **Transitivity**: Imagine a chain of equivalence! If X is equivalent to Y,
and Y is equivalent to Z, then it's like saying X is also buddies with Z.
Friendship circles all around!

These rules help keep our equivalences reliable and predictable, making sure
they play nice with each other.

### Predicates

```gleam
import ask/predicate
// Defining custom predicates
let is_positive = fn(x) -> Bool { x > 0 }
let is_even = fn(x) -> Bool { x % 2 == 0 }
// Combining predicates using logical operations
let combined_pred = predicate.and(is_positive, is_even)
// Using predicate to evaluate values
let result = combined_pred(6) // Returns True
```

## Conclusion

`ask` equips Gleam developers with a robust toolkit for managing equivalences and
predicates efficiently. Whether you're implementing algorithms, data validation
systems, or decision-making processes, `ask` facilitates streamlined code
development, allowing you to focus on problem-solving.

Further documentation can be found at <https://hexdocs.pm/ask>.

## Development
Expand Down
15 changes: 4 additions & 11 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
name = "ask"
version = "1.0.0"
version = "0.1.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
#
# description = ""
# licences = ["Apache-2.0"]
# repository = { type = "github", user = "username", repo = "project" }
# links = [{ title = "Website", href = "https://gleam.run" }]
#
# For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/.
description = "Utilities for composing predicates and equivalence relations"
licences = ["Apache-2.0"]
repository = { type = "github", user = "furrycatherder", repo = "gleam-ask" }

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
Expand Down
11 changes: 11 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This file was generated by Gleam
# You typically do not need to edit this file

packages = [
{ name = "gleam_stdlib", version = "0.37.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5398BD6C2ABA17338F676F42F404B9B7BABE1C8DC7380031ACB05BBE1BCF3742" },
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
]

[requirements]
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
5 changes: 0 additions & 5 deletions src/ask.gleam

This file was deleted.

69 changes: 69 additions & 0 deletions src/ask/equivalence.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
pub type Equivalence(a) =
fn(a, a) -> Bool

/// Create a new equivalence that always returns `True`.
///
/// This is sometimes called the trivial or universal equivalence.
///
pub fn trivial() -> Equivalence(a) {
fn(_, _) -> Bool { True }
}

/// Combine two equivalences into a new equivalence that returns `True` if both
/// equivalences return `True`.
///
pub fn and(first: Equivalence(a), second: Equivalence(a)) -> Equivalence(a) {
fn(value: a, other: a) -> Bool { first(value, other) && second(value, other) }
}

/// Combine two equivalences into a new equivalence that returns `True` if either
/// equivalence returns `True`.
///
pub fn or(first: Equivalence(a), second: Equivalence(a)) -> Equivalence(a) {
fn(value: a, other: a) -> Bool { first(value, other) || second(value, other) }
}

/// Negate an equivalence to create a new equivalence that returns `True` if the
/// original equivalence returns `False`.
///
pub fn not(eq: Equivalence(a)) -> Equivalence(a) {
fn(value: a, other: a) -> Bool { !eq(value, other) }
}

/// Create a new equivalence for a list of values based on the given equivalence.
///
pub fn list(eq: Equivalence(a)) -> Equivalence(List(a)) {
fn(values: List(a), others: List(a)) -> Bool {
case values, others {
[], [] -> True
[value, ..values], [other, ..others] -> {
eq(value, other) && list(eq)(values, others)
}
_, _ -> False
}
}
}

/// Create a new equivalence for a pair of values based on the given equivalences.
///
pub fn pair(
first: Equivalence(a),
second: Equivalence(b),
) -> Equivalence(#(a, b)) {
fn(value: #(a, b), other: #(a, b)) -> Bool {
case value, other {
#(value1, value2), #(other1, other2) -> {
first(value1, other1) && second(value2, other2)
}
}
}
}

/// Map the input of an equivalence to create a new equivalence.
///
pub fn map_input(
over eq: Equivalence(a),
with fun: fn(b) -> a,
) -> Equivalence(b) {
fn(value: b, other: b) -> Bool { eq(fun(value), fun(other)) }
}
82 changes: 82 additions & 0 deletions src/ask/predicate.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import gleam/list

pub type Predicate(a) =
fn(a) -> Bool

/// Create a new predicate that always returns `True`.
///
pub fn always() -> Predicate(a) {
fn(_) -> Bool { True }
}

/// Create a new predicate that always returns `False`.
///
pub fn never() -> Predicate(a) {
fn(_) -> Bool { False }
}

/// Combine two predicates together into a new predicate that returns `True` if
/// both predicates return `True`.
///
pub fn and(first: Predicate(a), second: Predicate(a)) -> Predicate(a) {
fn(value: a) -> Bool { first(value) && second(value) }
}

/// Combine two predicates together into a new predicate that returns `True` if
/// either predicate returns `True`.
///
pub fn or(first: Predicate(a), second: Predicate(a)) -> Predicate(a) {
fn(value: a) -> Bool { first(value) || second(value) }
}

/// Negate a predicate.
///
pub fn not(p: Predicate(a)) -> Predicate(a) {
fn(value: a) -> Bool { !p(value) }
}

/// Combine a list of predicates together into a new predicate that returns `True`
/// if all predicates return `True`.
///
pub fn every(ps: List(Predicate(a))) -> Predicate(a) {
case ps {
[] -> fn(_) -> Bool { True }
[p, ..ps] -> fn(value: a) -> Bool { p(value) && every(ps)(value) }
}
}

/// Combine a list of predicates together into a new predicate that returns `True`
/// if any predicate returns `True`.
///
pub fn some(ps: List(Predicate(a))) -> Predicate(a) {
case ps {
[] -> fn(_) -> Bool { False }
[p, ..ps] -> fn(value: a) -> Bool { p(value) || some(ps)(value) }
}
}

/// Create a new predicate that returns `True` if it returns `True` for every
/// element in a list.
///
pub fn all(p: Predicate(a)) -> Predicate(List(a)) {
fn(a: List(a)) -> Bool {
a
|> list.all(p)
}
}

/// Create a new predicate that returns `True` if it returns `True` for any
/// element in a list.
///
pub fn any(p: Predicate(a)) -> Predicate(List(a)) {
fn(a: List(a)) -> Bool {
a
|> list.any(p)
}
}

/// Map the input of a predicate to create a new predicate.
///
pub fn map_input(over p: Predicate(a), with fun: fn(b) -> a) -> Predicate(b) {
fn(b: b) -> Bool { p(fun(b)) }
}
Loading

0 comments on commit 56944d9

Please sign in to comment.