def main() { print("Hello, World!") }
This is a brief introduction to the Anna programming language, written in the form of a «Choose your own adventure»-style gamebook. To get started, choose one of these links:
def Int fibonacci(Int n) { return if (n < 2) { 1 } else { fibonacci(n-2) + fibonacci(n-1) } } def main() { print(fibonacci(20)) }
The above code is a program that calculates and prints the 20:th Fibonacci number. It demonstrates a few features of the Anna syntax. The first line tells us how to declare a new function:
def Int fibonacci(Int n)
The def
keyword means that we're defining a function. Then
a return type and the name of the function we're defining. After
that follows a list of all input parameteres the function accepts
and their types. A few things should be noted:
If you want to know more about type inference, click here. Otherwise, continue reading. The next line of interest is this:
return if (n < 2) { 1 } else { fibonacci(n-2) + fibonacci(n-1) }
This line tells us a few important things about Anna:
fibonacci
function,
just write the function name followed by it's input parameters
in parenthesis, like in any C-like language.
if
clause in Anna returns a value. In computer
words, it is an expression, not a statement. Specifically, it
returns the last value of the block which was executes. In
fact, there are no statements in Anna - everything is an
expression,
return
keyword in the code above was superflous - since the if
expression was the last expression in the fibonacci function,
it would have been returned as the function output
regardless. This is in fact correct - the return keyword was
only used to make the intent of the code more clear.
That wasn't very hard. It's going to stay pretty easy for a while longer.
def main() { doggie := "woof" // Declare dogie as a variable holding the value woof print(doggie) // Print woof doggie = "mooo" // Assign mooo to doggie print(doggie) // Print mooo }
Wow. That one may have been even easier than the first one.
:=
and the initial value for the variable.
=
and the new value for
the variable.
Anna is a so called statically typed language. That means that
she insists that she must have an idea of the type of every
variable at compile time. Anna does this by checking the type of
the initial value. This is called type inference. If we don't
want the variable to have the type of the initial value, we'll
use the cast operator. In Anna, the cast operator is
called as
. It is used like this:
cat := "meow" as Any // Declare cat as a variable of type Any
The above code will declare the variable cat, which will be of type Object. Object is the root of the Anna type tree, which means that objects of any type can be cast to an Object. The initial value of the foo variable is the string meow.
So what happens if we try to perform an invalid cast? Let's find out what happens when we try to cast an Int to a String, even though an Int does not have all the members of a String.
print(1337 as String) // Invalid cast, returns null
When we try to perform an invalid cast, the null value will be returned instead. That's easy enough.
One important difference between Anna and other C-like
languages should be noted here, namely that Anna does not
consider conversion and casting to be related. In In
Anna, converting an object to a different type is done
using code
like Float::convert(someInt)
or Complex::convert(math.pi)
.
To learn more about converting, see the manual
section Casting and
converting.
Anna also has constants. Constants are like variables, except after the initial assignment, they can never be reassigned. This is how you declare a constant:
pi :== 4.0 // Declare pi as a constant with value 4.0
/* Prints<PHONE_NUMBER>000. Integers in Anna are arbitrary precision, so there are no overflows */ print(1*2*3*4*5*6*7*8*9*10*11*12*13*14*15) // Underscores in numeric literals are ignored print(100_000_000) // Hexadecimal, binary and octal integer literals print(0xdeadbeef, 0b010101, 0o777) // This is a decimal integer literal, not an octal one. Use the 0o prefix to // specify octal integer literals print(0777) // Prints 3.5. The output type of Int/Float algebra is always a Float. print(1 + 2.5) // Another way of saying 1.0 print(1_000.0e-3) /* These two lines are syntax errors - floating point numbers must have at least one digit before and after the decimal point */ print( 0. ) print( .5 )
Anna employs some clever tricks to store numbers efficiently. If you think that sounds super interesting, click here. Otherwise, keep reading.
Anna has several collection types, types that represent a series of elements. The most basic of these is the List, a collection which stores values at unique integer offsets.
// lst is a list with four members lst := ["first", "second", "third", "fourth"] /* Access list element by index. Regular access is zero based, so 0 is the first element, etc. Negative indices are offsets from the end of the list, with -1 being the last element in the list */ // Prints «first fourth» print(lst[0], lst[-1]) // Print the number of members. In this case, that is '4' print(lst.count) // Truncate the list to three elements lst.count = 3 // Increase the list length to 15. The new elements will have null as their // initial value. lst.count = 15 // Create a new list, which contains the members of the original list // in the range of 1 (inclusive) to 3 (exclusive) lst2 := lst[1..3] // Print the number of members. In this case, that is '2' print(lst2.count) // Prints the first member of lst2, which is "second" print(lst2[0]) /* Lists are suitable for use as stacks. Use the push and pop methods to add an element to the end and remove the last element, respectively, and use the empty? property to check if the stack is empty. */ // Push an element to the end of a list lst2.push("last") // Remove and return the last element of a list print(lst2.pop()) // The «empty?» property is 1 if the list is empty, and null otherwise print(lst.empty?) // Join two lists together lst3 := ["Spoon", "Banana"] ~ lst2 // Append one list to another lst ~= ["Cheese", "Eggs"] /* Lists know the type of object they store, e.g. String or Int. This is a compile time error - lst3 is a list of strings, not a list of integers */ lst3.push(5) /* When creating a list using a collection literal, Anna automatically calculates the type objects that can be stored in a list as the intersection of all types specified. In this case, the type is the intersection of the Int, String and Float types. */ weirdList := [1, "hello", 6.666] /* Use template specialization if you want to manually specify the type of object to store in a list. This is a list of objects. */ objList := «Object»[1, 3, 5, "hello", 6.666] /* The in operator checks if the specified value exists in the list. It returns null if not, and the integer offset of the item if true. In this case, «3» will be printed. */ print("hello" in objList)
empty?
in Anna? Question marks are
valid as part of variable names in Anna, so long as
they're not the first character. To learn more about legal
Anna variable names, read the
section Naming of variables and
types.Anna also has a convenient short hand syntax for when you want to assign the values of the individual items in a collection to different variables.
/* This declares that foo, bar, and baz are integer variables with the values of 1, 2 and 3, respectively. Note the use of ':=', making this a variable declaration, not a simple assignment. */ [foo, bar, baz] := [1, 2, 3] [host, port] :== server.split(':') /* This will also work as expected, though someFancyFunction must of course return a collection of Ints, or there will be a compile time type checking error. */ [foo, bar, baz] = someFancyFunction()
List assignment can be used with any iteratable data type,
e.g.[a, b, c] :== "XYZ"
.
List assignment and list declaration is implemented via macros. The assignment and declaration macros detect assignment and declaration to lists and transform the code into calls to the macros __varList__, __constList__ and __assignList__. The source code for these macros <%! toggleCodePopup("listAssign", "can be found here") %>.
<%! codePopup("listAssign", "bootstrap/listAssign.anna") %>Maps are collections that map from one object to another. The default Anna map is the HashMap.
/* hash is a HashMap that maps from one String to another String. It has four such mappings */ hash := ["first":"A", "second":"B", "third":"C", "fourth":"D"] // Print the number of members. In this case, that is '4' print(hash.count) // Hashes are not ordered, so there is no well defined way to truncate them. // For that reason, the count property for hashes is read only, making this a // syntax error. hash.count = 3 // Prints the value associated with the key "third", which is "C". print(hash["third"]) // Add another mapping hash["other"] = "unknown" // "unknown" is not in the hash, so this returns null print(hash["unknown"]) // Set the default value to "I don't know" hash.default = "I don't know" // "unknown" is not in the hash, but this time we have set // a default value for the hash, namely "I don't know". print(hash["unknown"]) /* The in operator checks if the specified key exists in the map. Note that this is different from the in operator of the List type, which searches the values, not the keys. It returns null if not, and non-null otherwise. In this case, «?» will be printed. */ print("unknown" in hash)
Function calls in Anna have a few additional options
/* In addtion to using only ordering to determine input parameters in a function call, Anna supports mapping parameters by name as well. */ math.sin(value: 1.0) /* Named parameters in function calls can significantly increase code readability for calls to function that accept many parameters. */ def myFun(Int intVal, Float floatVal, String stringVal, Char charVal) { return intVal + floatVal + stringVal.count + charVal.ordinal } // The following two calls are equivalent myFun(1, 2.0, "abc", 'g') myFun(floatVal:2.0, stringVal:"abc", charVal:'g', intVal:1) // Function parameters can also have a default value. def myFun(Int intVal, Float floatVal, String stringVal = "", Char charVal = 'a') { return intVal + floatVal + stringVal.count + charVal.ordinal } // The following three calls are equivalent myFun(1, 2.0) myFun(floatVal:2.0, intVal: 1) myFun(1, 2.0, "", 'a')
The while loop looks like in any other C-like language
while(1) { print("Are we having fun yet?") }
Anna does not have a traditional C-like for loop syntax, instead, the each method should be used. To print the values 5 to 10 in Anna, do the following:
(5..11).each(ii){print(ii)}
5..11
is a Range object,
spanning from 5 (inclusive) to 11
(exclusive). The each(ii){...}
part is a bit of syntactic suger. A macro will translate
it into a piece of code utilizing the
type Range::Iterator
. All
Anna collection types,
like List
, String
and HashMap
have an
Iterator type, which is accessible through
the iterator
property of an
instance. An iterator, like it's name implies, can be used
for stepping through the members of a
collection. Iterators are used internally not only for the
each macro, but for a series of macros
including map
, filter
and reduce
, which allow you
to write very powerful data transformation code with very
little effort. These macros can all be used on any type
that provide an iterator.
Anna will automatically determine the correct type for
the ii
variable. All
collection type objects, such as maps, lists and ranges
provide an each method. It can be used with a single
argument, such as above, or it can be used with two
arguments, in which case the first argument is the
index/key and the second is the value:
temperatures := ["Monday":31.1, "Tuesday":31.7, "Wednesday": 32.5] temperatures.each(day,value){print("Day: ", day, ", temperature: ", value)}
Ranges are objects that represent a finite or semi-finite collection of evenly spaced integers.
// r is a range from 6 (inclusive) to 10 (exclusive) r := 6..10 /* We can specify a step on a range. The step on this range is 6, making the numbers it contains 5, 11 and 17 */ print(5..20|6) /* Use ... instead of .. to specify an semi-infinite range. This is the range containing all positive numbers. */ 1... // And this is the range of all positive, odd numbers: 1|2... /* Instead of using the .. and ... sugar syntax, we can use the Range constructor directly. It takes three arguments, first, last and step. This is often useful when writing macros. */ r := Range(6, 10, 1) /* The most common use for ranges is to slice lists and strings */ // This outputs [2, 8, 6, 1, 1, 4] print([9,2,3,8,1,6,8,1,5,1,9,4][1|2...]) // This outputs «banana» print("abrainbadnoah"[1|2...])
In addition to an each method, collection types offer a wide variety of functional programming methods, like map and filter.
// Prints 2, 3 and 4, on separate lines [1, 2, 3].map(i){i+1}.each(i){print(i)} // Filters out the even numbers for the list [1, 2, 3, 4].filter(i){i ^mod 2 == 0}
Anna does not have so called type coercison. There is exactly one value in the Anna language which is considered to be false, and that is the null object.
if(?) { // ? (aka null) is the only false value in Anna. // This will _not_ get executed } if(0) { // The number 0 is an Int objects, and all Int objects are considered // to be true, so this will get executed } if([]) { // En empty list is also true, so this will also get executed } if(false) { // This is likely a syntax error -there is no 'true' or 'false' constant // defined in Anna. }
/* The code below tries to convert the first command line argument of the program to an integer and assign it to the laps variable. If that fails, laps is instead assigned a default value of 5. Note that the type of an or expression is the intersection of the two types that are checked, in this case Int and Int. In other words, the laps variable will have the type Int. */ laps := Int::convert(system.argument[1]) or 5 /* The code below checks if the first command line argument is '--level', and if so, sets the variable 'level' to the second command line argument. The return type of an and expression is the type of the second argument, in this case Int. If no level is set or if the conversion fails, an additional or expression sets the default to 2. */ level := (system.argument[1] == "--level") and Int::convert(system.argument[2]) or 2
The null object (which is written ?
) has all
members, and their value is null. Calling any method on
the null object will return null. Calling a method or
accessing a member on the null object is not an
error. Many functions in the Anna standard library also
return null when given a null argument.
// This will print «nullnullnullnullnull» print(?, 1+?, (? as String)~ "hello", "hello" ~ ?, (? as Int) + 1 )
/* Previous examples have shown us that this is the standard way to define a function */ def myFunctionDecl(Int arg) { return arg+1 } /* By skipping the name of the function, we can create an anonymous function, that is, a function not bound to a constant name. We can do this when we simply want to send a function as a parameter to another function call: */ print(functional.reduce([1,2,3,4], 0, def (Int a, Int b){a+b})) /* Creating a named function is really just a short hand for doing two things, namely creating an anonymous function and binding it to a constant. We can do the two things separatly with identical meaning, like this: */ myFunctionConst :== def (Int arg) { return arg+1 } /* Sometimes, it is useful to bind a function to a variable instead of a constant: */ myFunctionVar := def (Int arg){ arg+1 } /* It (obviously) doesn't matter if you're calling a function variable or a function constant. This will print 8. */ print(myFunctionVar(7)) /* But a variable can be reassigned. This will print 7, because the function variable has been reassigned. */ myFunctionVar = def (Int arg){ arg } print(myFunctionVar(7))
/* Use the class keyword to define your own types */ class MyClass { // This declares that the type has a member of type Int with the // name myMember and the initial value 7. myMember := 7 // This declares that the type has a method named myMethod def myMethod() { // If an identifier can't be found in the scope of the local // function, the object scope is automatically checked. print(myMember) // The identifier «this» automatically exists and points to // the current object print(type(this).name) } } /* Call a type in order to create a new object instance of that type. This calls the constructor, which sets the value of myMember to 7. */ myInstance := MyClass() // This will print «7» and «MyClass». myInstance.myMethod() // Member variables are public by default. // For now, there is no support for non-public members. myInstance.myMember = 9 // This will print «9» and «MyClass». myInstance.myMethod()
/* Properties are method calls that look like member variables. For example, the count property of the List type returns the number of elements in the list. */ myList := [1, 2, 3] print(myList.count) // This prints 3 myList.push(0) print(myList.count) // This prints 4 /* Some properties are read only, like List::empty?, but others can be written to as well as read. MutableList::count can be written to. */ myList.count = 2 // This truncates the list print(myList, myList.count) // This prints [1, 2] 2 myList.count = 5 // This will null-pad the list to five elements. print(myList.count) // This prints 5 /* Defining you own properties is straight forward. Define a getter and optionally a setter method, both with suitable parameter lists. Then declare a member variable and add a property attribute to it, giving the name of the getter/setter as parameters. */ class PropertyExample { propValue := 0 def myGetter() { propValue } def mySetter(Int val) { print("Setting myProperty to %" % [val]) propValue = val } var Int myProperty (property(myGetter, mySetter)) } /* For read only properties, there is a slightly less verbose short hand. Simply define a suitable getter method and add the property attribute directly to the getter method like this: */ class PropertyExample2 { def myProperty() (property) { 3 } }
// When defining a type, members can be set as static using the // static attribute class MyClass { var Int staticVar (static) } // Static variables are shared between all instances of an object, // so this will print '7' mt1 := MyClass(), mt2 := MyClass() mt1.staticVar = 7 print(mt2.staticVar) // Static members can be accessed by using the :: operator MyClass::staticVar = 9 // If a method is accessed using the static member access operator, the unbound // version of the method is returned. The unbound method does not have an // implicit «this», so it needs an additional input parameter, which is the // first parameter in the list. myList := [] // This is identical to myList.push(7) MutableList::push(myList, 7) // Methods can be defined as static, just like variables class MyClass { def Object staticMeth() (static) { // In static methods, this does not point to the current // object instance (there is none), instead this points // to the type. // This might seem meaningless, but can actually be quite useful // in static methods of subclassed types. print(this, this::staticMeth) } } def main() { MyClass::staticMeth() }
Converting is the act of taking a piece of data represented as one type of object and creating a new object of a different type that represents the same piece of data. Conversion can be lossless (such as when converting an Int to a String) or lossy (such as when converting a Float to an Int).
/* All conversions in Anna are by convention done using the static convert method. */ sevenString :== String::convert(7) sevenFloat :== Float::convert(sevenString) sevenComplex :== Complex::convert(sevenFloat)
Note that conversion must not be confused with casting.
Some objects in Anna are mutable, i.e. they can be changed
after they where created. Others are imutable. It
sometimes makes sense to provide both a mutable and an
imutable version of the same basic type. Anna does not
currently support automatically stripping mutating methods
from a mutable type to create an imutable version,
instead, two separate types must be created. When creating
such a pair of types, the Anna convention is that both
types of objects should have two properties,
named freeze
and thaw
, that can be used
to obtain a mutable or imutable object with the same
content.
/* The String literal is of the ImutableString type, which is imutable. This is a compile time error, because ImutableString does not have a __set__ function. */ imutableString :== "foo" imutableString[0] = 'F' /* Use the thaw property to get a mutable version of the String. This will work as expected. */ mutableString :== "foo".thaw mutableString[0] = 'F' /* You can use the ~= append operator both on mutable and imutable strings. When used on imutable strings, every call to ~= will create a new ImutableString. When this happens in e.g. a loop, it can lead to a lot of copying, which can make your code slow. In such situations, you can speed up the program by making sure the String you're appending to is mutable: */ result :== "".thaw someCollection.each(item){ result ~= doSomeProccessing(item) } /* The List collection literal is mutable, so this is ok. */ mutableList :== [1,2,3] mutableList[0] = 0 /* MutableList.freeze returns an ImutableList, so this is a compile time error. */ imutableList :== [1,2,3].freeze imutableList[0] = 0 /* The thaw property of a mutable object returns itself, as does the freeze property of an imutable object. */ useless :== "useless".freeze alsoUseless := ["useless"].thaw /* Note that when thawing an already mutable object, the convention is to return the object itself, not a copy. This will print «I'm a little tea» */ thing :== "tea" thing2 := thing.thaw thing2 ~= "pot" print("I'm a little %()" % [thing])
For more information on mutability in Anna, see the Mutable section of the manual.
/* Templates are specialized using «». This creates an empty list of Int items. */ lst :== MutableList«Int»() /* Explicitly specializing a template is superflous when all template parameter types can be calculated from the constructor call. This will also create a list of Int items. */ lst2 :== MutableList(5,6,7) /* The specialization of collection literals can be specified */ // Empty list of integers lst3 :== «Int»[] // Empty HashMap mapping from String to Char hash :== «String, Char»[] /* A simple example of how to create your own template type. Foo uses Int as the default type for the %A template parameter. All templated arguments must be prefixed with a '%' symbol. Template arguments that are type names must begin with an upper case letter. */ class Foo (template(%A:Int)) { def %A bar(%A a, %A b) { a+b } } def Int main() { foo :== Foo«Float»() print(foo.bar(3.0,4.0)) } /* Template specialization parameters aren't restricted to being types. In this example, we specify which member method should be called by using a template. */ class Foo (template(%op:__add__)) { def Int bar(Int a, Int b) { a.%op(b) } } def Int main() { foo :== Foo() print(foo.bar(3,4)) boo :== Foo«__sub__»() print(boo.bar(3,4)) } // On some keyboard layouts, «» may not be easily accessible. And If you're // not using a unicode character set, «» may not even be available to you. // As a fallback, Anna also supports using <<>>. <<Int>>[]
TODO
Sometimes, one has to write a large bulk of nearly identical code expressions. I these cases, it is often possible to use the expandCode macro to reduce the amount of repetition.
// This is a boring piece of code first :== getNextValue(someValue, someOtherValue) second :== getNextValue(someValue, someOtherValue) third :== getNextValue(someValue, someOtherValue) fourth :== getNextValue(someValue, someOtherValue) fifth :== getNextValue(someValue, someOtherValue) // This is exactly equivalant to the above, but less redundant expandCode( %var :== getNextValue(someValue, someOtherValue), var: [first, second, third, fourth, fifth])
expandCode
is just a regular
Anna macro. You can <%! toggleCodePopup("expandCode", "check out the source code") %>.
The Anna version of the classical C switch statement contains some changes:
break
keyword is
needed at the end of a case - execution never continues
into an alternative case.
cases(someCollection)
keyword.
case(varName as
SomeType)
,
where SomeType
is the
name of the type you want to test for,
and varName
is a variable
name. If the test succeeds, the
variable varName
will be
declared to be of
type SomeType
and hold
the value from the original switch condition.
/* Switch containing Int:s */ (0..10).each(foo) { switch(foo) { case(2,4,6,8) { print("Even") } case(7) { print("Seven") } cases(1..5|2) { print("Odd") } default { print("Whatever") } } } /* Switch containing strings */ ["abc", "a", "aa", ?, "bbb"].each(bar) { switch(bar) { case("abc") { print("abc") } case("b", "bb", "bbb") { print("Bunch of b:s") } cases(["a":?, "o":?, "u":?, "e":?, "i":?, "y":?]) { print("Vowel") } default { print("Don't righly know!") } } } // Loop over a series of items and classify them using their type [3, 4.0, "foo", 'g'].each(item) { switch(item) { case(i as Int) { print("It is an Int, its square is ", i*i) } case(f as Float) { print("It is a Float, its square root is ", math.sqrt(f)) } case(s as String) { print("It is a String, its length is ", s.count) } default { print("Don't know, don't care") } } }
switch
is just a regular
Anna macro. You can <%! toggleCodePopup("switch", "check out the source code") %>.
/* The struct macro is used to easily create an object to store a specific list of fields in. All fields are typed based on the input provided to the struct macro. */ myVar :== struct(someNumber: 7, someString: "hello", someList: [1,2,3]) // Prints «10 helloworld 2» print(myVar.someNumber + 3, myVar.someString ~ "world", myVar.someList[1])
struct
is just a regular
Anna macro. You can <%! toggleCodePopup("struct", "check out the source code") %>.
enum MyEnum { foo bar = 32 baz } enum MyEnum2 { aaa=-7 bbb ccc } def main() { // This will print the names of these three enum values, namely «foo bar baz» print(MyEnum::foo, MyEnum::bar, MyEnum::baz) // This will print the actual integer value, which is 0 print(MyEnum::foo.__value) // MyEnum2 starts at offset -7, so this will print «-7 -6 -5» print( MyEnum2::aaa.__value MyEnum2::bbb.__value MyEnum2::ccc.__value ) }
enum
is just a regular
Anna macro. You can <%! toggleCodePopup("enum", "check out the source code") %>.
/* A simple macro that takes a string literal and reverses the order of the characters in it */ macro reverseStringLiteral(node) { /* Check that we have exactly one argument to the macro, otherwise set the error flag, print an error message and return a null literal. */ checkCallCount(node, 1) /* Check that node[0] is a StringLiteral ast symbol, otherwise set the error flag, print an error messagge and return a null literal. */ checkNodeType(node[0], StringLiteral) /* Return a new StringLiteral, that is the reverse of the original literal */ return StringLiteral(node, (node[0] as StringLiteral).payload[-1|-1...]) } /* Use the expand call to tell the Anna interpreter that you wish to use the macros of a library for macro expansion. If the above macro was defined in the file myStringMacro.anna, then it could be used like this in another file: */ expand(myStringMacro) def main() { // Prints olleH print(reverseStringLiteral("Hello")) }
The macro call is just a wrapper around the def call to shorten the code a bit. We could have written the above macro using the following def syntax:
def parser.Node reverseStringLiteral(parser.Call node) { use(parser) ... }
TODO
Anna is a statically, structurally typed computer language that supports LISP style macros, functions and types as first class citizens, closures and continuations; it has a C-style syntax and its aim is to combine the expressiveness and power of LISP with the type safety, familarity and control of Java.
Note the use of a semicolon to combine two separate sentences into something that works around the originally stated limitations and still manages to be perfectly readable. That is the essence of Anna.
Sounds good? To learn more about Anna, check out the following sections:
Anna is a computer programming language. That is, it is a language designed to be written and read by humans, but which contains a series of instructions designed to be executed by computers. There are many programming languages, all with their own strengths and weaknesses.
That should give you an idea about Anna. If you want more in depth information about some aspect of Anna, follow one of the links below.
Anna is a statically typed language with a front end that allows you to write your code with a C-like structure, e.g. using infix operators, braces and brackets. What sets Anna apart from other such languages is it's support for LISP-features such as LISP-style macros, continuations, closures and anonymous functions.
 At first glance, Anna feels very familiar to Java and C# programmers, and coders of C, C++ and other C-like languages should pick up the changes relatively quickly as well. Anna does have a few nifty short cuts to save a few keypresses, including:
foo :=
bar()
. Note the use of := instead of plain old
=. This makes the above expression into a variable
declaration, not an assignment. The correct type for the
foo variable in the code above is determined at compile
time by inspecting the return type of the foo
function. In much the same way, Anna can usually
determine the return type of functions. Go and C#
programmers should recognise this feature.
foo = if (a>3) {3} else
{a}
. If a function reaches the end of it's block
without encountering a return statement, the output from
the last expression of the function is returned.
This is all fine and good, but hardly something that makes it worth ones time to switch to a whole other programming language. What really sets Anna apart is the level of metaprogramming it allows. Anna allows you to redefine the very building blocks of the language. Use/with statements, exceptions and loop constructs can all be created or altered in order to extend the language in any way you choose. Even better, such extensions are written in the language itself, no C++-style separate template metaprogramming language is needed.
There are two fundamental building blocks in Anna that allow you to redefine the language. They are macros and continuations. We'll explain both brifely. C and C++ coders may think to themselves that they know macros, and they really aren't that great. In one sense they'd be correct, because C-style macros really aren't that great. Ignore C macros, lets instead talk about Anna style macros, because they're actually pretty neat. But first, just a little bit of background.
Pretty much all compilers will read a source file into some kind of tree of nodes called an abstract syntax tree, or AST for short.
Every node in an AST is a regular Anna object, and the
compiler can alter these nodes as it sees fit. That's
pretty nifty, but how does that tie in with us? Anna has
two keywords for defining a function,
namely def
and macro
. They work pretty
much the same, they both let you define a piece of code
with a few input parameters and a few output
parameters. There are two differences between the two,
firstly that macros are run at compile time, and secondly
that the input and output of the macros is an AST. The
implications of this may not be obvious at once, so we'll
consider a few examples. First, look at the code snippet
below:
macro replaceObjectWithInt(node) { node.replaceChildren(child) { id :== child as Identifier if(id and id.name=="Object") { return Identifier(id, "Int") } child } return node }
What does the above code do? If you can't guess, the
correct answer is that it walks the entire AST and
replaces all identifiers that have the
value Object
with a new
identifier with the
value Int
. Instant
templating! Of course, the Anna standard library
already has a very nice templating system, so
there is no need to write your own, but the possibility to
make that type of additions to the language is actually in
your hands.
The most common reason for using macros is to construct sublanguages, also called domain specific languages. The Anna front end is pretty loose about what input it will accept - pretty much anything that can be reasonably parsed into an AST is allowed. That opens up the possibility to create macros that parse things using a completely different syntax. For example, one could write a macro that allows you to create date objects using the standard ISO data format:
birthday := isoDate(1978-05-20, 12:34:00)
It is possible to create a list search interface like LINQ in Anna, but unlike LINQ, it is not a special case grafted onto the compiler. Anna provides a general purpose framework that allows anyone proficient in Anna to create libraries that do the same thing LINQ does, but better.
But enough talk about what could theoretically be done with macros - it turns out that pretty large parts of Anna already are implemented that way. Switch expression and the enum type are macros. As is expandCode.
This paragraph is for C and C++ coders that know what a longjmp is. If you don't, feel free to skip this paragraph. So, you know what a longjmp is? Congratulations! A continuation is exactly like a longjmp, except that the restrictions placed on longjmps are removed. Specifically, longjmp:s only allow you to «rewind» the stack and continue execution further down on the function stack, whereas continuations allow you to jump up, down or sideways into a whole other branch of the stack - as many times as you like. That's it. Nothing more. Simple. Because continuations can more or less arbitrarily control program flow, they can be used to easily implement various high level features like exception handling and green threads. Feel free to skip the next paragraph, which says the same thing with smaller words.
Continuations are a way to explicitly control the location that a function will return to when it finishes.
Very few Anna programmers will directly use continuations on a regular basis. Most will stay away from writing their own macros as well. What makes these features exciting is that they allow other programmers to evolve the languages. Changes that traditionally require a deep understanding of the compiler and that can only be implemented by a small handful of people are suddenly within the grasp of any language guru. (Not that there are many Anna gurus around at the moment...)
This concludes the Anna overview for people familiar with the C class of languages. You now need to decide where to go next:
In daily use, Python and Ruby are probably the languages that most resemble Anna. Anna code looks a fair bit like C, which means the code may not look like Python or Ruby, but once you've gotten used to it, the way the language feels and works should be very familiar.
def Int fibonacci(Int n) { return if (n < 2) { 1 } else { fibonacci(n-2) + fibonacci(n-1) } } def main() { print(fibonacci(20)) }
Like Ruby, but unlike Python, Anna does not have statements, only expressions. A function declaration is an expression that returns a function. Thus, you do not need Pythons's rather limited lambda expression to create anonymous functions:
functionExpectingAFunctionAsAParameter(def (){print("I am happy")})
Much like in Ruby, there is a very simple shortcut for throwing in a block of code as an additional parameter in Anna. If you simply append a code block afer a function call, Anna will interpret it as a function parameter. The above code example could just as well have been written like this:
functionExpectingAFunctionAsAParameter {print("I am happy")}
Another way in which Anna is like Python and Ruby is the convenient support for functional programming.
someList = [1, 2, 75, 6, 7] // Print square off all numbers in someList print(someList.map(val) { val*val }) // Print the even numbers from someList print(someList.filter(val) { (val ^mod 2) == 0}) // Print the first even number of someList print(someList.find(val) { (val ^mod 2) == 0})
Blocks of code, by the way, are anonymous functions that take 0 parameters.
So. Enough dodging, lets address what is likely to be the major cause of concern for Ruby- and Python lovers. Static typing. In Anna, you will sometimes have to do slightly more typing. It's true. Anna does its best to figure out types for you using type inference, and when it can, you don't have to tell her, but there are still situations where you have to talk about types. Lets break it down:
The upshoot of this is that you have to do significantly less type checking when validating your input arguments (and you do validate your input arguments, don't you?), regaining you some of those lost key strokes. You also get better documantation and ahead of time code checking - some silly mistakes can be caught earlier in the development process in Anna.
Static typing does catch some bugs at compile time that aren't tiggered until you happen to run a specific function with a specific combination of parameters in dynamic languages. That is a fact. Static typing also involves more typing. Another fact. It is hoped that the «smart» static typing of Anna is the optimal tradeoff - you get most of the safety and a more tolerable amount of typing than in other statically typed languages.
The practice of duck typing, e.g. not checking for specific function types when validating the type of an input parameter, but only checking that the required members are present, is increasingly popular in modern dynamically typed programming languages. This technique makes code significantly more reusable, and can also to a degree decouple modules from each other. On the surface, duck typing seems to be an idea that can only work in dynamically typed languages, but statically typed languages like Anna can actually implement a close equivalnt, something called structural typing.
Structural typing means that if a function expects a parameter
of type A
, and type B
contains all the
same members as A, and all the members have compatible
signatures, then type B can be used as input to the function,
regardless of if B
actually inherits from
type A
or not.
This concludes the Anna overview for people familiar with scripting languages. You now need to decide where to go next:
Most languages, including Python and Java, have operations
that let you check if an object explicitly states that it
implements some interface. You have, for example,
the Comparable
interface in
Java, which defines a set of methods that allow you to
compare one object to another. The realization that it
doesn't, or at least shouldn't, matter if an object
explicitly states that it implements a specific interface,
so long as it actually does implement it, has struck a
great number of people over the ages. In python, it has
become popular to avoid
the is
operator to perform
type checking, and instead simply check if the relevant
fields and methods are present in an object. This idea is
called duck typing, and while the idea and its usage
predates Python, the concept of duck typing has been
widely adopted as best practice by the Python community.
Anna takes the concept of duck typing and runs with it. It
is not possible in Anna to check if an object inherits
from a specific type. There is simply no inheritance
information available. You can _only_ check if an
object A
is compatible with
a type B
using duck
typing. It is also trivial to perform operations on types
in order to type templates dynamically. For example, say
you want to test if the
object someObject
has all
the members common to both
the String
and Int
types. That is
easily done using this code:
if(someObject is String.intersect(Int)) { ... }
Anna makes it similarly easy to filter out a subset of members from a type, create the union of two types, etc.
In order for structural typing to be viable in a statically typed
language, it must be easy to create new types on the spot. A very
simple example of this is the or
macro. Take the
following snippet of Anna code:
foo :== 1 bar :== "1" var baz = foo or bar
The or operator in Anna will return the first value (foo)
if it is true, otherwise, bar will be returned. This means
that the return type of foo or
bar
is the intersection of the types of the
respective arguments, in this case, the intersection of
the types Int
and ImutableString
.
Anna is a statically typed language. That means that before a block of Anna code is compiled by the computer, every variable declaration and every object member will have a known type associated with it. This does not, however, mean that you as a programmer must tell the interpreter about all of those types. Anna goes out of her way to find situations where there is no ambiguity as to the type of a variable, and to relieve you of the burden of explicit typing in such situations. Consider this trivial example:
var Int i = 7
The above is an Anna style variable declaration, the
variable i
is declared to
be of type Int
, and is
given the initial value of 7. Now, the above is a
perfectly valid Anna expression, but it is not the
simplest way possible to express the above. You see, the
numeric literal 7
is known
by the interpreter to be of the type Int. The interpreter
can use this information to guess the type of the variable
declaration. Therefore, Anna also has an alternative,
simpler syntax:
i :== 7
This only saves you seven key strokes in the example above, but consider for example templated types, such as a map with String keys and Float values:
var Map«String,Float» map = ["Mon":0.0, "Tue":1.1] map := ["Mon":0.0, "Tue":1.1]
Suddenly, we've cut the number of key strokes nearly in half, without sacrificing readability or type safety.
Variable declarations aren't the only place where the interpreter can figure out the variable type for you:
/* * Function with return type implied by the type of value it is returning */ def next(Int in) { return in+1 }
Anna, much like LISP, allows you to define functions that accept a tree of nodes representing a program code snippet as an input, and returning another code snippet as its output. This is in stark contrast to regular functions, that accept the evaluated results of a code snippet as its input and output. For example, consider the following snippet:
TODO
Anna does not have exceptions. It would be easy to implement exceptions using continuations, but they are considered bad coding style in Anna. The perception is that exceptions make your programs unpredictable and often causes error handling, which is the least well tested part of most application, to use completely different code paths that often result in unintended bugs. In its stead, Anna allows you to record all errors that occured during the execution of a function, and deal with them where convenient. Instead of the classic try/except block of other languages, Anna has a collect block that in many ways behaves the same, but with an important difference. This is how a collect block will usually look like:
collect { /* Some code that may cause various error conditions */ ... }.each(err){ switch(err) { case(ioErr as IOError) { /* If we encounter an IO error, ignore the rest of the error * list, but continue running the program */ print("There was an IO problem. Details:\n", err.message) break } case (pErr as PermissionError) { /* If we encounter a permission error, exit the program */ print("Access denied! Shutting down") exit(1) } default { /* If we encounter any other error, continue processing the * error list */ print("Unknown error. Details:\n", err.message) } } }
The collect
call will save
any errors that occur while executing its body, including
any errors occusing in functions called by the body at any
level of nesting, into a list of errors. Once the code has
finished, the list of errors is iterated over and handled
by the each
loop.
Curious about the switch expression, and how it was used for trying to cast a value to different types in the above code? You might want to read up on the switch statement examples.
How is this different from exceptions? Exceptions can skip over parts of your code in very unpredictable ways. Whenever you allocate some form of resource, you need to use try/finally to make sure it's released, which is very error prone. When an error happens in Anna, it is recorded, but the code will keep in running. In Anna, you don't need to code defensively around the possibility that some code you called might throw an exception. You do't need to be afraid thet there is an undocumented exception that might be thrown by some library code you're using causing a program flow that you hadn't thought of and results in rare and unpredictable bugs. Instead, code that errors out be recorded, and will then usually simply return null. And because using null objects does nothing in Anna, you can usually make your code exit cleanly and automatically on errors without even trying. For example, look at this trivial example:
var f = File("foo.txt").open() while(f.readLine()) { ... } f.close()
If the file can not be read, either because it doesn't exist or because you lack the permissions, the open call will fail and return null, and f will be assigned null. Calling readLine on the null object will again return null, ending the loop at once. Calling close on the null object will also have no effect. So because of how null handling works in Anna, the above code will handle any error without forcing you to write any error handling code.
This section gives an overview of the various Anna features that, while important, did not warrant an entire section of their own.
The null object in Anna, which can be accessed through
the ?
keyword, is a very
odd object. It masques as every interface, and calling any
method on it will without exception have no side effects
and will return the null object. In other words, it is
perfectly ok to call ? + 1
,
it will not crash or throw an exception, and the result
will be null.
For more information about error handling in Anna, and how the null object ties in with it, read the section titled Handling errors with no exceptions.
There is no separate Boolean type in Anna. There is exactly one object that takes on the responsibility of representing the false state, and that is the null object. Any other object, including the empty list, the number zero and the text strings "null", "?" and "0" all count as true in the eyes of if expressions, while loops and any other statement that expects a yes/no answer. Null is the new no.
On a semi-related note, there is a naming convetion in
Anna that all variables, constants and members that hold
boolean values should have a name ending with a question
mark, such as empty?
, property?
or teapot?
. For more variable naming
conventions, read the
section Naming of variables and
types.
In Anna, if you enclose a list of expressions within curly brackets, you will define a block.
// Define a block { print("This is a block, and it will not get run") }
Unlike other C-based languages, the block does not by default get run, so the code above does not actually print anything. Instead, an anonymous function is returned by the block expression, that can then be further manipulated:
// Define a block and explicitly call it { print("This block will be run because it is explicitly called") }() // Define a block and assign it to the variable myBlock myBlock := { print("This is yet another block") } myBlock()
If you've read the section about functions in Anna, you may be wondering what the difference between a function and a block is. The code above could just as well be written using functions:
// Define an anonymous function and explicitly call it def () { print("This function will be run because it is explicitly called") }() // Define a named function def myFun() { print("This is yet another function") } myFun()
The answer is that blocks are just syntactic sugar for defining extremely simple functions, and internally, they are exactly the same thing.
// These two lines are exactly equivalent, but less writing is required // when using the block syntax {print("hi")} def () (block){print("hi")}
You may have noticed that the function definition that is
equivalent to the block definition snuck in an attribute
in the function definition,
namely block
. So what does
the block
attribute do? The
answer is that the return
expression will return from the innermost
function that is not a block.
So there you go. Anna has two different syntaxes for
defining callable sequences of code, called blocks and
functions. Blocks use a very terse syntax, but
functions are a lot more expressive in that you can pass
parameters to them, you can control how
the return
expression
works, you can give them a name, etc. Underneath, they are
completely equivalent and rely on the same implementation,
and functions are in fact a strict superset of the
functionality of blocks.
There is one more thing that one should know about blocks. When calling a function, a code block coming right after the parameter list is interpreted as an argument to that function:
// These two lines are completely equivalent someFunction(1, 2, 3, {1+1}) someFunction(1, 2, 3) {1+1} // These two lines are also completely equivalent someOtherFunction({1+1}) someOtherFunction {1+1}
What is the point of this alternative syntax? Here are some examples of it in use:
if(someCheck()) { doSomething() } while(someCondition()) { doSomething() } switch(x) { case(0) { ... } ... } (0..10).map(idx){0.0+x}.each(val){print(idx)}
As you can see, this syntax (which has been adapted from Ruby), allows you to pass on a piece of code to another function using a rather terse syntax that does not force you to enclose huge swaths of code in multiple sets of parenthesis.
The main benefit of this type of code is how easily it allows you to extend and repurpose Anna for new problem domains. Imagine that you're writing a database API. You might want a simple way to define database transactions. Consider the following piece of code:
class Database { ... def transaction( def Object code()) { connection.beginTransaction() code() connection.commit() } }
The above code would allow you to use the following simple syntax for performing a group of queries witin a transaction like this:
// Connect to the database db := Database(...)  These are all part of the same transaction db.query(...) db.query(...) db.query(...) }
Anna has a small number of special operators that allow you to implement some of the most common language operations with a slightly terser syntax that most people find easier to parse. These operators include comparison, assigment, basic aritmetic, string formating, list slicing and a few more. The complete table of operators and their precedence is listed in the table below. Most of these operators are overloadable. The exceptions are the '!' not operator, the '.' member access operator and a few more. The rest can be overloaded by defining methods with special names. For example, when the '+' operator is used...
In addition to overloading +, - and various other operators, Anna also allows you define you own generic operators. These operators all start with the «^» symbol, followed by an identifier:
a :== 4 b :== 7 print(a ^bitand b)
The above example shows usage of
the ^bitand
generic
operator of the Int class, which will perform a bitwise
and operation. These «generic operators» are actually just
a different syntax for calling a method with one
parameter. To define your own generic operator, simply
define a method named like the operator. An example:
class Color { function blend(Float alpha) { ... } } var col = Color() col ^blend 0.1
| Operator symbols | Operator method names | Notes |
|---|---|---|
++, --
|
__next__, __prev__
|
|
*, /, %
|
__mul__, __div__, __format__
|
|
+, -, ~
|
__add__, __sub__, __join__
|
|
|
|
__pair__
|
__pair__ can not be overloaded.
|
^xxx
|
xxx
|
any identifier can be used as an operator using the generic operator syntax. |
<, >, ==, !=, <=, >=, in
|
__lt__, __gt__, __eq__, __neq__, __lte__, __gte__, __in__
|
While all of these operators can be overloaded, it
is strongly discouraged to overload any except
for in . Instead,
override the __cmp__ method, used to compare two
objects and return a negative Int, zero or a
positive Int depending on if the first number is
smaller, equal to, or greater than the second one,
respecitvely.
|
or, and
|
__or__, __and__
|
__or__ and __and__ can not be overloaded.
|
=, ~=, +=, -=, :
|
__assign__, __append__, __increase__, __decrease__, __mapping__
|
__assign__
and __mapping__ can
not be overloaded.
|
The Sugar layer has a small number of reserved words,
including 'def', 'var' and 'return'. Because macros can
never be redefined, the builtin collection of macros more
or less count as additional reserved words. It may seem
scary that modules you import can add reserved words to
the language - what if a module you rely on suddenly adds
a macro named 'pie'? Suddenly, you'll have to edit all
your source code to remove any variables and functions
named pie. And given how delicious pie is, that is bound
to take an insurmountable amount of time. For this
reason, great care must be taken when using
the expand
expression to
import macros.
Like C and friends Anna uses /* to start a multiline comment, and */ to end it. But unlike C and friends, comments can be nested inside each other. This makes it trivial to comment out a large chunk of code, even if it happens to contain multiline comments.
/* /* This is a comment */ thisIsAComment() */ thisIsCode()
Some objects in Anna are mutable, i.e. they can be changed after creation. Mutable objects are sometimes significantly easier to work with, but the fact that an imutable object can't change unexpectedly can often be a significant boon to debugability. Imutable objects also offer important opportunities for optimizations.
Anna has both mutable and imutable versions of several basic types. The table below shows these types. Anna also has an intersection of these types, which is suitable to use when you're taking a value as input into a function and don't care if you're getting the mutable or the imutable version of the object.
| Mutable version | Imutable version | Intersection |
|---|---|---|
MutableList
|
ImutableList
|
List
|
MutableString
|
ImutableString
|
String
|
It is sometimes easy to confuse the concepts of mutability and of constants in Anna. They might on the surface seem to be more or less the same, but in actuality, they are different concepts that complete each other.
So what does this mean in practice?
Whenever suitable, make sure to use contants instead of variables, and to use imutable versions of types instead of mutable. Doing so will make intent of the code clearer, debugging easier and might even make the application run faster.
Many statically typed computer languages allow you to define multiple methods with the same name but different argument lists. The compiler will at compile time figure out the most suitable version of the function and make sure that version is called. The benefit of this is obvious, functions that perform the same operation using slightly different inputs can be named the same thing, which avoids having to come up with arbitrary minor differences in function names. There are drawbacks with allowing overloading of method names, though:
clyde
, then code
like myVar := clyde
becomes ambigous. We need a parameter list in order to
unambiously determine which
clyde
function we want.
Anna works around these issues by introducing a second type of name for a function. All type members and stack values, including functions and methods, have exactly one canonical name, which is unique within a specific namespace (that is, the name is unique within the stack frame or type it is a member of). This is the «true» name of the function and must be used in any situation where an unambigous name is needed. But functions and methods can also have an arbitrary number of aliases, specified using the alias attribute in the function declaration. Such aliases are also searched when performing lookup for function and method calls. If multiple function matches are found, the function with the most compatible type signature is chosen. Aliases are only used when calling a function or method.
Note:
def outerFunction(Int value) ( alias(myAlias) ) { print("outer %" % [value]) } { def innerFunction(Object value) ( alias(myAlias) ){ print("inner %" % [value]) } /* This will call outerFunction, which will print 'outer 7' */ myAlias(7) /* This is an error - aliases can only be used when calling a function. They are ignored when performing regular variable lookup. */ print(myAlias) }()
In most C-like languages, the operation of casting a value
to a new type (an operation which does not create a new
object) and the operation of converting a value to a
different type (an operation which creates a new object of
a different type, which should approximate the original
object in some way) use the same syntax, even though they
are fundamentally very different operations. For example,
in C# or Java, one casts an object someObject
to the type SomeType
using code
like (SomeType)someObject
, and similarly,
converting the integer someInt
to the
type double
is done
using (float)someInt
. This is not so in Anna.
The casting operator as
will
never create a new object. It simply either returns
exactly the same object cast to a different type, or, if
the specified object can not be cast to that type, null is
returned. For example, to cast the value 7
to
a Float
,
write 7 as Float
. This will
result in a null value, because Int objects do not have
all the members of a Float and can therefore not be cast
to Float.
Converting values is not hardcoded into the language, but
the convention is to use a static convert
method in the desired target Type. For example, to convert
a Float object to an Int, one would
write Int::convert(7.1)
. This
will create a new Int object holding the closest integer
representation of the floating point number 7.1, which is
7.
Note that there is nothing magical about
a convert
method. It is simply a naming
convention that type conversion should be handled by a
static method named convert
located in the
type being converted to. It is the responsibility of every
type author to create suitable convert methods for their
types. Sometimes, it is desirable to make it possible to
add new such conversion methods. For example, it is very
often nice to be able to write custom behavior for
the String::convert
method,
in order to provide a nice and descriptive string
representation of an object. The convetion to use in such
cases is to add a
non-static toXXX
-method to
the target object, where XXX is the name of the type to
convert to. For example,
the String::convert
-method
will check if an object being converted to a String has
a toString
-method, and if
so, use that to convert the object to a String.
There are various situations when writing Anna code where one wishes to insert placeholders into the code that will later on be replaced with something else, usually through macros. In these situations, Anna uses so called internal identifiers to make it more clear what parts wil be replaced. An internal identifier is any regular identifier, prefixed with a % symbol. Some examples:
/* %A will get substituted with the relevant template type here. */ class Foo (template(%A:Int)) { def %A bar(%A a, %A b){ a+b } } /* The code in the first param will get generated once for each item in the second parameter, substituting %var with a different item from the collection each time. */ expandCode( %var := getNextValue(someValue, someOtherValue), var: [first, second, third, fourth, fifth]) /** This (rather meaningless) macro turns a call like setFooToValue(7) into foo = 7 It uses an internal identifier inside the ast macro to specify what piece of code to replace. */ macro setFooToValue(call) { return ast(foo = %value) % ["value": call[0]] }
It should be noted that there are no technical reasons why internal identifiers are actually needed in Anna, the language would work just as well without them. The reason why Anna requires the use of internal identifiers is for clarity - it makes it obvious which parts of the code are simply placeholders that are to be substituted with something else.
The percent symbol, when not used as a prefix for an identifier, is an operator called the format operator. You can read more about the format operator in the following section.
Various Anna types implement the %
operator,
also called __format__
. This operator should
be used for interpolating a collection of values into a
template containing placeholders. Some examples:
// Prints «This string is used as a template» print("This string is used as a %" % ["template"]) // Prints «A fancy string uses fancy named placeholders» print( "A %(quality) string uses %(quality) named placeholders" % ["quality": "fancy"]) /* AST nodes also have a format operator, it works much the same way as named placeholders in strings This will print «print(7)» */ print(ast(%someFunction(7)) % ["someVar": parser.identifier(?, "print")])
The Anna parser is written to be as accepting as possible of weird elements, and instead offloads as much of the code validation as possible to macros. This allows you to write microformat macros. These are macros that take an AST subtree and transform then into another subtree in order to allow you to define your own compact data formats. For example, JSON is a valid subset of Anna, so one could write a JSON microformat macro to transform an AST containing JSON into the Anna equivalent, meaning one could insert JSON data verbatim into the code, and make sure the JSON macro parses it to correct Anna code:
m :== json({data:[1,2,3,7], unit:"seconds", caption:null})
Note: This macro has yet to be written. Another very practical use of this feature is date handling. There are macros that make sure anything you send to the Date constructor, or anything you add or remove from a date, is parsed using the standard ISO date format:
// Create a new date from a specified date then := isoDate(2009-1-1, 12:30) // Move ahead x years and one hour then += isoDateDelta(x-0-0, 1:00)
The Anna standard library comes with three different collections built in,
List
, an ordered
collection using positive integer indices as keys, and
which comes in both
a MutableList
and
an ImutableList
flavor,
both of which are templates that can be specialized to
store any type,
e.g. MutableList«Int»
.
HashMap
, a mutable,
unordered collection using arbitrary objects as keys,
and is a template that can be specialized to store any
key/value type pair,
e.g. HashMap«String,
List«Pair«Int, Float»»»
.
Range
, which defines a
(possible infinite) imutable sequence of equally spaced
integer values.
Anna list objects use zero based offsets, and use a dynamically allocated array to store it's values internally, meaning that accessing any element in the array by index will always be a fast operation. This also means that if there are large gaps in the List, it will take up more memory than it needs, and that inserting new elements anywhere that is not near the end of the list is an inherently slow operation.
Lists can be used as stacks by employing
the push
and pop
methods and
checking the empty?
attribute.
When assigning beyond the last index of a list, it is
dynamically resized. All previously unassigned slots are
given the null value. Code
like silly=[],silly[99999999999999999]=?
is an excellent way to run out of memory.
Anna hashmap objects are one-to-one mappings that use
the hashCode
and __eq__
methods
of the key object to find the corresponding
value. Hashmaps are unordered. That is, when iterating
over them, the elements may be presented in any order.
The real syntax for Anna is painfully simple. There Anna AST contains only three types of nodes:
?
, 3
,
1.0
or "foo"
.
foo
,
String
or %bar
.
X(Y,Z)
,
where X
, Y
and Z
are arbitrary other
AST nodes. In the above example, the AST
node X
is invoked
with Y
and Z
as input
parameters.
That's it. Those are the only things needed to write Anna programs. For somebody coming from a C background, that might seem anemic for a programming language. Where are the code blocks? Where are the if/while/whenever statements? Function definitions? Meanwhile, anybody with a LISP background will twiddle their beards in boredom. They know.
The sugar front end of Anna is a simple language front end that has a few keywords and special constructs such as operator overloading that allow you to use a terser, C-like syntax. The dot operator is used for setting and getting member values, we have arithmetic operators with the usual precedence, we have special language constructs to allow us to define types, functions and variables with a little less verbosity, etc. All of this syntactic sugar is stripped away by the front end of the compiler, and is converted into the basic AST node types mentioned above. Below are some examples of these transformations.
| Regular | Sugar free |
|---|---|
{ foo bar } |
__block__(foo, bar)
|
lst.count
|
__memberGet__(lst, count)
|
lst.count = 3
|
__assign__(__memberGet__(lst, count), 3)
|
lst.push(1)
|
__memberGet__(lst, push)(1)
|
lst[4]
|
__memberGet__(lst, __get__)(4)
|
lst[4] = 'X'
|
__assign__(__memberGet__(lst, __get__)(4), 'X')
|
1+2
|
__memberGet__(1, __add__)(2)
|
foo = bar()
|
__assign__(foo, bar())
|
foo := 3
|
__var__(foo, ?, 3, __block__())
|
bar :== 3
|
__const__(bar, ?, 3, __block__())
|
def next(Int in) {in+1}
|
__const__( next, ?, __def__( next, ?, __block__( __var__(in, Int, ?, __block__())), __block__(), __block__(__memberGet__(in,__add__)(1))), __block__()) |
someCall(paramName: value)
|
someCall(__mapping__(paramName, value))
|
«Object»[1, 2, 3]
|
__collection__(__block__(Object), 1, 2, 3)
|
[1, 2, 3]
|
__collection__(?, 1, 2, 3)
|
["a":1, "b":2]
|
__collection__(?, __mapping__("a", 1), __mapping__("b", 2))
|
class MyClass ( extends(BaseClass) ) { def myMethod(){ print( "Here we are!") } } |
__const__( MyClass, ?, classType( MyClass, __block__(extends(BaseClass)), __block__( __const__( myMethod, ?, __def__( myMethod, ?, __block__(), __block__(), __block__( print("Here we are!") )), __block__()))), __block__(extends(BaseClass))) |
Anna has very simple library handling. There is no
special language construct needed for using a library. If
a library named fooBar
exists, and has a member
named baz
, simply
write fooBar.baz
anywhere
in your code in order to access that member - the library
will be loaded as needed. If you want to use
the baz
member often, and
don't want to use the full path of the member, use
the use
expression,
e.g. use(fooBar)
. In any
scope where a use expressioin is visible, the members of
the library can be used directly, e.g. by
writing baz
. The
use
expression can be used
globally for a whole module, or it can be used locally
inside a single function block. For readability, it is
highly reccomended to use as few
global use
expressions as
possible.
def someFunction() { use(math) // Because of the use expression, pi in the expression below will be // resolved to math.pi, e.g. the constant pi in the math library. print(pi) }
use
expression is
more general than simply a short cut for libraries. If you
want to find out more about it, read
the documentation on the use
expression.
Writing you own Anna library is extremely simple. Libraries are nothing but regular Anna files. When loading a library, the colon-separated directories listed in the ANNA_PATH environment variable is prepended to a list containing ./lib and a compile time defined install directory for the Anna standard library, usually /usr/local/lib/anna. When searching for a library, the above list is searched in order until a file with the same name as the library is found, with either the .anna suffix or the .so suffix. Files ending in .anna are assumed to be source code library files, and are interpreted by the Anna interpreter and loaded.
The use expression is a short hand convenience - any use:d expressions are considered in scope and considered when performing identifier lookup.
/* The use expression can be used for other things than just importing a library. */ lst :== [] use(lst) /* These calls resolve to lst.push, because lst is use:d */ push(1) push(2) push(3) print(pop(), pop(), pop())
The design goals of Anna include:
At the parser level, Anna is designed to be able to handle as much input as possible in order to make it easier to create microformats that feel natural to work with. Some of the results of this are:
As you can see, Anna leaves you with a lot of options when it comes to separators. There are however very specific coding conventions:
/* Multiple-line expression groups should never have a trailing separator. Put the ending parenthesis/brace/bracket on a separate line in order to make cutting and pasting of code easier. */ default { foo bar baz } someFunctionCall( arg1 arg2 arg3 ) literal :== [ "first" "second" "third" ] /* Single-line expression groups should never have a trailing separator. The reason is to keep down the amount of clutter. */ default { foo, bar } someFunctionCall(arg1, arg2) literal :== ["first", "second"]
Anna names may only contain the latin letters a-z in lower and upper case as well as underscores, numbers and the ? and ! characters.
Variable and constant names must have a lower
case letter as the first non-underscore letter,
like desertSpoon
or _giantBanana
. Type names
must have an upper case letter as the first
non-underscore letter,
like CrispyBacon
or _VeganApplePie
. The
reason for enforcing this is that it makes it
significantly easier to write an Anna parser.
In addition, the following conventions exist in regards to naming:
__someName__
, are
reserved by the implementation. You can override these
where it makes sense, e.g. implementing a new type that
has an __add__
member in
order to allow the use of
the +
operator, but you
should never make up new names in this namespace.
Range.open?
property,
which is non-null only for semi-infinite ranges.
documentationFragment
,
callingFunction
or memberAttribute
.
doc
,
fun
or attr
.
e
constant in the math
module is an exception to this rule)
return
expression
should be used to make it explicit what is returned -
unless the return value truly doesn't matter.
The «Easy to parse» design constraint has unfortunatly led
to a significant number of exceptions to the «C-like
syntax» rule. For example, Anna uses either
List«Int»
or List<<Int>>
to
specialize a template, instead
of List<Int>
, which is
used in C++, Java and C#. The reason is simple: It is
impossible to write a general parser that
uses <>
both for
comparisons and for template specializations without
making very specific exceptions in the parser. Such
exceptions would also severly restrict what code can be
placed inside of a template specialization. In Anna,
templates can be used to insert any expression into a code
template, not just a Type.
/* A small sample program using a template specialization that would be impossible to parse unambiguably if <> was used for template specialization. */ class Foo (template(%op:?)) { def bar(Int a, Int b) { %op } } def Int main() { boo :== Foo«a > b»() print(boo.bar(3,4)) }
It should be noted that the creators of D, another C-like
language which aims to be easier to parse than e.g. C++,
ran into the same problem with template
specialization. They chose instead to
use List!(Int)
as the template specialization
syntax. Unfortunatly, that would be impossible in Anna,
since Anna is less picky with what can be specialized,
leading (again) to syntactic ambiguity. On the other hand, D
uses <<
and >>
for
bitshifting, just like C, meaning these symbols where not
available for template specialization. The solution that
works for one language does not always work for another.
One significant kludge in the Anna syntax is the expression merge pass of compilation. It is used to move expressions into the expression preceeding them, and is useful in situations like the if/else expression pair. While useful, it is definitely a rather large syntactic wart. TODO: Explain more precisely how it works, and how it can be used.
This section gives an overview of the Anna interpreter implementation. It is not important to read or understand this section in order to begin programming in Anna. It is intended as a starting point for those who wish to dig into the source code.
At the language level, everything in Anna is an Object. Types and functions are objects. There are also no primitive data types, integers, characaters and floats are also objects. This is not true at the implementation level, though:
For a more complete description of the pointer magic used to avoid memory allocations for integers and characters, see for example this article.
The above holds true for as long as only common operations, such as adding, subtracting, etc. are performed on the values. If e.g. reflection is employed or any other operation that requires access to the object type, then any object stored directly in the stack is silently converted into a full fledged object.
Aside from the performance increase and memory usage reduction, there is no way to tell that this is happening behind the scenes. Unlike e.g. Python, this optimization does not leak. This is possible because the above data types are all imutable and there is no memory location pointer or other object identity for objects. It is simply impossible to tell e.g. two integers with the same value apart.
The Anna implementation uses a very simplistic object model. Objects in Anna are represented by a struct looking like this:
struct anna_object { /** Various bit flags for this object. The first 8 bits of this flag are reserved by the memory allocator and should not be touched. */ int flags; /** The type of this object */ struct anna_type *type; /** All the non-static member values of this object. To decode what member lives at what offset, use the information stored in the type object. */ anna_entry_t member[]; };
The memory overhead of an object (including the memory overhead of the memory allocator and the garbage collector, is one word (the flags) and one memory address(the object type).
anna_entry_t
represents one non-static
member value of the object.
type->member_count
.
| Value | Meaning |
|---|---|
| 0 | This entry is used to store a pointer to a anna_object struct, which lives at the address pointed to by this pointer. |
| 1 | This entry is used to store an Int object. To get the value of the object, right shift the entry two bits. |
| 2 | This entry is used to store a Char object. To get the value of the object, right shift the entry two bits. |
| 3 | This entry is used to store a piece of memory that should be garbage collected, but is not an anna_object struct. The address of the object can be found by stripping the last bit from the pointer. |
| Any other | This entry is used to store a floating point number. The first 16 bits of the number need to be flipped before use. |
A description of how this bizarre scheme works.
The type struct used to store information about a specific type in Anna is rather complicated. In addition to storing static member data, the type struct also stores data that allows for introspection, efficient garbage collection, etc.
Anna ships with a test suite consisting of a series of small Anna programs and their expected output and exit status. Currently, the tests suite consists of ~250 tests taking up over 4000 lines of code, which is about 9 % of the total source code in the Anna distribution.
To run the Anna test suite, simply type make
test
at the command line prompt. Don't be surprised
if a few tests fail, Anna is currently not 100 % finished.
The test suite has multiple goals, the most important of which are:
Anna is still a work in progress. As such, there are various known issues with the current implementation. These include:
MutableList
,
ImutableList
,
Pair
and HashMap
. Making
subclasses of specialized versions of these types,
e.g. HashMap«Int, Char»
works fine, as does subclassing any other unspecialized
types.
Anna is open source and can be downloaded online. There is still no actual Anna release, the source code is avaiable on Github.


Name
HPL_rollT Roll U and forward the column panel.
Synopsis
#include "hpl.h"
void
HPL_rollT(
HPL_T_panel *
PBCST
,
int *
IFLAG
,
HPL_T_panel *
PANEL
,
const int
N
,
double *
U
,
const int
LDU
,
const int *
IPLEN
,
const int *
IPMAP
,
const int *
IPMAPM1
);
Description
HPL_rollT
rolls the local arrays containing the local pieces of U, so
that on exit to this function U is replicated in every process row.
In addition, this function probe for the presence of the column panel
and forwards it when available.
Arguments
PBCST (local input/output) HPL_T_panel *
On entry, PBCST points to the data structure containing the
panel (to be broadcast) information.
IFLAG (local input/output) int *
On entry, IFLAG indicates whether or not the broadcast has
already been completed. If not, probing will occur, and the
outcome will be contained in IFLAG on exit.
PANEL (local input/output) HPL_T_panel *
On entry, PANEL points to the data structure containing the
panel (to be rolled) information.
N (local input) const int
On entry, N specifies the local number of rows of U. N must
be at least zero.

LDU (local input) const int
On entry, LDU specifies the local leading dimension of U. LDU
should be at least MAX(1,N).
IPLEN (global input) const int *
On entry, IPLEN is an array of dimension NPROW+1. This array
is such that IPLEN[i+1] - IPLEN[i] is the number of rows of U
in each process row.
IPMAP (global input) const int *
On entry, IMAP is an array of dimension NPROW. This array
contains the logarithmic mapping of the processes. In other
words, IMAP[myrow] is the absolute coordinate of the sorted
process.
IPMAPM1 (global input) const int *
On entry, IMAPM1 is an array of dimension NPROW. This array
contains the inverse of the logarithmic mapping contained in
IMAP: For i in [0.. NPROW) IMAPM1[IMAP[i]] = i.
See Also
HPL_pdlaswp01T.


Did you lose your job during this pandemic?
Here is a tiny little form that will help you to get discovered by potential hiring organizations
and good friends who can connect you to them.
How does this work?
- Fill up the form with your details.
- The entries of this form will be floated to a publicly accessible database through this
website.
- We are reaching out to recruiters, HR teams and organizations that are currently hiring and
looking for people like YOU.
What we don't do.
- We do not sell your data or spam you.
- We are not promising any jobs for the people registering their information.
We are a group of independent volunteers trying to help people get back to workforce as early as
possible. No gimmicks. No cross-selling. Just a simple volunteer effort.


- Presented at conference - won a prize lol
- Took part in Data Study Group (as facilitator) for the whole of September
- Set up an XAI interest group for students, reaching across Turing and Bristol
- Entropy Project - quite interesting inital results trying to find a way of addressing the independent component problem with approximate entropy by trying to fuse svd entropy with approximate entropy using the matrix profile. Need to spend time trying to link it back to the structure of the time series - incoporating expert knowledge into feature engineering.
- Incorporating Generative Ideas into LIME. Interesting results using GMM to model trainind data distribution and then Fisher Vectors to get similarity scores between test point and samples in feature space. Similar to MeLIME (paper Peter put in the Slack channel), need to look at ways to to evaluate explanations other than robustness
- Learning interpretable logical rules - FOIL algorithm - trying to optimise this by using 'influential training points'
Supervision 04/08/2020
Admin:
- First year review, when will this be? Do I need to do anything?
Since last meeting:
- Put everything I've done on Github and my website. Link here
- Tried to contribute to FAT-Forensics (pull request with Kacper)
- Applied to Turing Data Study Group
- Attended ICML. Write up here
- Research:
- Thought a lot about understanding vs. explaining and what it means in relation to data. Wrote this article
- Research Project 1: Extracting meaning from longitudinal health data (link here)
I guess the main idea behind this project is trying to use an understanding of the data to make better decisions about how to select features, with a potential link to Peter's original idea: "extracting a narrative from machine learning experiments " as well as a link to idea: "maybe if we perform more intelligent feature engineering we could use simpler models"
- Wrote a Jupyter noteboook exploring the feature extraction pipeline for the depresjon dataset (predicting depression from activity levels)
- Discussed the difficulty in knowing how to slice data, especially when number of subjects is small but number of samples is large
- Found that entropy performs well as an aggregate feature
- Mini literature review of different entropy measures for time series
- Started to think about how entropy measures are not robust to noise as they rely on extracting templates from the sample and dont account for dependence between templates
- Started to play around with the idea of "how can we extract independent samples from time series data and use these to improve existing entropy measures"
- Implemented an entropy measure that uses the matrix profile of a time series and then computes the entropy over the matrix profile
- Need to run some proper tests on this new measure. Want to relate it back to how different entropy measures perform according to the shape of the time series
- Think that representation learning for time series data could be really interesting here
- Research Project 2: Generative Models for Post-Hoc Interpretability (link here)
The basic idea behind this project is looking at whether it makes sense to create explanations for the outputs of machine learning models without understanding the input (explaining the data).
- The paper Peter sent me regarding kernels and distance metrics (Thomas Gartner et al.) sparked this way of thinking: how do you define "local" in high dimensional space? How important is the choice of kernel?
- Made a jupyter notebook that looks at some of the weaknesses of LIME, particularly the issues with sampling and locality
- Currently Lime samples using a normal sampler for the associated bin of the discretised value of each feature in the instance wanting to be explained
- I want to build a generative model of the training data, sample from that, and then use a Fisher kernel to measure locality and explore whether that offers more robust explanations
- Research Project 3: Causal Structure of Data for Interpretable Models (link here)
This project questions whether we can have explainability without an understanding of the causal structure of the underlying data.
- Made a Jupyter notebook that creates synthetic datasets representing different causally related features and exploring how SHAP attributes feature importance. Results show that SHAP attrbutes importance depdnding on the specific type of causal relation.
- A recent paper has attempted to re-write SHAP to account for causal structure by allowing the symmetric property to be relaxed. I think that this could be improved by identifying the type of causal relation between feature through causal discovery algorithm. This is what I'm going to explore next.
- Research Project 4: Bridging Machine Learning and Symbolic Reasoning for Intepretable Models of Noisy Biomedical Data (link here)
This is probobaly my most ambitious project and is still being thought out in my head. I was really interested in a paper ("Bridging machine learning and logical reasoning by abductive learning.") that uses abduction with machine learning to learn rules. Im really interested in applying this to see how a discrete module could be bridged with a differntiable learning module to learn explanatory concepts in the data. I think this could actually be interesting in biomedical data - especially as a solution to limited size training sets.
Supervision 11/06/2020
Discussion Points
What I have been working on: Extracting Meaning from Biomedical Data
- Discussed the ''myth of the accuracy vs. interpretability trade off'' as presented by Rudin
- Discussed the machine learning development pipeline and where explainability can augment this process.
- Lots of research has been conducted in post-hoc explanations of machine learning models
- Growing research area looks at developing explainability of model training, using influence functions etc
- Lack of literature looking at the explainability of data. This can be further explored by differentiating between 'explanation' of the data and how this contributes to an 'understanding' of the data and what is meant by these terms. Also discussed the difference between a 'description' of the data i.e. summary statistics and more in depth knowledge extraction i.e what sort of distribution is the data drawn form. Overall idea here would be to look at how an explanation/understanding of the data would contribute to the machine learning pipeline and would perhaps address problems including choosing a model based on lack of understanding/ease of use
- Presented the idea of 'interpretable feature extraction' and currently researching representation learning. Discussed the question of whether sparsity necessarily implies interpretability. Discussed a link to Kacper's work on how representation learning is achieved via surrogate models.
- Discussed entropy in light of presentation Torty had done that week to Alan Wilson: this involved deriving relative entropy from a bayesian perspective and relating this to entropy maximisation. Discussed the ubiquity of entropy: physical meaning, information theoretic meaning and a probabilistic meaning. Discussed link to scoring methods and how assumptions are encoded in choice of entropy. (Peter to send papers) Discussed the tendency to rely on the KL divergence without explicitly stating assumptions. Discussed how this is due to lack of general understanding/conceptualisation of entropy and could benefit from some bottom up discussion of entropy (machine learning 101). Could also link this back to understanding the data.
- Discussed the general problems with trying to conduct work in an oversaturated field positives and negatives to this. Discussed trying to sample 'locally' in high dimensions and how this breaks down. Discussed how dimensionality reduction should be performed before neighbours are samples. Discussed the importance of distance metric in high dimensional spaces. Going to investigate how distance metrics affect the predictions from LIME - could I use BLIimey for this? Link to work that Peter had conducted on distance metrics, using Haskell (Peter to send paper) to define distances between different structures i.e. lists and vectors.
- Discussed UK Biobank - agreed that I would hold off applying to a particular project until I've sandboxed successfully some ideas
- Discussed the data I have been sandboxing recently: this is longitudinal data: 1) predicting epileptic fits from brain signals 2) predicting depression from activity data
- Discussed 'extracting meaning from longitudinal data' this could either be state based models, or sliding windows, or very simple point based models where window size = 1. Can we say, after seeing the data whether we need to model with state based models or with sliding windows? (Peter to send papers, Neil twiney?) Can I link this back to the work I have done?
- Discussed also the difficulty in biomedical longitudinal data for diseases looking specifically at the limited subjects but high number of samples and how this affects feature extraction - can we make best guesses about the type of prediction algorithm/features we could use after seeing the shape of the data? Discussed hierarchical modelling as a way to do this. Possible link back to work I did on modelling moral judgments and hierarchically clustering peoples morals and how bias is instilled here. Also discussed how choice of features and algorithm is not just affected by an understanding of the data but also on an understanding of the machine learning/statistical task
- Discussed causal inference and relevance to my work. Discussed my ongoing project investigating how current explainability methods/ vanilla regression deal with different types of confounding. This is what you should prepare a presentation on for peter's group. Also discussed my side project of developing interactive learning resources for causal inference
- Discussed potentially getting more involved in FAT - forensics, need to ask Kacper what he wants done to the project and possibly coordinate with miquel.
- Discussed Mihaela van der schar, potentially link up but would prefer to do this later on in the project when I have achieved something lol
Action Points:
- Organise and share supervision meeting notes and progress reports - GitHub
- Develop GitHub further, by next meeting want to have dedicated 'research projects' pages relating to the above directions.
Supervision 29/04/2020
Discussed
- Peter had reviewed my research proposal and fed back that it looked good but neeeded more of an overall aim - we discussed that this could be health
- Peter reccomended contacts from bristol who do health research including Louise millard, biobank
- Peter had sorted me a second supervisor, Ollie ray
- Discussed covid and also that I am going to stay in London for at least another year
Actions:
- Contact Louise Millard
- apply to Biobank
- Re-write research proposal to include health ideas - starrt working on stage 1: "extracting meaning form biomedical data
- Prepare and present phd proposal to Bristol Turing working group
- Prepare and present to Alan Wilson's mentoship group with the Turing
Supervision 17/02/2020 (Bristol)
I didnt actually record properly this meeting which is why this is so vague
Discussed:
- I am currently wiritng a research proposal
Action Points:
- Continue writing research proposal
Supervision 16/01/2020 (London)
Discussed:
- Peter had reviewed my literature review over christmas, gave feedback, not worth making it into anything formal (too reliant on other pieces of literature, specifically tim miller) but a good source of raw material
- Good to spend this time thinking about what im actually interested in, discussed a few ideas, decided that I am really interested in the cross over with social sciences but want to keep it machine learning focused (also interested in symbolic AI) not bothered about any particular technique.
- I fedback work I'd done on F score
- discussed explana-narratives and Peter reccomend I research genevieve lively narratist in bristol
Action points:
- Id been working on some research questions but they were very broad so I need to reveiew these and think more clearly about a narrative3 for the phd that interests me.
Supervision 2/12/2019 (Bristol)
I didnt actually record properly this meeting which is why this is so vague
Discussed:
- How to write an effective literature review
Actions:
- Continue to work on literature review
Supervision 25/11/2019 (Skype)
Discussed:
- Carry on with reading around explainable AI
- Bit bored of just reading papers so trying to do some regular coding (Hackerank and Kaggle) and trying to engage with Turing as much as possible via training
- Still expressing an interest in explainability of multi agent system (funny that in hindsight, now I know the terminology I am actually interested in the explainability of the integration of multi-modal data and representation learning)
- Went to Turing spark course and software engineering course
- peter reccommended I think about the following: "what does averaging of F-score on the classes mean" and "how to get verbal narratives out of machine learning experiments".
Actions:
Supervision 11/11/2019 (Bristol)
Discussed:
- Github community where members can add their areas of expertise
Reproducible research
reproducible articles: Type of typesetting that accepts all inputs and offers all
outputs
- NIPS submit fragile resultsʼ dependence on regularisation settings
Cross validation - should publish what choices made throughout model
creation and evaluation
- Argumentation - used to evaluate explanations - possibility of presenting work
back to research group
- Jim smith - possibly a cross over - email to keep him in loop
Francesca Toni will be at panel meeting
Conference on decision support and recommender systems
Jonathan Lowry is working on multi agent systems
Actions:
- Next meeting via Skype at 11.30 Monday 25th Nov - sort out Skype prior to this
Create an overleaf and map out literature review
- Carry on reading into argumentation focusing on Francesca tonis ideas & j
Lowry
- Book onto conference for 21st and 22nd of nov
- Look at Jonathon Lowry lecture series
Supervision: 21/10/2019 (Bristol)
Discussed:
- Idea behind explanations for multi component systems
- Idea behind 'background knowledge” for improving classification systems
- Peter to be in Japan from 22/10/19 - 07/11/19
- 3 ideas - social media, fairness, explainability. Social media seemed too
applied, fairness could be interesting particularly different contexts for bias and
methodology order. Continue with explainability, idea of argumentation and
negotiation amongst agents
- Have been lent Argumentation book
- Literature Review by Christmas on Overleaf
Actions:
- Continue with explainable AI
- Read book on Argumentation
- Think about literature review
- Research Turing Way
- Find second supervisor
- Book train for next meeting in bristol
Supervision 14/10/2019 (Bristol)
General Project Meeting - introduction to Peterʼs research group
Actions:
- To be added to Slack group
- Send Peter email with outlook email address and re-schedule meeting on
Monday
- Email Simon by end of week - sort out desk
- Read Kacperʼs papers. have plan by Monday
Supervision 30/9/2019 (Bristol)
Admin Stuff
- -Turing Induction Week
- -What to expect from Turing
- -How is long distance relationship going to work
- when will we have meeting/when will I submit work
- -Phd at Bristol -how is it assessed, first year 'upgradeʼ?
- -my paper
Content
present research proposal, different work packages:
- Collect moral data -research the best way to collect this form of data
- Use data to create a hybrid model of automated decision making
particularly looking at the impact of model assumptions maybe using non
parametric models as an alternative and how to aggregate individual models of
moral behaviour
- Reducing bias in automated decision making
- Transparency in automated decision making particularly looking at
procedural regularity.
Feedback from the Turing was that they didnʼt know if there was a viable way
to do the project. the project proposal needs to focus on things that can be
realistically achieved and the computational methods that will deliver that. Want to keep it grounded in computer science, want to combine it with Peter
research area.
Present ideas: I want to move away from the idea of automating moral decision making -think
this is too hard and complicated
But I have a few ideas:
- Data collection, how to collect moral data and how to ensure it representative
- Modelling morality to learn more about human ethics
- Transparent ai -cryptographic
- Different areas of automated decision making
- Counterfactual fairness, focus on protecting
- How can we make models robust to different assumptions, looking at non parametric models
- Human/technology agency tradeoff in automated decision making https://
onlinelibrary.wiley.com/doi/full/10.1002/poi3.198
- Probabilistic programming frameworks, pymc3, contributing to open source
- Peterʼs project: AI measurement theory Want to read Artificial intelligence with uncertainty
Notes from meeting
Admin:
- Phd Contract Costing has gone through from Bristol -Peter to chaser following
meeting
- At Bristol there are usually annual reviews (6 months for CS)
- First Year review is particularly important present a literature review and a plan,
mini Viva and a pass or fail
- Need to look for second supervisor/reviewer
- Kacper (PhD student under Peter at Bristol) doing similar thing in explainable python
package for methodology, high quality software, LIME improvements
Project Discussion:
Andrew Charlesworth based at the Bristol Law Faculty presents interesting
arguments about ethics washing. Peter expresses that a lot of problems within ethics can be solved with proper
legislation. Stuart Russel recently brought out a new book Human Compatible: AI and the
Problem of Control
Limited AV expertise in Bristol. May be easier to focus on building trust in AI, this objective could be easier to
measure than ethics . At some point will need to link with psychology/user studies. Interesting area to pursue is the idea of narrative machines, reason that we
trust is built on stories. Interesting area to pursue could be having a dialogue with interactive ai.
Actions
- Read, explore and branch out
- Read Human Compatible: AI and the Problem of Control
- Need to create/find a decent application for keeping track of readings and
ideas
- Develop skills
- Come up with a 3 month goal
- Come up with a weekly goal
- Peter is speaking at event on the 21st and 22nd November on recommender
systems -book this, may cost money
- Next meeting on the 14th October at 9.30 in room 4.01 in Bristol


On the third rotation, we learnt about UV Printing and Sublimation. UV Printing is a process using ultraviolet light to dry or cure ink as printed. Sublimation is a process of using heat to transfer dye onto materials. For UV Printing, I designed my picture on Coreldraw and then used the UV Printer to print the image on a popsocket. I created two products using dye sublimation, a t-shirt and a pencil pouch. In both cases, I designed the image using Coreldraw. I used the Brother-GT 361 and the Sawgrass 800 printer for both products. After completing the challenges, I think one advantage that sublimation holds over UV Priting is that it the image printed using sublimation is more durable compared to that of UV Printing.
Back to Index


Modern crystal awards, classic trophies, plaques, medals, ribbons and nameplates, Corporate Executive gifts, Golf treasures. Wedding gifts receive free tray engraving when you mention this site.
Designed and Maintained by R.A. Rahe Dream Productions, Inc.
info@dreamsters.com