Skip to content

Latest commit

 

History

History
197 lines (164 loc) · 5.63 KB

concept-generics.org

File metadata and controls

197 lines (164 loc) · 5.63 KB

Generics

Generics concept of the CPARSEC3 library consists of the following concepts:

Generic Data Type
A parametric typed struct/union.
Trait
A struct that provides a set of parametric typed functions/constants.
Generic Macros
A type-safe macro that provides a functionallity for easy to use traits/containers.

Generic Data Type

Generic Data Type is a parametric typed struct/union.

Generic Data Type is a similar concept as known as parameterized type, polymorphic type, template class (C++), and so on.

The below shows a example of Generic Data Types :

Array(T)
Array of objects of T type.
List(T)
Linked-list of objects of T type or NULL.
Maybe(T)
Wraps an object of T type or nothing.
Result(T, E)
Wraps an ok value of T type or an error value of E type.
Tuple(T1, …)
A tuple of values of various types.
  • Tuple(T1)
  • Tuple(T1, T2)
  • Tuple(T1, T2, T3)
  • Tuple(T1, T2, T3, T4)
  • Tuple(T1, T2, T3, T4, T5)
  • Tuple(T1, T2, T3, T4, T5, T6)

Trait

Trait provides a set of functions against a particular concrete type.

Trait is a similar concept as known as type class (Haskell), trait (Rust), interface (Java), and so on.

For example, an Eq(T) trait provides the following two functions :

  • bool eq(T, T)
  • bool neq(T, T)

To use Trait object, call to trait(M) that creates a concrete trait object. The parameter M is the name of the resulting concrete trait type (e.g. Eq(int)), or a name of corresponding concrete type (e.g. Array(int)).

Usecase 1. Use Eq(String) trait to test equality of two String values.

/* Eq(String) is a trait type to test equality of two String values */
Eq(String) E = trait(Eq(String));
if (E.eq("foo", "foo")) { printf("foo == foo\n"); }
if (E.neq("foo", "foo")) { printf("foo != foo\n");}
// -> foo == foo

Usecase 2. Use ArrayT(int) trait to construct, destruct, and manipulate Array(int) object.

/* ArrayT(int) is a trait type to construct, destruct, and manipulate Array(int) */
ArrayT(int) m = trait(Array(int));
Array(int) a = m.create(5);
size_t n = m.length(a); /* -> n = 5 */
for (int* it = m.begin(a); it != m.end(a); *it++ = n--)
  ;
for (int* it = m.begin(a); it != m.end(a); it++) {
  printf("%d ", *it); /* -> 5 4 3 2 1 */
}
m.free(&a);

Usecase 3. Use ItrT(Array(int)) trait to iterate each items in Array(int) container.

/* ItrT(C) is a trait type to iterate each items in container C */
Iterable(Array(int)) J = trait(Iterable(Array(int)));
ItrT(Array(int)) I = trait(Itr(Array(int)));
ArrayT(int) m = trait(Array(int));
Array(int) a = m.create(5);
size_t n = m.length(a); /* -> n = 5 */
for (Itr(Array(int)) it = J.itr(a); !I.null(it); it = I.next(it)) {
  I.set(n--, it);
}
for (Itr(Array(int)) it = J.itr(a); !I.null(it); it = I.next(it)) {
  printf("%d ", I.get(it)); /* -> 5 4 3 2 1 */
}
m.free(&a);

Generic Macros

CPARSEC3 provides also Generic Macros for easy to use various traits and containers. The name of each Generic Macros starts with prefix g_, such as for example g_array(T, ...), g_list(T, ...), g_eq(a, b), g_le(a, b), and so on.

Pros of Generic Macros
Makes it easy to use various traits and containers.
Cons of Generic Macros
Needs much more compile time / memory.

Usecase 1a. Constructs / initializes an array then iterates for each items in the array.

Array(int) a = g_array(int, 5, 4, 3, 2, 1);
size_t n = g_length(a); /* -> n = 5 */
for (Itr(Array(int)) it = g_itr(a); !g_null(it); it = g_next(it)) {
  printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
g_free(a);

Usecase 1b. Constructs / initializes an list then iterates for each items in the list.

List(int) a = g_list(int, 5, 4, 3, 2, 1);
size_t n = g_length(a); /* -> n = 5 */
for (Itr(List(int)) it = g_itr(a); !g_null(it); it = g_next(it)) {
  printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
g_free(a);

Usecase 2a. Use __auto_type and g_for(it, c) GCC extension to consruct and iterate an array.

// GCC only
#ifdef __GNUC__
__auto_type a = g_array(int, 5, 4, 3, 2, 1);
size_t n = g_length(a); /* -> n = 5 */
g_for(it, a) {
  printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
g_free(a);
#endif

Usecase 2a. Use __auto_type and g_for(it, c) GCC extension to construct and iterate a list.

// GCC only
#ifdef __GNUC__
__auto_type a = g_list(int, 5, 4, 3, 2, 1);
size_t n = g_length(a); /* -> n = 5 */
g_for(it, a) {
  printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
g_free(a);
#endif

Usecase 3a. Use g_scoped(T) GCC extension to deallocate an array automatically when leaving the current scope ; RAII (Resource Acquisition Is Initialization).

// GCC only
#ifdef __GNUC__
{
  g_scoped(Array(int)) a = g_array(int, 5, 4, 3, 2, 1);
  g_for(it, a) {
    printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
  }
  // No need to call `g_free(a)`
}
#endif

Usecase 3a. Use g_scoped(T) GCC extension to deallocate a list automatically when leaving the current scope ; RAII (Resource Acquisition Is Initialization).

// GCC only
#ifdef __GNUC__
{
  g_scoped(List(int)) xs = g_list(int, 5, 4, 3, 2, 1);
  g_for(it, xs) {
    printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
  }
  // No need to call `g_free(xs)`
}
#endif