np is different

Duck + Objects - Classes

Enter and execute code right from the convenience of your own browser. Click on the blue arrows to run any piece of example code.

try it out ⇥


Lists are containers for anything in np, and you can stuff things into them sequentially (like an array)

new myList = (: #Fluffy #Scruffy #Ginger )
print( myList )

will print out:

(: "Fluffy" "Scruffy" "Ginger" )

or by key (like a hash map):

new myList = (: contents = #cats )
print( myList )
(: contents="cats" )

or you can mix them:

new catCollection = (: contents = #cats 
  #Fluffy #Scruffy #Ginger )
print( catCollection )
(: "Fluffy" "Scruffy" "Ginger" contents="cats" )

You can get data out of lists in several ways.

-- accessing named entries with dot notation:
print( 'This contains mostly:' catCollection.contents )

-- accessing named entries with bracket notation
print( "Yes, it's full of" catCollection[#contents] )

-- accessing list items with bracket notation
print( 'First cat:' catCollection[1] )
This contains mostly: cats
Yes, it's full of cats
First cat: Fluffy

But one of the most important ways to use list items is to iterate over them:

catCollection:items { catName | print( '*' catName ) }
* Fluffy
* Scruffy
* Ginger

So what's going on there? Like all lists, catCollection has a built-in behavior called items, which takes a function as an argument. In this example, the function gets called once for ever sequential list item and outputs that item.

Lists come with all sorts of pre-defined behaviors, like for example the join function:

print( catCollection:join(' and ') );
Fluffy and Scruffy and Ginger

Separation of Payload and Behavior

Most object-oriented languages lump methods and fields together in an object. This can create problems if the two conflict. In np, everything you can stuff inside a list is considered to be data. For example, you could very well have a named entry called "each" without overwriting the each behavior.

This works in part because the behavior operator : is different from the operators you use to access the data contained in an object.

catCollection.each = 'furry'
Fluffy 1 1
Scruffy 2 2
Ginger 3 3
cats contents 4
furry each 5

By the way, note how the example above shows that the each behavior calls the receiving function print with 3 parameters: the list entry, its key, and an item counter - and standard print dutifully prints them all in a row.

New Behavior

So how can we teach objects new behaviors? Well, deep down behaviors are stored as named entries in lists, too. For example, let's give catCollection a meow behavior:

catCollection:bind(: meow = { cats | 
  print( cats:size() 'cats meow.' )
  } )
-- let's call meow:
3 cats meow.

This demonstrates how the catCollection object has no formal class, but instead responds to events that are defined in its bind list. By calling bind() without parameters, we can get that list of behaviors for any object:

print( list.keys(catCollection:bind()) )
(: "sort" "maxIndex" "join" "pop" "expand" "reduce" "map" "keys" "create" "copy" "clear" "iReverse" "size" "containsKeys" "keyCount" "toString" "get" "iSort" "iConcat" "concat" "find" "max" "insert" "items" "add" "iAdd" "bind" "min" "can" "condense" "implements" "reverse" "each" "set" )

You might notice something particular here. The explicit use of the library function list.keys() should raise eyebrows. Why can't we do catCollection:bind():keys() instead? Because objects can also have no defined behaviors at all, such is the case with the built-in behaviors for example. If you call myObject:bind(: ) on something, you too can strip it completely naked.

Mixing Instead of Inheriting

Since there is no class or prototype system in np, there is no inheritance. Nevertheless, np has some powerful mechanisms that allow you to manipulate object capabilities in interesting ways.

For example, you can add two event lists together to give an object the capabilities of both:

catCollection:bind( list << (: meow = { cats | 
  print( cats:size() 'cats meow.' )
  } ))
-- it now responds to meow()
-- and to items()

In this example, the catCollection object gets a new event list comprised of the combined list library and another list that contains the meow behavior.

You can check whether an object will respond to a given event by using the can behavior:

if( catCollection:can(#hazCheeseburger) )
  print( 'We can haz!' )
  print( 'Out of cheeseburgers!' )