Managing different types of morphism and their compositions
up vote
2
down vote
favorite
Here's some code I wrote to manage the different types of morphism and their compositions in C++17.
Let me know if you had any suggestions for substantial simplifications or improvements, or if I missed something.
There's an example at the end that composes a number of different morphisms together and then confirms that the final result can be used in a constexpr
context.
#include <iostream>
#include <vector>
#include <type_traits>
/// Extract the first argument type from a map.
template <typename R, typename A0, typename ... As>
constexpr A0 firstArg (R(*)(A0, As...));
/** Set theoretic morphisms **/
template<typename S, typename T>
struct Homomorphism {
constexpr static T value(const S);
};
template<typename S, typename T>
struct Monomorphism: Homomorphism<S, T> {
constexpr static T value(const S);
};
template<typename S, typename T>
struct Epimorphism: Homomorphism<S, T> {
constexpr static T value(const S);
};
template<typename S>
struct Endomorphism: Homomorphism<S, S> {
constexpr static S value(const S);
};
template<typename S, typename T>
struct Isomorphism: Monomorphism<S, T>, Epimorphism<S, T> {
constexpr static T value(const S);
};
template<typename S>
struct Automorphism: Endomorphism<S>, Isomorphism<S, S> {
constexpr static S value(const S);
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct MonomorphismComposition: Monomorphism<S, R> {
static_assert(std::is_base_of_v<Monomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Monomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct EpimorphismComposition: Epimorphism<S, R> {
static_assert(std::is_base_of_v<Epimorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Epimorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct IsomorphismComposition: Isomorphism<S, R> {
static_assert(std::is_base_of_v<Isomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Isomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename T1 = std::decay_t<decltype(firstArg(&H1::value))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename T = std::enable_if_t<std::is_same_v<T1, T2>, T1>>
struct EndomorphismComposition: Endomorphism<T> {
static_assert(std::is_base_of_v<Endomorphism<T>, H1>);
static_assert(std::is_base_of_v<Endomorphism<T>, H2>);
constexpr static T value(const T &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename T1 = std::decay_t<decltype(firstArg(&H1::value))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename T = std::enable_if_t<std::is_same_v<T1, T2>, T1>>
struct AutomorphismComposition: Automorphism<T> {
static_assert(std::is_base_of_v<Automorphism<T>, H1>);
static_assert(std::is_base_of_v<Automorphism<T>, H2>);
constexpr static T value(const T &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct HomomorphismComposition: Homomorphism<S, R> {
static_assert(std::is_base_of_v<Homomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Homomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename T>
struct IdentityAutomorphism: Automorphism<T> {
constexpr static T value(const T &t) {
return t;
}
};
/** This is a monomorphism if the type of S is a subset of T, i.e. is convertible to T. **/
template<typename S, typename T>
struct EmbeddingMonomorphism: Monomorphism<S, T> {
static_assert(std::is_convertible_v<S, T>);
constexpr static T value(const S &s) {
return s;
}
};
/*** EXAMPLE ***/
struct divby2: Automorphism<double> { constexpr static double value(double d) { return d / 2; }};
struct embed_divby2: MonomorphismComposition<EmbeddingMonomorphism<int, double>, divby2> {};
struct squared: Monomorphism<int, int>, Endomorphism<int> { constexpr static int value(int i) { return i * i; } };
struct squared_embed_divby2: MonomorphismComposition<squared, embed_divby2> {};
struct S {
explicit constexpr S(int val): val{val} {};
const int val;
};
struct s_to_int: Isomorphism<S, int> { constexpr static int value(const S &s) { return s.val; } };
struct bighom: MonomorphismComposition<s_to_int, squared_embed_divby2> {};
struct biggerhom: MonomorphismComposition<bighom, IdentityAutomorphism<double>> {};
constexpr auto sum() {
double d = 0;
for (int i = 0; i < 10; ++i)
d += biggerhom::value(S{i});
return d;
}
int main() {
for (int i = 0; i < 10; ++i)
std::cout << biggerhom::value(S{i}) << 'n';
constexpr double d = sum();
std::cout << "Sum is: " << d << 'n';
}
c++ object-oriented inheritance c++17 polymorphism
add a comment |
up vote
2
down vote
favorite
Here's some code I wrote to manage the different types of morphism and their compositions in C++17.
Let me know if you had any suggestions for substantial simplifications or improvements, or if I missed something.
There's an example at the end that composes a number of different morphisms together and then confirms that the final result can be used in a constexpr
context.
#include <iostream>
#include <vector>
#include <type_traits>
/// Extract the first argument type from a map.
template <typename R, typename A0, typename ... As>
constexpr A0 firstArg (R(*)(A0, As...));
/** Set theoretic morphisms **/
template<typename S, typename T>
struct Homomorphism {
constexpr static T value(const S);
};
template<typename S, typename T>
struct Monomorphism: Homomorphism<S, T> {
constexpr static T value(const S);
};
template<typename S, typename T>
struct Epimorphism: Homomorphism<S, T> {
constexpr static T value(const S);
};
template<typename S>
struct Endomorphism: Homomorphism<S, S> {
constexpr static S value(const S);
};
template<typename S, typename T>
struct Isomorphism: Monomorphism<S, T>, Epimorphism<S, T> {
constexpr static T value(const S);
};
template<typename S>
struct Automorphism: Endomorphism<S>, Isomorphism<S, S> {
constexpr static S value(const S);
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct MonomorphismComposition: Monomorphism<S, R> {
static_assert(std::is_base_of_v<Monomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Monomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct EpimorphismComposition: Epimorphism<S, R> {
static_assert(std::is_base_of_v<Epimorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Epimorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct IsomorphismComposition: Isomorphism<S, R> {
static_assert(std::is_base_of_v<Isomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Isomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename T1 = std::decay_t<decltype(firstArg(&H1::value))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename T = std::enable_if_t<std::is_same_v<T1, T2>, T1>>
struct EndomorphismComposition: Endomorphism<T> {
static_assert(std::is_base_of_v<Endomorphism<T>, H1>);
static_assert(std::is_base_of_v<Endomorphism<T>, H2>);
constexpr static T value(const T &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename T1 = std::decay_t<decltype(firstArg(&H1::value))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename T = std::enable_if_t<std::is_same_v<T1, T2>, T1>>
struct AutomorphismComposition: Automorphism<T> {
static_assert(std::is_base_of_v<Automorphism<T>, H1>);
static_assert(std::is_base_of_v<Automorphism<T>, H2>);
constexpr static T value(const T &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct HomomorphismComposition: Homomorphism<S, R> {
static_assert(std::is_base_of_v<Homomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Homomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename T>
struct IdentityAutomorphism: Automorphism<T> {
constexpr static T value(const T &t) {
return t;
}
};
/** This is a monomorphism if the type of S is a subset of T, i.e. is convertible to T. **/
template<typename S, typename T>
struct EmbeddingMonomorphism: Monomorphism<S, T> {
static_assert(std::is_convertible_v<S, T>);
constexpr static T value(const S &s) {
return s;
}
};
/*** EXAMPLE ***/
struct divby2: Automorphism<double> { constexpr static double value(double d) { return d / 2; }};
struct embed_divby2: MonomorphismComposition<EmbeddingMonomorphism<int, double>, divby2> {};
struct squared: Monomorphism<int, int>, Endomorphism<int> { constexpr static int value(int i) { return i * i; } };
struct squared_embed_divby2: MonomorphismComposition<squared, embed_divby2> {};
struct S {
explicit constexpr S(int val): val{val} {};
const int val;
};
struct s_to_int: Isomorphism<S, int> { constexpr static int value(const S &s) { return s.val; } };
struct bighom: MonomorphismComposition<s_to_int, squared_embed_divby2> {};
struct biggerhom: MonomorphismComposition<bighom, IdentityAutomorphism<double>> {};
constexpr auto sum() {
double d = 0;
for (int i = 0; i < 10; ++i)
d += biggerhom::value(S{i});
return d;
}
int main() {
for (int i = 0; i < 10; ++i)
std::cout << biggerhom::value(S{i}) << 'n';
constexpr double d = sum();
std::cout << "Sum is: " << d << 'n';
}
c++ object-oriented inheritance c++17 polymorphism
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
Here's some code I wrote to manage the different types of morphism and their compositions in C++17.
Let me know if you had any suggestions for substantial simplifications or improvements, or if I missed something.
There's an example at the end that composes a number of different morphisms together and then confirms that the final result can be used in a constexpr
context.
#include <iostream>
#include <vector>
#include <type_traits>
/// Extract the first argument type from a map.
template <typename R, typename A0, typename ... As>
constexpr A0 firstArg (R(*)(A0, As...));
/** Set theoretic morphisms **/
template<typename S, typename T>
struct Homomorphism {
constexpr static T value(const S);
};
template<typename S, typename T>
struct Monomorphism: Homomorphism<S, T> {
constexpr static T value(const S);
};
template<typename S, typename T>
struct Epimorphism: Homomorphism<S, T> {
constexpr static T value(const S);
};
template<typename S>
struct Endomorphism: Homomorphism<S, S> {
constexpr static S value(const S);
};
template<typename S, typename T>
struct Isomorphism: Monomorphism<S, T>, Epimorphism<S, T> {
constexpr static T value(const S);
};
template<typename S>
struct Automorphism: Endomorphism<S>, Isomorphism<S, S> {
constexpr static S value(const S);
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct MonomorphismComposition: Monomorphism<S, R> {
static_assert(std::is_base_of_v<Monomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Monomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct EpimorphismComposition: Epimorphism<S, R> {
static_assert(std::is_base_of_v<Epimorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Epimorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct IsomorphismComposition: Isomorphism<S, R> {
static_assert(std::is_base_of_v<Isomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Isomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename T1 = std::decay_t<decltype(firstArg(&H1::value))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename T = std::enable_if_t<std::is_same_v<T1, T2>, T1>>
struct EndomorphismComposition: Endomorphism<T> {
static_assert(std::is_base_of_v<Endomorphism<T>, H1>);
static_assert(std::is_base_of_v<Endomorphism<T>, H2>);
constexpr static T value(const T &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename T1 = std::decay_t<decltype(firstArg(&H1::value))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename T = std::enable_if_t<std::is_same_v<T1, T2>, T1>>
struct AutomorphismComposition: Automorphism<T> {
static_assert(std::is_base_of_v<Automorphism<T>, H1>);
static_assert(std::is_base_of_v<Automorphism<T>, H2>);
constexpr static T value(const T &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct HomomorphismComposition: Homomorphism<S, R> {
static_assert(std::is_base_of_v<Homomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Homomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename T>
struct IdentityAutomorphism: Automorphism<T> {
constexpr static T value(const T &t) {
return t;
}
};
/** This is a monomorphism if the type of S is a subset of T, i.e. is convertible to T. **/
template<typename S, typename T>
struct EmbeddingMonomorphism: Monomorphism<S, T> {
static_assert(std::is_convertible_v<S, T>);
constexpr static T value(const S &s) {
return s;
}
};
/*** EXAMPLE ***/
struct divby2: Automorphism<double> { constexpr static double value(double d) { return d / 2; }};
struct embed_divby2: MonomorphismComposition<EmbeddingMonomorphism<int, double>, divby2> {};
struct squared: Monomorphism<int, int>, Endomorphism<int> { constexpr static int value(int i) { return i * i; } };
struct squared_embed_divby2: MonomorphismComposition<squared, embed_divby2> {};
struct S {
explicit constexpr S(int val): val{val} {};
const int val;
};
struct s_to_int: Isomorphism<S, int> { constexpr static int value(const S &s) { return s.val; } };
struct bighom: MonomorphismComposition<s_to_int, squared_embed_divby2> {};
struct biggerhom: MonomorphismComposition<bighom, IdentityAutomorphism<double>> {};
constexpr auto sum() {
double d = 0;
for (int i = 0; i < 10; ++i)
d += biggerhom::value(S{i});
return d;
}
int main() {
for (int i = 0; i < 10; ++i)
std::cout << biggerhom::value(S{i}) << 'n';
constexpr double d = sum();
std::cout << "Sum is: " << d << 'n';
}
c++ object-oriented inheritance c++17 polymorphism
Here's some code I wrote to manage the different types of morphism and their compositions in C++17.
Let me know if you had any suggestions for substantial simplifications or improvements, or if I missed something.
There's an example at the end that composes a number of different morphisms together and then confirms that the final result can be used in a constexpr
context.
#include <iostream>
#include <vector>
#include <type_traits>
/// Extract the first argument type from a map.
template <typename R, typename A0, typename ... As>
constexpr A0 firstArg (R(*)(A0, As...));
/** Set theoretic morphisms **/
template<typename S, typename T>
struct Homomorphism {
constexpr static T value(const S);
};
template<typename S, typename T>
struct Monomorphism: Homomorphism<S, T> {
constexpr static T value(const S);
};
template<typename S, typename T>
struct Epimorphism: Homomorphism<S, T> {
constexpr static T value(const S);
};
template<typename S>
struct Endomorphism: Homomorphism<S, S> {
constexpr static S value(const S);
};
template<typename S, typename T>
struct Isomorphism: Monomorphism<S, T>, Epimorphism<S, T> {
constexpr static T value(const S);
};
template<typename S>
struct Automorphism: Endomorphism<S>, Isomorphism<S, S> {
constexpr static S value(const S);
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct MonomorphismComposition: Monomorphism<S, R> {
static_assert(std::is_base_of_v<Monomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Monomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct EpimorphismComposition: Epimorphism<S, R> {
static_assert(std::is_base_of_v<Epimorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Epimorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct IsomorphismComposition: Isomorphism<S, R> {
static_assert(std::is_base_of_v<Isomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Isomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename T1 = std::decay_t<decltype(firstArg(&H1::value))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename T = std::enable_if_t<std::is_same_v<T1, T2>, T1>>
struct EndomorphismComposition: Endomorphism<T> {
static_assert(std::is_base_of_v<Endomorphism<T>, H1>);
static_assert(std::is_base_of_v<Endomorphism<T>, H2>);
constexpr static T value(const T &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename T1 = std::decay_t<decltype(firstArg(&H1::value))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename T = std::enable_if_t<std::is_same_v<T1, T2>, T1>>
struct AutomorphismComposition: Automorphism<T> {
static_assert(std::is_base_of_v<Automorphism<T>, H1>);
static_assert(std::is_base_of_v<Automorphism<T>, H2>);
constexpr static T value(const T &s) {
return H2::value(H1::value(s));
}
};
template<typename H1,
typename H2,
typename S = std::decay_t<decltype(firstArg(&H1::value))>,
typename T1 = std::decay_t<decltype(H1::value(std::declval<S>()))>,
typename T2 = std::decay_t<decltype(firstArg(&H2::value))>,
typename R = std::decay_t<decltype(H2::value(std::declval<T2>()))>>
struct HomomorphismComposition: Homomorphism<S, R> {
static_assert(std::is_base_of_v<Homomorphism<S, T1>, H1>);
static_assert(std::is_base_of_v<Homomorphism<T2, R>, H2>);
static_assert(std::is_same_v<T1, T2>);
constexpr static R value(const S &s) {
return H2::value(H1::value(s));
}
};
template<typename T>
struct IdentityAutomorphism: Automorphism<T> {
constexpr static T value(const T &t) {
return t;
}
};
/** This is a monomorphism if the type of S is a subset of T, i.e. is convertible to T. **/
template<typename S, typename T>
struct EmbeddingMonomorphism: Monomorphism<S, T> {
static_assert(std::is_convertible_v<S, T>);
constexpr static T value(const S &s) {
return s;
}
};
/*** EXAMPLE ***/
struct divby2: Automorphism<double> { constexpr static double value(double d) { return d / 2; }};
struct embed_divby2: MonomorphismComposition<EmbeddingMonomorphism<int, double>, divby2> {};
struct squared: Monomorphism<int, int>, Endomorphism<int> { constexpr static int value(int i) { return i * i; } };
struct squared_embed_divby2: MonomorphismComposition<squared, embed_divby2> {};
struct S {
explicit constexpr S(int val): val{val} {};
const int val;
};
struct s_to_int: Isomorphism<S, int> { constexpr static int value(const S &s) { return s.val; } };
struct bighom: MonomorphismComposition<s_to_int, squared_embed_divby2> {};
struct biggerhom: MonomorphismComposition<bighom, IdentityAutomorphism<double>> {};
constexpr auto sum() {
double d = 0;
for (int i = 0; i < 10; ++i)
d += biggerhom::value(S{i});
return d;
}
int main() {
for (int i = 0; i < 10; ++i)
std::cout << biggerhom::value(S{i}) << 'n';
constexpr double d = sum();
std::cout << "Sum is: " << d << 'n';
}
c++ object-oriented inheritance c++17 polymorphism
c++ object-oriented inheritance c++17 polymorphism
edited yesterday
Calak
2,001314
2,001314
asked 2 days ago
Sebastian
235
235
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
up vote
3
down vote
accepted
I'm under the impression that this code is part of a larger design, whose extent and intentions I can't entirely guess. Sorry if my review seems a bit restrictive or short sighted in that regard.
General design
My understanding is that you want to build a mathematical type system over the fairly permissive C++ set of function and function-like types. That's a noble undertaking but I'm afraid that they are so different realities that it will end in a misunderstanding.
Take your definition of what a c++ application is: R(*)(A0, As...)
(in the firstArg
signature). This will match a function pointer, but function references, lambdas, member functions, functors, pointer to member functions are as legitimate targets and they won't necessarily match this signature.
Then there is also the problem of function overloading: what if foo
has three overloads? Which one will R(*)(A0, As...)
match? (the answer is none, it simply won't compile).
Three lines further, in contrast to this vigorous simplification, you begin to build a complex inheritance tree whose semantics are transparent to the compiler, at least beyond the identity between argument and return type: how would the compiler decide if a function really is a monomorphism?
I believe you would be better off with a simpler design uncoupling c++ types and mathematical types, at least to a certain extent.
C++ application composition
That is already a hard problem on its own right, depending on how much you want to constrain the domain. But it's certainly easier if you want to only accept applications exposing S value(T)
as an interface. What I'd suggest then is to provide a handier template:
template <typename R, typename A>
struct Application {
using result_type = R;
using argument_type = A;
template <typename F>
Application(F f) : fn(f) {}
R value(A) { return fn(a); } // no need to have it static now
std::function<R(A)> fn;
};
You can then turn any complying function-like object into a compatible Application
: auto square = Application<double, double>((auto n) { return n * n; };
. Verifying application composition is now trivial: std::is_same<typename F::argument_type, typename G::result_type
(std::is_convertible
might be a choice too).
Morphisms classification
I'm a bit skeptical about this all-encompassing inheritance tree. The first thing to note is that simple inheritance won't constrain value
in the derived class based on its specification in the base class. Inheriting from endomorphism
won't constrain a class to expose a value
function whose argument type and return type are the same. Virtual functions would constrain it, but frankly, with multiple inheritance that seems unnecessarily dangerous and complex.
What you could do is keep the kind of morphism as a tag and then constrain the function value
with std::enable_if
and std::conditional
:
template <typename R, typename A, typename Morphism>
struct Application {
// ...
using morphism_tag = Morphism;
using result_type = std::conditional_t<std::is_base_of_v<Endomorphism, morphism_tag>,
std::enable_if_t<std::is_same_v<R, A>, R>,
R
>;
result_type value(argument_type);
// ...
};
Your code won't compile if you generate an Application
with an Endomorphism
tag whose return type doesn't match the argument type.
I'm not sure how extensible it would be though, and which rules we would be able to enforce.
Morphisms composition
With this infrastructure, you would be able to compose morphisms more easily, along those lines:
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto resulting_morphism(Application<R1, A1, T1>, Application<R2, A2, T2>) {
if constexpr (std::is_base_of_v<Endomorphism, T1> && std::is_base_of_v<Endomorphism, T2>)
return Monomorphism();
else if constexpr ( /*...*/ )
// ...
else throw std::logical_error(); // throw in constexpr context simply won't compile
)
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto compose(Application<R1, A1, T1> a1, Application<R2, A2, T2> a2) {
return Application<R1, A2, decltype(resulting_morphism(a1, a2)>((auto arg) {
return a1(a2(arg));
});
}
This is all excellent. Thank you so much for the feedback!
– Sebastian
yesterday
A question: I suppose this would limitApplication
to not beingconstexpr
sincestd::function
is notconstexpr
. (I believe it has a non-trivial destructor?) I am quite inexperienced with tags and have never usedstd::conditional
, so this is teaching me a lot. I'm very thankful for your answer.
– Sebastian
yesterday
Also, the code was just my brain spiralling out again upon realizing - months ago - that there is an epi between mazes with infinitesimally thin walls, and mazes where the walls have the same thickness as the cells themselves.
– Sebastian
18 hours ago
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
I'm under the impression that this code is part of a larger design, whose extent and intentions I can't entirely guess. Sorry if my review seems a bit restrictive or short sighted in that regard.
General design
My understanding is that you want to build a mathematical type system over the fairly permissive C++ set of function and function-like types. That's a noble undertaking but I'm afraid that they are so different realities that it will end in a misunderstanding.
Take your definition of what a c++ application is: R(*)(A0, As...)
(in the firstArg
signature). This will match a function pointer, but function references, lambdas, member functions, functors, pointer to member functions are as legitimate targets and they won't necessarily match this signature.
Then there is also the problem of function overloading: what if foo
has three overloads? Which one will R(*)(A0, As...)
match? (the answer is none, it simply won't compile).
Three lines further, in contrast to this vigorous simplification, you begin to build a complex inheritance tree whose semantics are transparent to the compiler, at least beyond the identity between argument and return type: how would the compiler decide if a function really is a monomorphism?
I believe you would be better off with a simpler design uncoupling c++ types and mathematical types, at least to a certain extent.
C++ application composition
That is already a hard problem on its own right, depending on how much you want to constrain the domain. But it's certainly easier if you want to only accept applications exposing S value(T)
as an interface. What I'd suggest then is to provide a handier template:
template <typename R, typename A>
struct Application {
using result_type = R;
using argument_type = A;
template <typename F>
Application(F f) : fn(f) {}
R value(A) { return fn(a); } // no need to have it static now
std::function<R(A)> fn;
};
You can then turn any complying function-like object into a compatible Application
: auto square = Application<double, double>((auto n) { return n * n; };
. Verifying application composition is now trivial: std::is_same<typename F::argument_type, typename G::result_type
(std::is_convertible
might be a choice too).
Morphisms classification
I'm a bit skeptical about this all-encompassing inheritance tree. The first thing to note is that simple inheritance won't constrain value
in the derived class based on its specification in the base class. Inheriting from endomorphism
won't constrain a class to expose a value
function whose argument type and return type are the same. Virtual functions would constrain it, but frankly, with multiple inheritance that seems unnecessarily dangerous and complex.
What you could do is keep the kind of morphism as a tag and then constrain the function value
with std::enable_if
and std::conditional
:
template <typename R, typename A, typename Morphism>
struct Application {
// ...
using morphism_tag = Morphism;
using result_type = std::conditional_t<std::is_base_of_v<Endomorphism, morphism_tag>,
std::enable_if_t<std::is_same_v<R, A>, R>,
R
>;
result_type value(argument_type);
// ...
};
Your code won't compile if you generate an Application
with an Endomorphism
tag whose return type doesn't match the argument type.
I'm not sure how extensible it would be though, and which rules we would be able to enforce.
Morphisms composition
With this infrastructure, you would be able to compose morphisms more easily, along those lines:
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto resulting_morphism(Application<R1, A1, T1>, Application<R2, A2, T2>) {
if constexpr (std::is_base_of_v<Endomorphism, T1> && std::is_base_of_v<Endomorphism, T2>)
return Monomorphism();
else if constexpr ( /*...*/ )
// ...
else throw std::logical_error(); // throw in constexpr context simply won't compile
)
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto compose(Application<R1, A1, T1> a1, Application<R2, A2, T2> a2) {
return Application<R1, A2, decltype(resulting_morphism(a1, a2)>((auto arg) {
return a1(a2(arg));
});
}
This is all excellent. Thank you so much for the feedback!
– Sebastian
yesterday
A question: I suppose this would limitApplication
to not beingconstexpr
sincestd::function
is notconstexpr
. (I believe it has a non-trivial destructor?) I am quite inexperienced with tags and have never usedstd::conditional
, so this is teaching me a lot. I'm very thankful for your answer.
– Sebastian
yesterday
Also, the code was just my brain spiralling out again upon realizing - months ago - that there is an epi between mazes with infinitesimally thin walls, and mazes where the walls have the same thickness as the cells themselves.
– Sebastian
18 hours ago
add a comment |
up vote
3
down vote
accepted
I'm under the impression that this code is part of a larger design, whose extent and intentions I can't entirely guess. Sorry if my review seems a bit restrictive or short sighted in that regard.
General design
My understanding is that you want to build a mathematical type system over the fairly permissive C++ set of function and function-like types. That's a noble undertaking but I'm afraid that they are so different realities that it will end in a misunderstanding.
Take your definition of what a c++ application is: R(*)(A0, As...)
(in the firstArg
signature). This will match a function pointer, but function references, lambdas, member functions, functors, pointer to member functions are as legitimate targets and they won't necessarily match this signature.
Then there is also the problem of function overloading: what if foo
has three overloads? Which one will R(*)(A0, As...)
match? (the answer is none, it simply won't compile).
Three lines further, in contrast to this vigorous simplification, you begin to build a complex inheritance tree whose semantics are transparent to the compiler, at least beyond the identity between argument and return type: how would the compiler decide if a function really is a monomorphism?
I believe you would be better off with a simpler design uncoupling c++ types and mathematical types, at least to a certain extent.
C++ application composition
That is already a hard problem on its own right, depending on how much you want to constrain the domain. But it's certainly easier if you want to only accept applications exposing S value(T)
as an interface. What I'd suggest then is to provide a handier template:
template <typename R, typename A>
struct Application {
using result_type = R;
using argument_type = A;
template <typename F>
Application(F f) : fn(f) {}
R value(A) { return fn(a); } // no need to have it static now
std::function<R(A)> fn;
};
You can then turn any complying function-like object into a compatible Application
: auto square = Application<double, double>((auto n) { return n * n; };
. Verifying application composition is now trivial: std::is_same<typename F::argument_type, typename G::result_type
(std::is_convertible
might be a choice too).
Morphisms classification
I'm a bit skeptical about this all-encompassing inheritance tree. The first thing to note is that simple inheritance won't constrain value
in the derived class based on its specification in the base class. Inheriting from endomorphism
won't constrain a class to expose a value
function whose argument type and return type are the same. Virtual functions would constrain it, but frankly, with multiple inheritance that seems unnecessarily dangerous and complex.
What you could do is keep the kind of morphism as a tag and then constrain the function value
with std::enable_if
and std::conditional
:
template <typename R, typename A, typename Morphism>
struct Application {
// ...
using morphism_tag = Morphism;
using result_type = std::conditional_t<std::is_base_of_v<Endomorphism, morphism_tag>,
std::enable_if_t<std::is_same_v<R, A>, R>,
R
>;
result_type value(argument_type);
// ...
};
Your code won't compile if you generate an Application
with an Endomorphism
tag whose return type doesn't match the argument type.
I'm not sure how extensible it would be though, and which rules we would be able to enforce.
Morphisms composition
With this infrastructure, you would be able to compose morphisms more easily, along those lines:
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto resulting_morphism(Application<R1, A1, T1>, Application<R2, A2, T2>) {
if constexpr (std::is_base_of_v<Endomorphism, T1> && std::is_base_of_v<Endomorphism, T2>)
return Monomorphism();
else if constexpr ( /*...*/ )
// ...
else throw std::logical_error(); // throw in constexpr context simply won't compile
)
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto compose(Application<R1, A1, T1> a1, Application<R2, A2, T2> a2) {
return Application<R1, A2, decltype(resulting_morphism(a1, a2)>((auto arg) {
return a1(a2(arg));
});
}
This is all excellent. Thank you so much for the feedback!
– Sebastian
yesterday
A question: I suppose this would limitApplication
to not beingconstexpr
sincestd::function
is notconstexpr
. (I believe it has a non-trivial destructor?) I am quite inexperienced with tags and have never usedstd::conditional
, so this is teaching me a lot. I'm very thankful for your answer.
– Sebastian
yesterday
Also, the code was just my brain spiralling out again upon realizing - months ago - that there is an epi between mazes with infinitesimally thin walls, and mazes where the walls have the same thickness as the cells themselves.
– Sebastian
18 hours ago
add a comment |
up vote
3
down vote
accepted
up vote
3
down vote
accepted
I'm under the impression that this code is part of a larger design, whose extent and intentions I can't entirely guess. Sorry if my review seems a bit restrictive or short sighted in that regard.
General design
My understanding is that you want to build a mathematical type system over the fairly permissive C++ set of function and function-like types. That's a noble undertaking but I'm afraid that they are so different realities that it will end in a misunderstanding.
Take your definition of what a c++ application is: R(*)(A0, As...)
(in the firstArg
signature). This will match a function pointer, but function references, lambdas, member functions, functors, pointer to member functions are as legitimate targets and they won't necessarily match this signature.
Then there is also the problem of function overloading: what if foo
has three overloads? Which one will R(*)(A0, As...)
match? (the answer is none, it simply won't compile).
Three lines further, in contrast to this vigorous simplification, you begin to build a complex inheritance tree whose semantics are transparent to the compiler, at least beyond the identity between argument and return type: how would the compiler decide if a function really is a monomorphism?
I believe you would be better off with a simpler design uncoupling c++ types and mathematical types, at least to a certain extent.
C++ application composition
That is already a hard problem on its own right, depending on how much you want to constrain the domain. But it's certainly easier if you want to only accept applications exposing S value(T)
as an interface. What I'd suggest then is to provide a handier template:
template <typename R, typename A>
struct Application {
using result_type = R;
using argument_type = A;
template <typename F>
Application(F f) : fn(f) {}
R value(A) { return fn(a); } // no need to have it static now
std::function<R(A)> fn;
};
You can then turn any complying function-like object into a compatible Application
: auto square = Application<double, double>((auto n) { return n * n; };
. Verifying application composition is now trivial: std::is_same<typename F::argument_type, typename G::result_type
(std::is_convertible
might be a choice too).
Morphisms classification
I'm a bit skeptical about this all-encompassing inheritance tree. The first thing to note is that simple inheritance won't constrain value
in the derived class based on its specification in the base class. Inheriting from endomorphism
won't constrain a class to expose a value
function whose argument type and return type are the same. Virtual functions would constrain it, but frankly, with multiple inheritance that seems unnecessarily dangerous and complex.
What you could do is keep the kind of morphism as a tag and then constrain the function value
with std::enable_if
and std::conditional
:
template <typename R, typename A, typename Morphism>
struct Application {
// ...
using morphism_tag = Morphism;
using result_type = std::conditional_t<std::is_base_of_v<Endomorphism, morphism_tag>,
std::enable_if_t<std::is_same_v<R, A>, R>,
R
>;
result_type value(argument_type);
// ...
};
Your code won't compile if you generate an Application
with an Endomorphism
tag whose return type doesn't match the argument type.
I'm not sure how extensible it would be though, and which rules we would be able to enforce.
Morphisms composition
With this infrastructure, you would be able to compose morphisms more easily, along those lines:
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto resulting_morphism(Application<R1, A1, T1>, Application<R2, A2, T2>) {
if constexpr (std::is_base_of_v<Endomorphism, T1> && std::is_base_of_v<Endomorphism, T2>)
return Monomorphism();
else if constexpr ( /*...*/ )
// ...
else throw std::logical_error(); // throw in constexpr context simply won't compile
)
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto compose(Application<R1, A1, T1> a1, Application<R2, A2, T2> a2) {
return Application<R1, A2, decltype(resulting_morphism(a1, a2)>((auto arg) {
return a1(a2(arg));
});
}
I'm under the impression that this code is part of a larger design, whose extent and intentions I can't entirely guess. Sorry if my review seems a bit restrictive or short sighted in that regard.
General design
My understanding is that you want to build a mathematical type system over the fairly permissive C++ set of function and function-like types. That's a noble undertaking but I'm afraid that they are so different realities that it will end in a misunderstanding.
Take your definition of what a c++ application is: R(*)(A0, As...)
(in the firstArg
signature). This will match a function pointer, but function references, lambdas, member functions, functors, pointer to member functions are as legitimate targets and they won't necessarily match this signature.
Then there is also the problem of function overloading: what if foo
has three overloads? Which one will R(*)(A0, As...)
match? (the answer is none, it simply won't compile).
Three lines further, in contrast to this vigorous simplification, you begin to build a complex inheritance tree whose semantics are transparent to the compiler, at least beyond the identity between argument and return type: how would the compiler decide if a function really is a monomorphism?
I believe you would be better off with a simpler design uncoupling c++ types and mathematical types, at least to a certain extent.
C++ application composition
That is already a hard problem on its own right, depending on how much you want to constrain the domain. But it's certainly easier if you want to only accept applications exposing S value(T)
as an interface. What I'd suggest then is to provide a handier template:
template <typename R, typename A>
struct Application {
using result_type = R;
using argument_type = A;
template <typename F>
Application(F f) : fn(f) {}
R value(A) { return fn(a); } // no need to have it static now
std::function<R(A)> fn;
};
You can then turn any complying function-like object into a compatible Application
: auto square = Application<double, double>((auto n) { return n * n; };
. Verifying application composition is now trivial: std::is_same<typename F::argument_type, typename G::result_type
(std::is_convertible
might be a choice too).
Morphisms classification
I'm a bit skeptical about this all-encompassing inheritance tree. The first thing to note is that simple inheritance won't constrain value
in the derived class based on its specification in the base class. Inheriting from endomorphism
won't constrain a class to expose a value
function whose argument type and return type are the same. Virtual functions would constrain it, but frankly, with multiple inheritance that seems unnecessarily dangerous and complex.
What you could do is keep the kind of morphism as a tag and then constrain the function value
with std::enable_if
and std::conditional
:
template <typename R, typename A, typename Morphism>
struct Application {
// ...
using morphism_tag = Morphism;
using result_type = std::conditional_t<std::is_base_of_v<Endomorphism, morphism_tag>,
std::enable_if_t<std::is_same_v<R, A>, R>,
R
>;
result_type value(argument_type);
// ...
};
Your code won't compile if you generate an Application
with an Endomorphism
tag whose return type doesn't match the argument type.
I'm not sure how extensible it would be though, and which rules we would be able to enforce.
Morphisms composition
With this infrastructure, you would be able to compose morphisms more easily, along those lines:
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto resulting_morphism(Application<R1, A1, T1>, Application<R2, A2, T2>) {
if constexpr (std::is_base_of_v<Endomorphism, T1> && std::is_base_of_v<Endomorphism, T2>)
return Monomorphism();
else if constexpr ( /*...*/ )
// ...
else throw std::logical_error(); // throw in constexpr context simply won't compile
)
template <typename R1, typename A1, typename T1,
typename R2, typename A2, typename T2>
constexpr auto compose(Application<R1, A1, T1> a1, Application<R2, A2, T2> a2) {
return Application<R1, A2, decltype(resulting_morphism(a1, a2)>((auto arg) {
return a1(a2(arg));
});
}
answered yesterday
papagaga
3,984221
3,984221
This is all excellent. Thank you so much for the feedback!
– Sebastian
yesterday
A question: I suppose this would limitApplication
to not beingconstexpr
sincestd::function
is notconstexpr
. (I believe it has a non-trivial destructor?) I am quite inexperienced with tags and have never usedstd::conditional
, so this is teaching me a lot. I'm very thankful for your answer.
– Sebastian
yesterday
Also, the code was just my brain spiralling out again upon realizing - months ago - that there is an epi between mazes with infinitesimally thin walls, and mazes where the walls have the same thickness as the cells themselves.
– Sebastian
18 hours ago
add a comment |
This is all excellent. Thank you so much for the feedback!
– Sebastian
yesterday
A question: I suppose this would limitApplication
to not beingconstexpr
sincestd::function
is notconstexpr
. (I believe it has a non-trivial destructor?) I am quite inexperienced with tags and have never usedstd::conditional
, so this is teaching me a lot. I'm very thankful for your answer.
– Sebastian
yesterday
Also, the code was just my brain spiralling out again upon realizing - months ago - that there is an epi between mazes with infinitesimally thin walls, and mazes where the walls have the same thickness as the cells themselves.
– Sebastian
18 hours ago
This is all excellent. Thank you so much for the feedback!
– Sebastian
yesterday
This is all excellent. Thank you so much for the feedback!
– Sebastian
yesterday
A question: I suppose this would limit
Application
to not being constexpr
since std::function
is not constexpr
. (I believe it has a non-trivial destructor?) I am quite inexperienced with tags and have never used std::conditional
, so this is teaching me a lot. I'm very thankful for your answer.– Sebastian
yesterday
A question: I suppose this would limit
Application
to not being constexpr
since std::function
is not constexpr
. (I believe it has a non-trivial destructor?) I am quite inexperienced with tags and have never used std::conditional
, so this is teaching me a lot. I'm very thankful for your answer.– Sebastian
yesterday
Also, the code was just my brain spiralling out again upon realizing - months ago - that there is an epi between mazes with infinitesimally thin walls, and mazes where the walls have the same thickness as the cells themselves.
– Sebastian
18 hours ago
Also, the code was just my brain spiralling out again upon realizing - months ago - that there is an epi between mazes with infinitesimally thin walls, and mazes where the walls have the same thickness as the cells themselves.
– Sebastian
18 hours ago
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f208736%2fmanaging-different-types-of-morphism-and-their-compositions%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown