Segno

This is a new programming language which runs on the equally-new Sonata runtime system. Segno is architected to reuse good ideas from various programming languages to come up with a unique system that combines (in the designer's opinion) the best of all worlds. This is no small task, and the current state of the language is still in the design phase.

Mostly, you could say that Segno is an attempt to merge the best features of Haskell and Smalltalk. Obviously, these two languages are quite different, so the result will not look much like either one of them. There will be no data constructors (Haskell) nor will there be only a few hard and fast rules for operators and methods (Smalltalk). The concepts that it tries to apply are more general, but some of the more superficial similarities will occassionally shine through.

Features

Some of the interesting features that Segno will support:

Module System
A decent platform for building larger projects, with intelligent runtime support for detecting collisions, etc. Like e.g. Python, you will be able to import modules into different or the same namespace as the current module.
Easy Concurrency
Segno will use Sonata's light-weight threads which can communicate via message passing. Libraries and syntactic elements in Segno will make it easy to use such features.
Syntactic Flexibility
Like e.g. Haskell, you will be able to add new operators with varying precedences and associativity. These are always bound to existing functions, though, so anything you can do with an operator you can do with a normal function.
Curried Functions
Functions are automatically curried to allow partial application. This means that you can consruct new functions on the fly with a subset of functionality from the original, without having to construct lambda functions.
Unicode Names
You can name functions or operators anything you like, including mathematical symbols.
Object-Oriented
Segno uses a template-based object-oriented style, like e.g. Lua. This allows Segno to be very flexible and simple for building objects. There are no primitive types outside of Sonata, so Segno operates as a completely object-oriented system. When literals occur in source code, they are translated into objects of their respective types, so they can have messages sent to them just like in e.g. Ruby.

The rest of this document regards the specifics of these features, including code samples and semantic references on how Segno will behave.

Object Orientation

Segno presents to the user a fully object-oriented interface. This means that everything is an object. At the Sonata level, of course, there must be primitives; but these are provided by Sonata itself and appear in Segno as fully-fledged objects which Sonata implements behind the scenes. Examples are the Integer, Float, and Char classes. These have very few methods that can be called on them, but they are sufficient to use as bases for the rest of Segno's object-oriented code.

Defining a Class

class Monster is
    attack :: Integer
    defense :: Integer
    health :: Integer

    takeAttack :: Function (Attack -> Integer)

    attack := 10
    defense := 5

    takeAttack attack :=
        self.health := self.health - attack.damage

class Attack is
    [virtual] damage :: Integer

class Spell :< Attack is
    [virtual] element :: Enum (FIRE | EARTH | WIND | ICE | LIGHT | DARK | VOID)
    cast :: Function (Monster -> Integer)

    damage := 10
    cast target :=
        target.takeAttack(self)

Sending Messages

Calling a method on an object in Segno is called "sending [that object] a message". This is because Segno does something different than e.g. Java when invoking a method on an object. Say we have some kind of object to represent a magick spell and we want to cast it on a monster.

dragon :: Monster

dragon.takeAttack which :=
  if which.element == FIRE then
    error "immune"
  else
    self.health := self.health - which.damage
  end

fire :: Spell
fire.element := FIRE
fire.damage := 100
fire.cast(dragon)

Here we create a new monster called "dragon" and we then override the action that occurs when the monster gets hit by a spell. We then create a new instance of a spell called "fire" and set some attributes appropriately, then cast it on the dragon. Effectively, it seems like we're directly manipulating functions, but in reality we're doing something different. Notice the difference between assigning a value, which is := (meant to indicate a mutable value), and setting a function, which is = (this shows an equivalent relationship). When a field appears followed by parentheses, as in fire.cast(dragon) then the special "eval" method is executed for the object "fire" and the field "cast", which ends up running the code in "cast" with the "fire" object bound as the value of the "self" variable.

Special Types

With regard to syntactic processing, there are several special types which are handled uniquely with regard to how you specify them. Two are shown above: Function and Enum. The Function type takes a type signature as part of its specification. An Enum takes the possible values separated by vertical bars "|" which are used to form the Enum. The type checker is capable of understanding this to reason about the coverage of guard conditions and cases. Both of these special types are explored elsewhere in this document.

Enumerations

The Enum type offers enumerations to Segno. Like in most languages, enumerations provide a way to explicitly list a small amount of alternatives. As in statically-typed functional languages e.g. Haskell, the Enum type also allows the type-checker to determine coverage for condition guards and case statements. This helps prevent certain errors in code logic surrounding enumerations from falling through the cracks.

Functions

A special type in Segno, the Function has a different method of self-evaluation than other types. When a function self-evaluates, it creates an environment containing the bound variables and references in its own environment, then executes the corresponding function code within this environment. It then returns the result of the execution.

Tags

Things that appear in square brackets "[]" in declarations are called tags and are used to indicate a variety of things about that particular declaration. Certain tags apply only in certain circumstances.

TagTargetMeaning
virtualclass attributeChildren classes or instances must implement; the implication here is that inheritors must provide some kind of "default value" to fill in here.
constantclass attributeValue may not be assigned to.
purefunctionReferentially-transparent; result may be memoized automatically for performance; multiple calls in close temporal proximity may be parallelized automatically.

Operators & Functions

Like many languages, Segno lets you define your own operators; unlike most languages there are absolutely no restrictions on what you can call them (as long as they're not reserved names). To avoid utter confusion there is one rule: once an operator is defined, it cannot be redefined within the same scope. Another recommendation that would be best followed is to only use special symbols (e.g. not regular words) for operators.

All operators are merely references to actual functions. At the time an operator is defined, an arity check is done to make sure that the number of arguments taken by the referenced function is at least the number of arguments to the operator. A binary operator cannot alias a function that takes only one argument, for example; this makes no sense. The most common use case will probably be to use functions of arity two for infix operators, while functions of singular arity will be used for prefix or postfix operators.

[infix]   (==) :- equivalent
[postfix] (!)  :- factorial
[prefix]  (~)  :- not

As you can see, the mechanism for making an operator is to tag it with the kind of operator that it is, then specify the operator in parentheses, the use the alias binder :- to set the reference.

Builtin Operators

Some special operators are builtin and always in scope; they cannot therefore ever be overridden. These follow a predictable form so you will easily recognize them.

OperatorNameMeaning
:<Inheritance BindCreates an inheritance dependency from the parent on the RHS to the child on the LHS.
::Type BindDeclare that the variable on the LHS is of the type on the RHS.
:=Assignment BindOverwrite the value previous stored by the variable on the LHS with the value on the RHS. The value must be type-compatible with the variable.
:-Alias BindThe variable on the LHS now refers to the variable on the RHS.