All Projects → onihusube → harmony

onihusube / harmony

Licence: BSL-1.0 license
C++ Monadologie

Programming Languages

C++
36643 projects - #6 most used programming language
Meson
512 projects

Projects that are alternatives of or similar to harmony

cefal
(Concepts-enabled) Functional Abstraction Layer for C++
Stars: ✭ 52 (+100%)
Mutual labels:  monad, cpp20
FARGonautica
No description or website provided.
Stars: ✭ 85 (+226.92%)
Mutual labels:  concept
apropos
Fast strong typed 'Either' data structure for typescript and flow
Stars: ✭ 20 (-23.08%)
Mutual labels:  monad
agrpc
Async GRPC with C++20 coroutine support
Stars: ✭ 53 (+103.85%)
Mutual labels:  cpp20
crynn
3D Game Engine Created In C++ & OpenGL
Stars: ✭ 45 (+73.08%)
Mutual labels:  cpp20
hostbase
A Ruby GUI based on advanced rogue AP attack using the WPS
Stars: ✭ 43 (+65.38%)
Mutual labels:  concept
ScrapeM
A monadic web scraping library
Stars: ✭ 17 (-34.62%)
Mutual labels:  monad
ts-belt
🔧 Fast, modern, and practical utility library for FP in TypeScript.
Stars: ✭ 439 (+1588.46%)
Mutual labels:  monad
sdsl-lite
Succinct Data Structure Library 3.0
Stars: ✭ 55 (+111.54%)
Mutual labels:  cpp20
malloy
A C++ library providing embeddable server & client components for both HTTP and WebSocket.
Stars: ✭ 29 (+11.54%)
Mutual labels:  cpp20
clc
No description or website provided.
Stars: ✭ 20 (-23.08%)
Mutual labels:  cpp20
BinaryLove3
Simple C++ 20 Serialization Library that works out of the box with aggregate types!
Stars: ✭ 13 (-50%)
Mutual labels:  cpp20
Planeverb
Project Planeverb is a CPU based real-time wave-based acoustics engine for games. It comes with an integration with the Unity Engine.
Stars: ✭ 22 (-15.38%)
Mutual labels:  concept
explicit-semantic-analysis
Wikipedia-based Explicit Semantic Analysis, as described by Gabrilovich and Markovitch
Stars: ✭ 34 (+30.77%)
Mutual labels:  concept
f
a library to write async vert.x code similar as using java syntax
Stars: ✭ 22 (-15.38%)
Mutual labels:  monad
modern-cpp
C++ online course. Modules about modern C++ features. C++11, C++14, C++17 and C++20
Stars: ✭ 15 (-42.31%)
Mutual labels:  cpp20
fpEs
Functional Programming for EcmaScript(Javascript)
Stars: ✭ 40 (+53.85%)
Mutual labels:  monad
HelvetaCS
Modern C++ CS:GO base
Stars: ✭ 41 (+57.69%)
Mutual labels:  cpp20
php-slang
The place where PHP meets Functional Programming
Stars: ✭ 107 (+311.54%)
Mutual labels:  monad
mercator
Automatic typeclass-based abstraction over monad-like types
Stars: ✭ 54 (+107.69%)
Mutual labels:  monad

Test by GCC latest Test by MSVC

harmony

"harmony" is a header only library for working with monad in the C++ world.

It identifies monadic types by CPO and concept, and adds support for bind and some monadic operations.

A monadic type, for example...

  • Pointer
  • Smart Pointer (std::unique_ptr<T>, std::shared_ptr<T>)
  • std::optional<T>
  • Containers (std::vector<T>, std::list<T>... etc)
  • Either<L, R> (Result<T, E>) like types
  • Any program defined types that can recognized monad

Example

#include <iostream>
#include <optional>

// Main header of this library
#include "harmony.hpp"

int main() {
  
  std::optional<int> opt = 10;

  // Processing chaining
  std::optional<int> result = harmony::monas(opt) | [](int n) { return n + n; }
                                                  | [](int n) { return n + 100;};
  
  std::cout << *result; // 120
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

#include <iostream>
#include <optional>

#include "harmony.hpp"

int main() {
  
  std::optional<int> opt = 10;
  
  std::optional<int> result = harmony::monas(opt) | [](int n) { return n + n; }
                                                  | [](int n) { return n + 100; }
                                                  | [](int)   { return std::nullopt; }  // A processsing that fails
                                                  | [](int n) { return n*n; };
  
  if (harmony::validate(result)) {
    std::cout << *result;
  } else {
    std::cout << "failed!"; // This is called
  }
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

Overview

  • Generic library based on Customization Point Object (CPO) and Concept
  • All bind operator (operator|) is use Hidden friends idiom
  • Header only
  • Requires C++20 or later
    • GCC 10.1 or later
    • MSVC 2019 Preview latest

Facility

concept uwrappable

The uwrappable concept determines whether a type is monadic and is a fundamental concept in this library.

It's defined as follows:

template<typename T>
concept unwrappable = requires(T&& m) {
  { harmony::cpo::unwrap(std::forward<T>(m)) } -> not_void;
};

It is required to be able to retrieve the value contained in the type by unwrap CPO.

CPO unwrap

The name harmony::unwrap denotes a customization point object.

Given a subexpression E with type T, let t be an lvalue that denotes the reified object for E. Then:

  1. If T is an pointer type or indirectly readable (by operator*) class type, harmony::unwrap(E) is expression-equivalent to *t.
  2. Otherwise, if t.value() is a valid expression whose type not void, harmony::unwrap(E) is expression-equivalent to t.value().
  3. Otherwise, if t.unwrap() is a valid expression whose type not void, harmony::unwrap(E) is expression-equivalent to t.unwrap().
  4. Otherwise, if T modeles std::ranges::range, harmony::unwrap(E) is E.
  5. Otherwise, harmony::unwrap(E) is ill-formed.

If E is an rvalue, we get the same result as above with t as the rvalue.

concept maybe list

The types that modeled maybe, list correspond to maybe monad, list monad, respectively.

template<typename T>
concept maybe =
 unwrappable<T> and
 requires(const T& m) {
   { harmony::cpo::validate(m) } -> std::same_as<bool>;
 };

template<typename T>
concept list = maybe<T> and std::ranges::range<T>;

list is maybe and range, maybe is uwrappable and requires that it is possible to determine if the contents are present by validate CPO.

CPO validate

The name harmony::validate denotes a customization point object.

Given a subexpression E with type T, let t be an const lvalue that denotes the reified object for E. Then:

  1. If T not modeles unwrappable, harmony::validate(E) is ill-formed.
  2. If bool(t) is a valid expression, harmony::validate(E) is expression-equivalent to bool(t).
  3. Otherwise, if t.has_value() is a valid expression, harmony::validate(E) is expression-equivalent to t.has_value().
  4. Otherwise, if t.is_ok() is a valid expression, harmony::validate(E) is expression-equivalent to t.is_ok().
  5. Otherwise, if std::ranges::empty(t) is a valid expression, harmony::validate(E) is expression-equivalent to std::ranges::empty(t).
  6. Otherwise, harmony::validate(E) is ill-formed.

Whenever harmony::validate(E) is a valid expression, it has type bool.

concept rewrappable

rewrappable indicates that the value of type T can be unit (or return) for an object of type M.

template<typename M, typename T>
concept rewrappable = 
  unwrappable<M> and
  requires(M& m, T&& v) {
    harmony::cpo::unit(m, std::forward<T>(v));
  };

This is also defined by unit CPO.

CPO unit

The name harmony::unit denotes a customization point object.

Given a subexpression E and F with type T and U, let t, u be an lvalue that denotes the reified object for E, F, let m that denotes the result for cpo::unwrap(t). Then:

  1. If T not modeles unwrappable, harmony::unit(E, F) is ill-formed.
  2. Otherwise, If m is lvalue reference, decltype((m)) and U models std::assignable_from, harmony::unit(E, F) is expression-equivalent to m = u.
  3. Otherwise, if T& and U models std::assignable_from, harmony::unit(E, F) is expression-equivalent to t = u.
  4. Otherwise, harmony::unit(E, F) is ill-formed.

If F is an rvalue, we get the same result as above with u as the rvalue.

concept monadic

monadic indicates that the result of applying callable F to the contents of unwrappable M can be reassigned by unit CPO.

template<typename F, typename M>
concept monadic = 
  std::invocable<F, traits::unwrap_t<M>> and
  rewrappable<M, std::invoke_result_t<F, traits::unwrap_t<M>>>;

concept either

The type that models either corresponds to Either monad.

template<typename T>
concept either = 
  maybe<T> and
  requires(T&& t) {
    {cpo::unwrap_other(std::forward<T>(t))} -> not_void;
  };

either is maybe, and indicates that an invalid value (equivalent to) can be retrieved.

This is also defined by unwrap_other CPO.

CPO unwrap_other

The name harmony::unwrap_other denotes a customization point object.

Given a subexpression E with type T, let t be an lvalue that denotes the reified object for E. Then:

  1. If T not modeles maybe, harmony::unwrap_other(E) is ill-formed.
  2. If T is an specialization of std::optional, harmony::unwrap_other(E) is std::nullopt.
  3. Otherwise, if T is an pointer type or pointer like type (e.g smart pointer types), harmony::unwrap_other(E) is nullptr.
  4. Otherwise, if t.error() is a valid expression whose type not void, harmony::unwrap_other(E) is expression-equivalent to t.error().
  5. Otherwise, if t.unwrap_err() is a valid expression whose type not void, harmony::unwrap_other(E) is expression-equivalent to t.unwrap_err().
  6. Otherwise, harmony::unwrap_other(E) is ill-formed.

If E is an rvalue, we get the same result as above with t as the rvalue.

type monas<T>

harmony::monas is the starting point for using the facilities of this library. It's a thin wrapper for monadic types.

operator bind

This library uses operator| as the bind operator (e.g >>=).

#include <iostream>
#include <optional>

// Main header of this library
#include "harmony.hpp"

int main() {
  
  std::optional<int> opt = 10;

  // Process chaining
  std::optional<int> result = harmony::monas(opt) | [](int n) { return n + n; }
                                                  | [](int n) { return n + 100;};
  
  std::cout << *result; // 120
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

(The code at the beginning is republished.)

You can chain any number of operations on valid values. They will not be called on invalid values.

However, if you want to change the type, use map.

monadic operation map(transform)/map_err

map/transform performs the conversion of valid values and map_err performs the conversion of invalid values.

transform is a mere alias for map.

int main() {
  using namespace harmony::monadic_op;

  // Conversion of valid value. int -> double
  auto result = std::optional<int>{10} | map([](int n) { return double(n) + 0.1; });
  // decltype(result) is not std::optional<double>, but a type like Either<double, nullopt_t>.
  
  std::cout << harmony::unwrap(result) << std::endl; // 10.1

  // Conversion of invalid value. std::nullopt_t -> bool
  auto err = std::optional<int>{} | map_err([](std::nullopt_t) { return false; });
  // decltype(err) is not std::optional<bool>, but a type like Either<int, bool>.

  std::cout << std::boolalpha << harmony::unwrap_other(err);  // false
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

Both take one Callable object f, as an argument. The return type of f is arbitrary, but the result is wrapped in harmony::monas (So you can continue to chain bind and other monadic operations.).

The type on the left side of | map(...) must models either.

monadic operation and_then/or_else

and_then and or_else are similar to map and map_err. The difference is that the Callable return type that you receive must be modeles either.

The return type in both cases must be able to accept the other unconverted value as is.

int main() {
  using namespace harmony::monadic_op;

  // Conversion of valid value. int -> double
  auto andthen = std::optional<int>{10} | and_then([](int n) { return std::optional<double>(double(n) + 0.1); });
  // decltype(*andthen) is std::optional<double>.
  
  std::cout << harmony::unwrap(andthen) << std::endl; // 10.1

  // Conversion of invalid value. std::nullopt_t -> bool
  auto orelse = std::optional<int>{} | or_else([](std::nullopt_t) { return std::optional<double>(-0.0); });
  // decltype(*orelse) is std::optional<double>.

  std::cout << harmony::unwrap(orelse);  // -0.0
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

These also wrap either return type with harmony::monas (So you can continue to chain bind and other monadic operations.).

The type on the left side of | and_then(...)(| or_else(...)) must models either.

monadic operation match(fold)

match takes a process for each valid and invalid value and applies it appropriately, depending on the state of the object.

However, the return type must be aggregated into one type.

fold is a mere alias for match.

int main() {
  using namespace harmony::monadic_op;

  int n = 10;

  int r = harmony::monas(&n)
    | match([](int n){ return 2*n;},          // Processing for valid values
            [](std::nullptr_t) { return 0;}); // Processing for invalid values

  std::cout << r << std::endl;  // 20

  int *p = nullptr;

  r = harmony::monas(p)
    | match([](int){ return 0;}, [](std::nullptr_t) { return 1;});

  std::cout << r << std::endl;  // 1
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

You can also pass only one Callable object to match (e.g generic lambda).

The type on the left side of | match(...) must models either.

monadic operation exists

exists applies the predicate and returns the result if the target object has a valid value. If it has an invalid value, it immediately returns false.

int n = 10;

bool r = harmony::monas(&n)
  | [](int n) { return n + n; }
  | exists([](int n) { return n == 20;});
// r == true

It also behaves like list for std::any_of.

std::vector<int> vec = {2, 4, 6, 8, 10};

bool r = vec | exists([](int n) { return n == 8; });
// r == true

The type on the left side of | exists(...) must models maybe.

monadic operation try_catch

try_catch takes a callable f and its arguments and returns Either with its result and the std::exception_ptr.

int main() {
  using namespace harmony::monadic_op;

  // Processing that can throw an exception
  auto f = [](int n, int m) -> int {
     if (m == 0) throw "division by zero";
     return n / m;
   };

  auto r = try_catch(f, 4, 2)
    | map([](int n) { return n == 2; });

  std::cout << std::boolalpha << harmony::unwrap(r) << std::endl; // true

  auto str = try_catch(f, 4, 0)
    | map_err([](std::exception_ptr exptr) { 
        try { std::rethrow_exception(exptr); }
        catch(const char* message) {
          return std::string{message};
        }
      });

  std::cout << harmony::unwrap_other(str);  // division by zero
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

operation map_to<T>/fold_to<T>

map_to<T> and fold_to<T> are map and fold(match) convenience operations, respectively. They return the converted value directly.

Using this, the previous code can be written as follows

int main() {
  using namespace harmony::monadic_op;

  // Processing that can throw an exception
  auto f = [](int n, int m) -> int {
     if (m == 0) throw "division by zero";
     return n / m;
   };

  bool r = try_catch(f, 4, 2)
    | map([](int n) { return n == 2; })
    | map_to<bool>;

  std::cout << std::boolalpha << r << std::endl; // true

  std::string str = try_catch(f, 4, 0)
    | map([](int) { return std::string{}; })  // To match the type
    | map_err([](std::exception_ptr exptr) { 
        try { std::rethrow_exception(exptr); }
        catch(const char* message) {
          return std::string{message};
        }
      })
    | fold_to<std::string>;

  std::cout << str;  // division by zero
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

If a type that is merely a maybe has an invalid value, it returns the default constructed value (if possible).

Also, in both cases, narrowing conversion is not allowed.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].