A fortunate chain of events – a dry read

At Askia we love to talk about Askia things… and about a year ago, the technical team got together in a room and agreed on what was our biggest need: the ability to elegantly call a web service from a survey and decipher the result and store it appropriately.

Web-service not included

I have mentioned in previous articles how an API allows you extend your para-data. With the IP-address that you collect (and that we encrypt – GDPR is watching you), you can obtain the general location of the person. With the location, you can get the weather at the time of the interview and the likelihood they voted to a given party in the last elections.

You could always call a web service by adding some JavaScript in your page but that was not very elegant… and also made it hard to hide any authentication method.

So we decided to create a new routing where the Web Service was called from the server and not from the browser – effectively hiding the call from the interviewee. We got inspiration from the Postman interface and quickly put together a new routing.

The interface allows you to run different scripts depending on the success of the call and to manipulate and store the different parts of the response… and we introduced a new keyword CurrentHttpResponse.

QueryWebService

At that point, we thought that this had been relatively easy and we contemplated a well deserved visit to the local pub for refreshments.

XML and the Argonauts

As we were putting together an example – calling openweathermap.org to get the weather anywhere in the world – we hit our first problem.

The response looked like like this:

<?xml version="1.0" encoding="utf-8"?>
<current>
   <city id="6690581" name="Belsize Park">
      <coord lon="-0.18" lat="51.55"></coord>
      <country>GB</country>
      <sun rise="2018-03-06T06:33:58" set="2018-03-06T17:50:36"></sun>
   </city>
   <temperature value="282.33" min="281.15" max="283.15" unit="kelvin"></temperature>
   <humidity value="66" unit="%"></humidity>
   <pressure value="988" unit="hPa"></pressure>
   <wind>
      <speed value="2.1" name="Light breeze"></speed>
      <gusts></gusts>
      <direction value="200" code="SSW" name="South-southwest"></direction>
   </wind>
   <clouds value="40" name="scattered clouds"></clouds>
   <visibility value="10000"></visibility>
   <precipitation mode="no"></precipitation>
   <weather number="521" value="shower rain" icon="09d"></weather>
   <lastupdate value="2018-03-06T13:50:00"></lastupdate>
 </current>

To get the temperature, we would have had to look for the string “temperature value=” and extract the following digits… it was possible but a bit of a dirty hack, we felt. As stated before, at Askia we love to talk but we hate dirty hacks.

So we started talking about having a XML parser. The cool kids in the dev team took a clear stand: we do not need a XML parser and we would be a laughing stock if we implemented one. What we needed was a JSON parser. Even better we thought: what if AskiaScript could natively support JSON? Note: I can confirm it, we did a XML parser anyway – I hope you are not laughing.

JSON native and the dictionary

So we came up with the following syntax:

Dim myAuthorVar = @{
 “name”:”Jerome”,
 “age“:21,
 “occupation”:”laughing stock”,
 “busy”: true,
 “children”: [“Mackenzie”, “Austin”],
 “address” : {
    “postcode”:”SW12”
    “city”:”london”
    }
 }
Return myAuthorVar [“occupation”]

We were very excited but that meant we need a new variable type – it’s sometimes called an object or a map but also a Dictionary – the failed librarians and encyclopaedists that we are loved that… so there it was: the Dictionary. It allows to store a series of named values in one object. You can set its properties with a method Set like this myAuthorVar. Set (”Busy”, False ). And access them like you would with an array but by specifying a string instead of a number like this: myAuthorVar [“name”].

Variant and Arrays of Variant

I mentioned that it would be a good time to go to the pub when somebody asked what was the type returned by a dictionary accessor. In other words what was the type of myAuthorVar [“age”] ? The response to this is “it depends”… and there was no way of knowing before. Right now, it was a number, but if a web service had indicated “age” as “fifty-ish”, the result would be a string.

So we had to introduce a new type: the Variant

If you called myAuthorVar.TypeOf(), it would return “variant”… but inside the variant is a dictionary. So we created a method for Variant to know what was inside and we called it InnerTypeOf. myAuthorVar.InnerTypeOf() does return “dictionary”.

It was also nice to write @[1 ,2 ,3] or even @[3.14159,”pear”, “apple”]  – both are arrays of variants that we decided to call “arrays” for simplicity.

A variant could hold any of what we decided to call the base basic types: number, string, date, dictionary and any array of the types above. OK – let’s go to the pub! But then we remembered that JSON supported Null and Booleans… and because we wanted full compatibility, we had to create two new AskiaScript types: Null (which does not do much) and Boolean having the possibility of only taking two values: true or false.

Booleans and back compatibility

This was a can of worms – because we used to consider True and False to be numbers. Let’s imagine some script like this:

 Dim myVariable = (Q1_Name = 7)
 ' … some clever coding…
 myVariable = 42
 ' … more clever coding…
 If myVariable = 42 Then
    ' Save the world ...
 Endif

In classic AskiaScript, this would create a variable called myVariable as a number with a value of 1 or 0 and later taking the value 42 allowing the world to be saved.

We did not want to break back compatibility. I am going to summarize what was hours of discussions. We decided that comparators (like equal or Has) had to return numbers. If they returned booleans, the setting to 42 would now trigger an error because 42 is not a Boolean. And if we permitted an automatic conversion of numbers into Booleans, my Variable would take the value True (and not 42) which would change the way the scripts ran… and the world as we know would perish.

Wordy woes

Having spoken for so long, we were quite thirsty as you might guess. But we realised that our language would become very verbose and somehow inelegant if we had to convert Variants into the type we wanted whenever we wanted to use them.

In the example above, if we wanted to find out the length of our author’s post-code, we would have had to write:

 Dim hisAddress = myAuthorVar["address"]
 Dim hisAddressAsDic = hisAddress.ToDictionary()
 Dim hisPostcode = hisAddressAsDic ["postcode"]
 Dim hisPostcodeStr = hisPostcode.ToString()
 return hisPostcodeStr.Length

This was ridiculous… it would take ages to write any serious code… and we had better things to do than write verbose code (at that stage I was thinking of all the beers I would not be able to drink if I had to type that much to get my own postcode). So we went back to the drawing table and agreed that

myAuthorVar [“address”] [“postcode”]. Length was all we needed.

This elegant code was only possible if Variants supported ALL the properties and methods of ALL the basic types. That was a lot of unit tests that we had to do. So we focused (no blurred vision) and we wrote them.

This meant a serious rewrite and a careful management of conflicts: Format is a method for numbers and dates and they act very differently. So we put together a set of rules.

I’ll give you a reference

At that point, we had spent a lot of time on this, we were (very) thirsty but we wanted it to be perfect. And we realised we had a problem – what if we wanted to change the Postcode of our author (by code).

myAuthorVar [“address”] returned a Variant holding a dictionary with the address – a copy of the address. So to change the postcode we would have needed to write:


 Dim hisAddress = myAuthorVar["address"]
 hisAddress.Set ( "postcode" , "EC2A" )
 myAuthorVar.Set ("address", hisAddress)

This was again way too verbose. So we decided that accessors ( the closed brackets [ ] used by dictionary and arrays) would not return a copy of the address but a reference to the address of the author. This meant that we could write

 myAuthorVar["address"].Set ( "postcode" , "EC2A" )

This added very serious complication the the code  it’s called pointers as in dangling pointers in C++)… and that’s very difficult to make work. In the above example (as in life), the variable hisAddress can outlive myAuthorVar. We had to write a lot of unit tests to ensure that everything worked and we did not have memory leaks. This is discussed here.

For short, a variable stops being a reference as soon as you assign it something else.

AskiaScript Anonymous

We had an ongoing problem with the Value property of question – and we thought it’d be a good idea to address it now before we went to the pub.

Q1.Value returns a string if Q1 is an open-ended question. And it returns an array of numbers if Q1 is closed with multiple response. It can also be a number or a date…

Now let’s imagine we have a script like this

 Dim myVariable = Q1
 ‘ On Mondays at precisely 12 o’clock
 If Now. Day()= 1 and Now.Hour() = 12 Then
    myVariable = Q2
 Endif
 ‘ What is myVariable.Value here?

AskiaScript is a compiler – it wants to know the type of things before it’s run… but in that example, myVariable. Value could be of a different type depending on the day and time it was run.

And what if we had something like Q1.NextVisibleQuestion.Value?

So we decided that as soon as you put a question into a variable then the variable becomes an “anonymous question”. All methods of an anonymous question would work but the Value property would be a variant…. And we also decided to make sure that CurrentQuestion was an anonymous question. Problem solved! Drinks anyone?

But then we had another huge back-compatibility problem. Let’s look at the following code:

 
 Dim myVariable = QNumeric
 Return myVariable + 1

In classic AskiaScript, the system would add an invisible “.Value” after QNumeric (we call that an implicit property). myVariable would be a number and we would return that number incremented by 1.

But with the introduction of anonymous questions, myVariable was now a question. Facing an operator (the +) we would add again the implicit property .Value. But now value would be a variant and we had no rule to add a variant to something else… up to now.

So we made sure that we had rules to add any variant to another Variant – or any basic type or array of basic types. Not just add but also subtract, multiply, divide, compare – including all the keywords like  Has, HasNone etc. In total, combining 4 operators, a dozen of comparators, with 6 basic types and 3 types of arrays (number, string and variants) that made a lot of decisions to take (and a lot of discussions) and many many unit tests.

Before we started this development, we had 1667 unit tests ensuring that all functions of the AskiaScript behave the same from one version to another.

For this, we had to add 2231 (!) more unit tests. Once they all passed successfully, we added the whole thing to Suite 5.4.8 and we hope you’ll like it.

Enough Quant Tricks, we’ll be in the pub for a swift one – we deserved it.

Of Askia Scripts and Functions

Introduction: What are Askia Scripts for? Or should I say what are their function?

AskiaScripts were designed to evaluate conditions within a survey – at first to branch the survey and then to set values to (often dummy) questions. They needed to be easy to write (and re-read!) and the user should know at creation time if the script was going to succeed or not.

The needs to improve AskiaScripts came as our clients’ surveys became incredibly complex – and that we used our language to produce our ADC.

Lately, AskiaScripts have been used to run very complex routings – like the post-codification of open-ended responses. We had a request to optimise a routing which had hundreds of lines…

AskiaScripts are also used in Tools to verify the quality of data at the end of collection. It’s here that the demand of functions came loudest where there is a need to norm the way straight-lining is evaluated for grid questions for instance. Here again we have seen scripts which have thousands of lines.

Finally, AskiaScripts are also used in Analyse to achieve increasingly complex calculations on the fly – and aggregating data while being at interview level.

From the feedback we received, we believe 2.0 is a success although the in-take has been slow (even internally).

I believe AskiaScripts will be used for weird custom adaptive conjoint, very complex calculations at run-time (segmentation) – I think it will also be used in defining and running super portfolios at a later stage.

Let’s summarise what the core values of AskiaScripts are – knowing they could be antonymic:

  • Simplicity
  • Adapted to survey research
  • Reliable: minimise the likelihood of runtime errors
  • Powerful: the competition often uses JavaScript (which does not have the 3 previous points)
  • And finally extensible – by Askia and by users

Functions: extending Askia Scripts

Rather than us adding functions whenever they are needed (which will still happen), we have decided to let users create their own functions. Teach a man to fish and you have saved yourself a fish.

A function is a piece of code that you can call with different parameters.

By default, the parameters of a function will be passed by value for our basic atomic types: numbers, strings, and dates. The arrays (and all complex objects) will be passed by reference.

Script Value Reference screenshot

If we want to change the way the parameters work, we can use the keyword ByVal or ByRef to force passing the parameter by value or by reference respectively.

Script By Val By Ref screenshot

Let’s talk about scope, baby

Scopes of variables screenshot

A scope defines where a variable is available.

Variable1 is available throughout your script. Referring to Variable2 will generate an error if it’s after the Else statement.

AskiaScript hit the same problem that most scripting languages have had (JavaScript, old VBA, … ). Every variable created is global – unless it’s within a For or an If – or a function.

This might not be a problem when you write a routing condition. It will be if you write an Adaptive Conjoint or a full-on survey analyser. You will need to remember which variables you have already used and name them differently and it will make it very hard to re-use code (the holy grail of any programmer). It also makes IntelliSense (automatic code completion) absolutely unusable.

Every language came up with a different solution to that problem. The original 1960 languages had global variables. Then functions were invented (with parameters passed by value or by address). Then classes and name spaces were invented. JavaScript went another way – it used nested functions to make sure that variables (and sub-functions) were not visible everywhere.

To be or not to be typed, that is the question…

Any variable or method in Askia is strongly typed – this means that at compilation time, we already know the type of the variable. This allows us to know if you can use a method or not for every object.

For questions, this means that we we know that Gender.Value is a number (1, 2 or DK) and that FavouriteNewspapers.Value is an array of numbers.

But if we have a function that takes a question as a parameter, we do not know the type of its value: it could be a number, an array of numbers, a string or a date…

Script Typed Question screenshot

Within the function, we say that the question is anonymous. And we have defined its Value to be a Variant. A variant is an object whose type we only know at run-time. For this, you have a few properties that you can use to convert a Variant into something more useful.

A variant has the property InnerType which indicates what it holds. You can convert any Variant into something else with the following methods: ToNumber(), ToString(), ToDate(), ToNumberArray().

Script Variant To String screenshot

Mods rule!

After a lot of internal discussion, we have decided to define Modules – or name spaces. You will be able to put together a set of variables and functions together. By default – and unless you specify it – these variables and functions will not be accessible from outside of the modules – in Object-Oriented Programming, this is called encapsulation.

You will be able to make some of the variables and functions available from outside the module – they will need to be prefixed by the keyword Export.

To clarify everything, let’s have some sample code:

Script Module screenshot

Inside the module, you can refer to the variables MaxAnswers and Pi from every-where. And you can call any function defined in there.

Outside the module, you will have to write SampleModule1::DoTheCalculation or SampleModule1::MaxAnswers to access the public members.

The default way to create a module is with Module XX / EndModule. You can either include the definition of your module in your condition script OR write it in a file that you add as a resource. These files must have a .asx extension (Askia Script eXtension). To use a module in a routing, you need to call Import + name of the module.

Script Import Module screenshot

Note that a call to SampleModule::PI or SampleModule::DoTheCalculation would return an error.

When Import SampleModule1 is called, all the code which is outside of the function will be run – that is everything in Initialisation a) and Initialisation b) in the example above.

AskiaScripts evolve all the time… and we might create a function which conflicts with a user defined one. The user defined one should still work (and be called) once the new version is released – back compatibility is important.

One side effect of what we have decided to do with modules is that variable declared in the main scope will be global in a whole script if modules are not used. We are hoping we won’t regret this in the future but the aim of AskiaScripts is not to build full on applications… yet!

Conclusion

Functions and modules will be available in 5.4.5 – released in askiafield in February 2017. We will – at a later stage – introduce newer concepts – true OOP, lambda functions. Imagine two instantiated similar modules, it’s pretty much like two objects! We might have something like Dim myObject As Module1 somewhere down the line.

I also believe that we will like to add methods to Askia objects: example Array.RemoveDuplicates().

Script Remove Duplicates screenshot

Note the possible keywords Extends and This (should we call it This or Self?)

But in the meantime, what we have added should make most advanced users happier. We’d love to hear what you think and you suggest what we do next.