Saturday, May 23, 2020

Formal Methods - A usabiliy perspective [Mapping]

In my last post I started a discussion about the classic usability book "The Design of Everyday Things" by Don Norman. I talked about the concepts of "Affordances" and "Signifiers". These are the primary mechanism by which users interact with design. Affordances refer to a relationship between an object, the functions it can perform, and the operations that can be performed by a user on it to achieve these functions. Signifiers are part of the design that help users identify the presence of an affordance. Signifiers further indicate to a user "where" a particular user-action is to be performed to achieve a function. I tried to illustrate these concepts with examples illustrating a few key formal methods artifacts ("things"). 

In this post, I would like to introduce another concept -- Mapping --  used by Don Norman in his book and try interpret it in the context of formal methods.

Mapping

If signifiers are part of the design that indicate how a user can interact with an object, "Mapping"s locate and relate signifiers to the context in which a user would interact with the artifact. Many of the examples of mappings given in the book have a "spatial" flavour to them -- the layout of light switches "mapped" to the actual physical layout of lights. A good mapping allows a user to understand and predict what their action would achieve.

Let us take the example of a type-system that I introduced in my last post. The artifact under consideration is a programming language; the "affordance" is the ability to construct a program having some properties (lack of runtime-errors), and the "signifiers" are type annotations. Here, a "mapping" is the relationship between program being written and the type-annotations. 

Most programming languages have a way of annotating types in a way that follows the syntax of a program. In this sense the mapping is as close to perfect as it can get! I would like to give a couple of examples where the mapping of the type-system works well, using a couple of different programming languages.

Example 1

Below is a simple C++ function to calculate the maximum element in a list. The type annotations here are indicated in bold text.

unsigned int maxVector(std::vector<unsigned int> inputVector) {
    unsigned int max = 0;
    for(unsigned int elem : inputVector) {
        if (elem > max) max = elem;
    }
    return max;
}

In this example, it is clear (if you are aware of the basics of C++ syntax) that the function maxVector takes as input a vector of type std::vector<unsigned int> and returns an unsigned int. Equally, the for-loop in the program iterates over each element of inputVector and the conditional updates the local variable max with the maximum element seen so far. The type of max matches with the return type of the function and so the overall program is well typed and we have some assurance that it is "correct".

Example 2

A similar implementation in a functional programming language OCaml is given below. Again the type annotations are in bold text.

let maxList (ls : int list) : int  =
    let max (x: int) (y: int) : int = if (x > y) then x else y in
    List.fold_left max 0 ls   
Let us also look at situations where the mapping is not great. How many among you have struggled at one time or another with getting the types right when writing a functional program. 

When things go wrong

The utility of mapping in the case of type-systems is really seen when things go wrong; the mapping provided by the type-system to the program plays a big role in localizing, identifying and fixing the program. Consider the example below of a modified version of the examples above

Example 3

unsigned int maxVector(std::vector<unsigned int> inputVector) {
    int max = 0;
    for(unsigned int elem : inputVector) {
        if (elem > max) max = elem;
    }
    return max;
}

Compiling the example above will give the warning:

hello.cpp: In function ‘unsigned int maxVector(std::vector<unsigned int>)’:
hello.cpp:9:18: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
         if (elem > max) max = elem;
             ~~~~~^~~~~
Basically, there is a problem in the expression (elem > max) and a quick inspection of the code allows us to easily localize the problem to the declarations of elem and max, which obviously do not match: the declarations
  • int max
  • std::vector<unsigned int> inputVector
clearly map the types to variables, and allow localization of the problem to these two declarations.

Note that the warning provided by the type-system is related to the notion of "Constraints" that I will talk about in a future post.

Let us take this example further

Example 4

template <typename T>
auto maxVector(std::vector<T> inputVector) {
    auto max = 0;
    for(auto elem : inputVector) {
        if (elem > max) max = elem;
    }
    return max;
}
int main()
{
    std::vector<unsigned int> vect{1,2,3,4,56,7};
    cout<<"Hello World " << maxVector(vect) << std::endl;
    return 0;
}

In the above example, I have tried to eliminate explicit typing as far as possible to focus on the concept of mapping. Compiling the above code using C++14 compiler gives the following warnings:

hello.cpp:10:18: warning: comparison of integers of different signs: 'unsigned int' and 'int' [-Wsign-compare]
        if (elem > max) max = elem;
            ~~~~ ^ ~~~
hello.cpp:20:29: note: in instantiation of function template specialization 'maxVector<unsigned int>' requested here
    cout<<"Hello World " << maxVector(vect) << std::endl;
                            ^
Here, although we can localize the warning to the expression, the mapping of types to the expressions is not so simple -- some of the mappings are implicit because of the use of the template argument T and the use of auto
  • We have to look at the instantiation of the template to the expression maxVector(vect) and from there further localize it to the declaration std::vector<unsigned int> vect{1,2,3,4,56,7}
  • We have to use the knowledge that the literal 0 in the declaration auto max = 0 is actually interpreted by the compiler as an int
  • Finally, we can localize the problem to the mismatch between the types of vect and max.
This was a much more difficult exercise to decipher the error than Example 3, and this can be attributed to the fact that the implicit type rules in C++14, can result in much more complex mappings of the affordance of types.

A similar argument can be made in the OCaml example. In Example 2 above I have been explicit in terms of type-annotations. But OCaml (and many functional languages) are typically equipped with type-inference systems that do not require the programmer to annotate types. The following is a completely valid program that achieves the same purpose as Example 2

Example 5

let maxList ls =
    let max x y = if (x > y) then x else y in
    List.fold_left max 0 ls   
As you can see there are no type annotations to be seen anywhere -- this is a particularly complex mapping of the affordance of he type-system: it is missing altogether! 

Although type-inference allows the programmer to type less code and is arguably less "ugly" with fewer intervening annotations, the implicit mapping makes the affordance of the type-system much more difficult to use. 

I am sure many of you have at some time or another torn out your hair when trying to fix a "type-error" in your program: in ocaml, you end up adding random type-annnotations at various points in the program, and in C++ you just wilt when faced with a 8000 line error message to do with templates that expand your horizons in terms of header files that you have never heard of before!

Conclusion

In the post above, I have tried to explain the notion of mapping when applied to the affordance of types provided by a programming language -- it is something that helps you "localize" and "locate" the affordance with respect to an artifact and helps users comprehend the relation between affordances and the artifact. In many cases it is spatial, and the example given in the book of mapping is spatial in the geometric sense: 
the layout of light-switches on a switch-board mirrors the geometry of lights in the ceiling.
In the case of programming languages and type-systems it is also spatial, but not quite geometric.

Sunday, April 26, 2020

Formal Methods - A usability perspective [Affordances and Signifiers]

This is a repost of a post in https://acm-fm-blog.blogspot.com/2020/04/formal-methods-usability-perspective.html

Welcome back to the ACM FM Blog. It has been a long time (5 months!) since I posted the first of what was to be a "series of posts" on usability of formal methods. I am afraid I have not lived up to my promise and initial enthusiasm. My excuse is that I am a novice when it comes to blogging and I was trying to "complete a post" -- and have been spectacularly unsuccessful. I have since come to the conclusion out that it is probably better to post something regularly and keep a dialog going rather than waiting for a "complete" post -- even at the cost of having to retract various statements and opinions and in general embarrassing myself. So, here goes!

I have been reading the classic book "The design of everyday things" by Don Norman; and would like to share some of my thoughts as I have been going through this book. As an aside, about the reading experience itself: I have been using a combination of Kindle and Audible to go through this book. I have found that listening to Audible while simultaneously reading on Kindle gives me a much more "immersive" experience. I am sure it would be better with a physical book (I can drop it, fold it backwards, scribble on it), but I do seem to enjoy the voice component quite a bit. I would encourage you to try it as well.

The book has a number of things that we can learn about in general, but my focus was really to try and relate it to formal-methods problems and solutions. Let me start with the very first "new" word I learnt from the book - "affordance". Now, I had not come across this word till I was working on product design at MathWorks; and the way MathWorkers were using it seemed to be a fancy way to talk about a graphical widget. The typical phrases I heard were: "what is the affordance for doing xyz", "is this affordance discoverable?" etc. (I have introduced another word "discoverable" that I will cover in a later post).

A Wikipedia search gave results that were not completely easy to understand and a some parts of the entry also referred back to this book! The explanation that I liked in the book is that an "affordance" is a "relation" between an object, the functions it can perform, and the operations that can be performed by a user on it to achieve these functions. A simple example is a chair that "affords" sitting. Trust me, his is not as trivial as it sounds! Most of us would have the image of a chair (from Wikipedia) like this. This is great and it is obvious that this chair "affords" sitting. But how about this chair, also from the same Wikipedia page? I am sure you will think twice before sitting on it. Another example is this chair -- I use a chair like this regularly, and I have seen so many people trying, unsuccessfully, to figure out how to sit in this chair (BTW, this is how you sit in this chair). Yet another interesting example is this -- I have still not figured out to use this chair without injuring myself!

Anyway, the point I want to make is that all the chairs in the last paragraph "afford" sitting. What they differ in how the design of the chair "signifies" the function it can perform to you (a user). A "signifier" is also a term introduced in the book as a mechanism for communicating an affordance which may otherwise not be perceived by the user. If an affordance is not perceived by a user, how can it be usable? Signifiers play an important role in the design of everyday things!

With the chairs, an interesting aspect to note is that "signifiers" are not completely context-free -- I am sure there are some of you for whom the way to use the different chairs is obvious: you have some background knowledge and are able to use the chair with no problems. This problem of "knowledge" is also addressed later in the book and I will not talk much about it now (another topic for a future post!)

Now let us apply this to formal methods and the work we are all involved in as a community. What are some examples of "affordances" and "signifiers" that we can associate with? The answers are not immediately obvious, but some examples I would like to briefly discuss are:
  • A function "affords" application, and a relation "affords" satisfaction
    • The signifier for this is just the mathematical existence of the function/relation itself 
  • A finite-state automata "affords" a regular-language (and vice-versa)
    • The signifier is the fact that you can follow arrows in the FSM bubble diagram and this leads to a language -- the fact that this is always a regular language is not at all obvious and is a deep result!
  • A type-system "affords" programs that avoids run-time errors (sometimes!)
    • The "signifier" of a type-system are the type-annotations that a programming language requires (or infers). Again the fact that this will ensure programs that do not have run-time errors is not at all obvious (or even true!)
  • A program "affords" satisfaction of a specification
    • Here the affordance is not so obvious: the object is a pair of program and specification. Together they beg the question of satisfaction: this is the utility/function of the object -- to demonstrate the satisfaction relation. This function itself it not at all trivial and I do not think there is any "signifier" for this. Maybe this needs to be broken up into smaller pieces.
  • A proof-tree "affords" a check that it is well-formed and is a proof in a particular logic
    • The signifier here is the structure of a proof-tree -- these are generally designed to indicate the inferences performed, and the rule used for each inference. The function of being checked is "afforded" by the tree-structure of the proof.
Now some of the above examples may seem a bit artificial, but I believe this is an exercise worth doing. It will focus our minds on who the users of our work are, what they are trying to achieve and how we are empowering them to achieve their goals. I believe that thinking through these concepts will help us get clarity on the value we provide  as a community and help more effective communication of this to the rest of the world.

In the above examples I have avoided using tools and algorithms that are based on formal-methods -- I wanted to focus on the core artifacts that we design and use in formal methods themselves rather than the program that executes them. The affordance of a program is to "execute it", and as a community I feel we have focused on coming up with "magic" algorithms which solve difficult problems; and have struggled to explain this to the rest of the world. For the world, the main affordance of a program is to execute it -- and this does not help explain the problem that is solved!

I hope this post directs you to think a little bit about affordances and signifiers. Do you have examples in your mind that you could share? I would love to hear your ideas.