realize(something new())

It’s an exciting day for Copper. Three new built-in functions have been added to give the user even more power:

  • ret_type()
  • realize()
  • new()

Each of these enhances the power of the type system in Copper by enabling greater abstraction in data flow.

ret_type()

This function returns the datatype of the object being returned from the function without having to run the function. It only works for constant-return functions, which are either (a) those that can be statically analyzed, such as a={ret(5)}, or (b) those that are created from direct assignment, such as a=5 . The current virtual machine only returns the unique return-type of the latter case, while those functions of the former case are said to return “function”.
Prior to this function, it was difficult to distinguish between functions that merely returned data and those that had complex operations associated with them. The distinguishing was usually done by calling the function and then checking its output via are_fn(). However, it may not be the desire of the user to perform such operations yet! ret_type() answers the need for no-call analysis.
ret_type() only works on functions, obviously. Any other type passed to this system function will return an error.
Potentially in the works is a related function, “inspect_fn”, which would expose more details of the function in an object.

realize()

This function can take a type-value object and create an instance of the object represented by that type, assuming that type is available internally (that is, built-in) or via a CustomObjectFactory instance shared with the Engine. It can also create an instance of an object which is of the same type as a given object.
Prior to this function, type-value objects were only used in identification of objects. This function opens a whole new domain of programming whereby memory-hogging or complex custom user objects can be spared from frequent instantiation by passing their type value around the application until an instance is needed.

new()

This function takes a string name and creates an instance of the object represented by that string, assuming that type is available internally (and enabled by a macro) or via a CustomObjectFactory instance shared with the Engine.
Prior to the CustomObjectFactory interface, realize(), and new() functions, it wasn’t possible for the virtual machine to create custom user objects via a built-in function other than copy(), which doesn’t always work as expected (because the user may make some objects monads or at least disable their copying by the virtual machine). realize() and new() are guaranteed to create unique instances of the objects or else empty functions in the case of failure.
What makes all these functions special is that now we can truly abstract data and seamlessly work with it in our algorithms until we need to actualize it.
Secondly, we can now create pairs of associated variables, one for data and one for type, providing meta-data during run-time without breaking syntax. Or we could assign a type-value as a precursor to the data required to be stored in a particular variable and even refresh/recreate/reset that data using the same function (no need for extra checks for existing data). For example:

a = type_of(0) # Type requirement #
...
a = realize(a:) # Instantiate #
...
a = realize(a:) # Reset #

Normally, you have to write a switch or a bunch of if-else cases just to cast the type.

Python Comparison

Python also has a special type-object, PyTypeObject, but I noted the implementation on the backend (https://docs.python.org/3/c-api/typeobj.html ) is overly complicated by comparison so that the grammar reads more like natural English. In Python:

print(type('string'))
# <class 'str'>

print(type(type('string')))
# <class 'type'>

print(type(str))
# <class 'type'>

Copper’s type-value object comes from type_of(), but the string version comes from typename_of().

print( type_of("string") ) # {String Type} #
print( type_of(type_of("string")) ) # {TypeValue Type} #
print( typename_of("string") ) # "string" #
print( typename_of(type_of("string")) ) # "type-value" #

In usage, the Python reads as:

print(type('string') is str)
# True

Note that the Python a boolean expression that can be wrapped as a function for more natural inlining:

def is_str(v):
    return type(v) is str

For identifying strings in Copper, you could use either are_type() or are_same_type(). The former function requires its first argument to be a type-value and the rest to be objects. The latter function compares the type-values of the given objects as-is.

is_str = [v] {
	ret( are_type(type_of("") ret_type(v)) )
}

OR:

is_str = [v] {
	ret( are_same_type("" ret_type(v)) )
}

As usual, Copper is longer, but with Copper, we can go not only from instance to type but also from type to instance, which would require a switch case in other languages. In Python, I learned you can just call the type directly (my example assumes no arguments are needed for the constructor):

a = type(0)
b = a()
print(b)
# 0

That’s very convenient albeit really hard to spot in a complex application. I like having dedicated functions better.
In Copper, to avoid instantiation of the object every time you need it for are_type(), you could save the types in advance and use the saved variables. For example:

str_type = type_of("")

I noted that Python type-objects will save nesting. So for example:

c = type(type(str))
print( isinstance(c, type(type(str))) )
# True

Unlike Python, Copper type-values only save type identifiers, not nested types. In a way, this eliminates the madness of tracking sub types. Note, it is possible to save as a type-value the type-value identifier, but it won’t contain any sub type.

One notable difference between Python and Copper is that, for Copper, the object type is not guaranteed to be a specific C++ class on the backend. In Copper, the type just means that the object adheres to a specific interface. In Python, however, it confines the object to a specific C class, and thus they say that type identification in Python unfortunately limits the flexibility of your code, whereas in Copper, it only limits it to the extent you would actually want.

Conclusion

I realize the Copper virtual machine is in “maintenance mode”, but the need for ret_type() brought it back into a short term of development.
I’m happy with these new changes. ret_type() solves an immediate problem inevitably encountered in programming with Copper, and realize() and new() open up new ways of programming.
I did not base my plans on Python, but I did learn more about Python as I did it. Notably, even though Copper seems more verbose initially, the ease of creating functions in Copper makes simplifying the necessary code a cinch. In the end, it’s no more than Python and nearly as powerful (if you consider nested types of types a desirable feature).
Seeing this implemented in Python has also made me realize these features are closer to mainstream needs than I initially imagined.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s