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.
Some of the interesting features that Segno will support:
The rest of this document regards the specifics of these features, including code samples and semantic references on how Segno will behave.
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.
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)
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.
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.
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.
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.
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.
Tag | Target | Meaning |
virtual | class attribute | Children classes or instances must implement; the implication here is that inheritors must provide some kind of "default value" to fill in here. |
constant | class attribute | Value may not be assigned to. |
pure | function | Referentially-transparent; result may be memoized automatically for performance; multiple calls in close temporal proximity may be parallelized automatically. |
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.
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.
Operator | Name | Meaning |
:< | Inheritance Bind | Creates an inheritance dependency from the parent on the RHS to the child on the LHS. |
:: | Type Bind | Declare that the variable on the LHS is of the type on the RHS. |
:= | Assignment Bind | Overwrite 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 Bind | The variable on the LHS now refers to the variable on the RHS. |