Skip to main content

An Introduction to Task Oriented Programming

  • Chapter
  • First Online:

Part of the book series: Lecture Notes in Computer Science ((LNTCS,volume 8606))

Abstract

Task Oriented Programming (or shortly TOP) is a new programming paradigm. It is used for developing applications where human beings closely collaborate on the internet to accomplish a common goal. The tasks that need to be done to achieve this goal are described on a very high level of abstraction. This means that one does need to worry about the technical realization to make the collaboration possible. The technical realization is generated fully automatically from the abstract description. TOP can therefore be seen as a model driven approach. The tasks described form a model from which the technical realization is generated.

This paper describes the iTask system which supports TOP as an Embedded Domain Specific Language (EDSL). The host language is the pure and lazy functional language Clean.

Based on the high level description of the tasks to do, the iTask system generates a web-service. This web-service offers a web interface to the end-users for doing their work, it coordinates the tasks being described, and it provides the end-users with up-to-date information about the status of the tasks being performed by others.

Tasks are typed, every task processes a value of a particular type. Tasks can be calculated dynamically. Tasks can be higher order: the result of a task may be a newly generated task which can be passed around and be assigned to some other worker later on. Tasks can be anything. Also the management of tasks can be expressed as a task. For example, commonly there will be many tasks assigned to someone. A task, predefined in the library for convenience, offers the tasks to do to the end-user much like an email application offers an interface to handle emails. This enables the end-user to freely choose which tasks to work on. However, one can define other ways for managing tasks.

A new aspect of the system is that tasks have become reactive: a task does not deliver one value when the task is done, but, while the work takes place, it constantly produces updated versions of the task value reflecting the progress of the work taken place. This current task value can be observed by others and may influence the things others can see or do.

This is a preview of subscription content, log in via an institution.

Buying options

Chapter
USD   29.95
Price excludes VAT (USA)
  • Available as PDF
  • Read on any device
  • Instant download
  • Own it forever
eBook
USD   39.99
Price excludes VAT (USA)
  • Available as EPUB and PDF
  • Read on any device
  • Instant download
  • Own it forever
Softcover Book
USD   54.99
Price excludes VAT (USA)
  • Compact, lightweight edition
  • Dispatched in 3 to 5 business days
  • Free shipping worldwide - see info

Tax calculation will be finalised at checkout

Purchases are for personal use only

Learn about institutional subscriptions

References

  1. Kernighan, B., Ritchie, D.: The C Programming Language, 2nd edn. Prentice Hall, Englewood Cliffs (1988)

    Google Scholar 

  2. Plasmeijer, R., Achten, P., Koopman, P.: iTasks: executable specifications of interactive work flow systems for the web. In: Hinze, R., Ramsey, N. (eds.) Proceedings of the International Conference on Functional Programming, ICFP 2007, Freiburg, Germany, pp. 141–152. ACM Press (2007)

    Google Scholar 

  3. Lijnse, B., Plasmeijer, R.: iTasks 2: iTasks for end-users. In: Morazán, M.T., Scholz, S.-B. (eds.) IFL 2009. LNCS, vol. 6041, pp. 36–54. Springer, Heidelberg (2010)

    Chapter  Google Scholar 

  4. van der Heijden, M., Lijnse, B., Lucas, P.J.F., Heijdra, Y.F., Schermer, T.R.J.: Managing COPD exacerbations with telemedicine. In: Peleg, M., Lavrač, N., Combi, C. (eds.) AIME 2011. LNCS, vol. 6747, pp. 169–178. Springer, Heidelberg (2011)

    Chapter  Google Scholar 

  5. Jansen, J., Lijnse, B., Plasmeijer, R.: Towards dynamic workflows for crisis management. In: French, S., Tomaszewski, B., Zobel, C. (eds.) Proceedings of the 7th International Conference on Information Systems for Crisis Response and Management, ISCRAM 2010, Seattle, WA, USA, May 2010

    Google Scholar 

  6. Lijnse, B., Jansen, J., Nanne, R., Plasmeijer, R.: Capturing the Netherlands coast guard’s SAR workflow with iTasks. In: Mendonca, D., Dugdale, J. (eds.) Proceedings of the 8th International Conference on Information Systems for Crisis Response and Management, ISCRAM 2011, Lisbon, Portugal. ISCRAM Association, May 2011

    Google Scholar 

  7. Meijer, E.: Server side web scripting in Haskell. J. Funct. Program 10(1), 1–18 (2000)

    Article  MathSciNet  Google Scholar 

  8. Hanus, M.: High-level server side web scripting in Curry. In: Ramakrishnan, I.V. (ed.) PADL 2001. LNCS, vol. 1990, pp. 76–92. Springer, Heidelberg (2001)

    Chapter  Google Scholar 

  9. Elsman, M., Larsen, K.F.: Typing XHTML web applications in ML. In: Jayaraman, B. (ed.) PADL 2004. LNCS, vol. 3057, pp. 224–238. Springer, Heidelberg (2004)

    Chapter  Google Scholar 

  10. Elsman, M., Hallenberg, N.: Web programming with SMLserver. In: Dahl, V. (ed.) PADL 2003. LNCS, vol. 2562, pp. 74–91. Springer, Heidelberg (2002)

    Chapter  Google Scholar 

  11. Thiemann, P.: WASH/CGI: server-side web scripting with sessions and typed, compositional forms. In: Adsul, B., Ramakrishnan, C.R. (eds.) PADL 2002. LNCS, vol. 2257, p. 192. Springer, Heidelberg (2002)

    Chapter  Google Scholar 

  12. Serrano, M., Gallesio, E., Loitsch, F.: Hop, a language for programming the web 2.0. In: Proceedings of the 11th International Conference on Object-Oriented Programming, Systems, Languages, and Applications, OOPSLA 2006, Portland, Oregon, USA, 22–26 October 2006, pp. 975–985 (2006)

    Google Scholar 

  13. Loitsch, F., Serrano, M.: Hop client-side compilation. In: Proceedings of the 7th Symposium on Trends in Functional Programming, TFP 2007, New York, NY, USA, Interact, 2–4 April 2007, pp. 141–158 (2007)

    Google Scholar 

  14. Cooper, E., Lindley, S., Yallop, J.: Links: web programming without tiers. In: de Boer, F.S., Bonsangue, M.M., Graf, S., de Roever, W.-P. (eds.) FMCO 2006. LNCS, vol. 4709, pp. 266–296. Springer, Heidelberg (2007)

    Chapter  Google Scholar 

  15. Cooper, E., Lindley, S., Wadler, P., Yallop, J.: An idiom’s guide to formlets. Technical report, The University of Edinburgh, UK (2007). http://groups.inf.ed.ac.uk/links/papers/formlets-draft2007.pdf

  16. van der Aalst, W., ter Hofstede, A., Kiepuszewski, B., Barros, A.: Workflow patterns. Technical Report FIT-TR-2002-02, Queensland University of Technology (2002)

    Google Scholar 

  17. Hemel, Z., Verhaaf, R., Visser, E.: WebWorkFlow: an object-oriented workflow modeling language for web applications. In: Czarnecki, K., Ober, I., Bruel, J.-M., Uhl, A., Völter, M. (eds.) MODELS 2008. LNCS, vol. 5301, pp. 113–127. Springer, Heidelberg (2008)

    Chapter  Google Scholar 

  18. Crandall, B., Klein, G., Hoffman, R.R.: Working Minds: A Practitioner’s Guide to Cognitive Task Analysis. MIT Press, Cambridge (2006)

    Google Scholar 

  19. Plasmeijer, R., van Eekelen, M.: Clean language report (version 2.1) (2002). http://clean.cs.ru.nl

Download references

Acknowledgements

The authors wish to thank the reviewers for their constructive feedback.

Author information

Authors and Affiliations

Authors

Corresponding author

Correspondence to Rinus Plasmeijer .

Editor information

Editors and Affiliations

A Functional Programming in Clean

A Functional Programming in Clean

This section gives a brief overview of functional programming in Clean [19]. Clean is a pure lazy functional programming language. It has many similarities with Haskell.

1.1 A.1 Clean Nutshells

This section contains a set of brief overviews of topics in Clean. These overviews should be short enough to read while studying other parts of this paper without loosing the flow of those parts. The somewhat experienced functional programmer is introduced to particular syntax or language constructs in Clean.

Modules. A module with name \(M\) is represented physically by two text files that reside in the same directory: one with file name \(M\) .dcl and one with file name \(M\) .icl.

The \(M\) .icl file is the implementation module. It contains the (task) functions and data type definitions of the module. Its first line repeats its name:

figure cp

An implementation module can always use its own definitions. By importing other modules, it can use the definitions that are made visible by those modules as well:

figure cq

The \(M\) .dcl file is the definition module. It contains \(M\)’s interface to other modules. The first line of a definition module also gives its name:

figure cr

A definition module basically serves two purposes.

  • It exports identifiers of its own implementation module by repeating their signature. Hence, identifiers which signatures are not repeated are cloaked for other modules.

  • It acts as a serving-hatch for identifiers that are exported by other modules by importing their module names. In this way you can create libraries of large collections of related identifiers.

Operators. Operators are binary (two arguments) functions that can be written in infix style (between its arguments) instead of the normal prefix style (before its arguments). Operators are used to increase readability of your programs. With an operator declaration you associate two other attributes as well. The first attribute is the fixity which indicates in which direction the binding power works in case of operators with the same precedence. It is expressed by one of the keywords infixl, infix, and infixr. The second attribute is its precedence which indicates the binding power of the operator. It is expressed as an integer value between 0 and 9, in which a higher value indicates a stronger binding power.

The snapshot below of common operators as defined in the host language Clean illustrates this.

figure cs

(These operators are overloaded to allow you to instantiate them for your own types.) Due to the lower precedence of ==, the expression \(x\) + \(y\) == \(y\) + \(x\) must be read as \((x\) + \(y)\) == \((y\) + \(x)\). Due to the fixities, the expression \(x\) - \(y\) - \(z\) must be read as \((x\) - \(y)\) - \(z\), and \(x\) \(y\) \(z\) as \(x\) (\(y\) \(z\)). In case of expressions that use operators of the same precedence but with conflicting fixities you must work out the correct order yourself using brackets ( ).

Guards. Pattern matching is an expressive way to perform case distinction in function alternatives, but it is limited to investigating the structure of function arguments. Guards extend this with conditional expressions. Here are two examples.

figure ct

In sign, the first alternative matches only if the argument evaluates to the value 0. In that case, sign results in the value 0. The second alternative imposes no pattern restrictions, but it does have a guard . Even though the pattern always matches, evaluation of the guard must result in True if the second alternative of sign is to be chosen. Therefor, the value -1 is returned only if the argument is a negative number. Finally, the last alternative has neither a pattern restriction nor a guarded restriction, and therefor matches all remaining cases, which concern the positive numbers. In those cases, the result is 1.

The implementation of for Date values illustrates nested guards. In contrast with top-level guards, nested guards must be completed with otherwise to catch any remaining cases. The otherwise keyword can also be used in top-level guards, as is shown on the last line of the function. The function first checks the guard on line 3 and returns True if the first year field is smaller than the second year field. If the guard evaluates to False, then the second guard on line 4 is tested. In case of equal year field values, evaluation continues with the nested guards on lines 5–7 that inspect the month fields. If the first nested guard on line 5 evaluates to True, then the comparison also yields True. In case of a False result, the second nested guard on line 6 is tested. In case of equal month field values, the comparison of the day values provides the final answer. Finally, to complete the nested guards, the last case on line 7 concludes that the first argument is not smaller than the second, a conclusion that is shared by the last top-level guard on line 8.

Choice and Pattern Matching. In Example 8 the function first Year Possible uses pattern matching to relate values of type Medium with year values. The enterYear function uses if to determine whether or not the user’s input is valid. Unlike most programming languages, in which an if-then-else construct is supported in the language, it can be straightforwardly incorporated as a function in a lazy functional language, using pattern matching as well. Let’s examine the type and implementation of if:

figure cu

The type tells you that the Bool argument is strict in if: it must always be evaluated in order to know whether its result is True or False. The implementation uses the evaluation strategy of the host language to make the choice effective. The if function has two alternatives, each indicated by repeating the function name and its arguments. Alternatives are examined in textual order, from top to bottom. Up until now the arguments of functions were only variables, but in fact they are patterns. A pattern \(p\) is one of the following.

  • A variable, expressed by means of an identifier that starts with a lowercase character or simply the _ wildcard symbol in case the variable is not used at all. A variable identifies and matches any computation without forcing evaluation. Within the same alternative, the variable identifiers must be different.

  • A constant in the language, such as 0, False, 3.14, , and . To match successfully, the argument is evaluated fully to determine whether it has exactly the same constant value.

  • A composite pattern, which is either a tuple ( \(p_1\) , ..., \(p_n\) ), a data constructor ( \(d\) \(p_1\) ... \(p_n \) ) where \(n\) is the arity of \(d\), a record \(f_1\) = \(p_1\) , ..., \(f_n\) = \(p_n\) , or a list [ \(p_1\) , ..., \(p_n\) ] or [ \(p_1\) , ..., \(p_n\) : \(p_{n+1}\) ]. Matching proceeds recursively to each part that is specified in the pattern. In case of records, only the mentioned record fields are matched. In case of lists, \(p_1\) upto \(p_n\) are matched with the first \(n\) elements of the list, if present, and \(p_{n+1}\) with the remainder of the list.

Patterns control evaluation of arguments until it is discovered that it either matches or not. Only if all patterns in the same alternative match, computation proceeds with the corresponding right-hand side of that alternative; otherwise computation proceeds with the next alternative.

Hence, in the case of if its second argument is returned if the evaluation of the first argument results in True. If it results in False the second alternative is tried. Because it does not impose any restriction, and hence also causes no further evaluation, it matches, and the third argument is returned.

In firstYearPossible the data constructors are also matched from top to bottom. The last case always matches, and returns the value 0.

List Comprehensions. Lists are the workhorse of functional programming. List comprehensions allow you to concisely express list manipulations. Their simplest form is:

[ \(e\) \(p\) \(g\) ]

Generator \(g\) is an expression that is or yields a list. (Note that \(g\) can also evaluate to an array. In that case you need to use instead of to extract array elements.) From the generator, values are extracted from the front to the back. Each value is matched with the pattern \(p\). If this succeeds, then the pattern variables in \(p\) are bound to the corresponding parts of the extracted value, and expression \(e\), that typically uses these bound pattern variables, yields an element of the result list. If matching fails, then the next element of the generator is tried.

Besides the pattern \(p\), elements can also be selected using a guarded condition:

[ \(e\)

figure cv

\(p\) \(g\) | \(c\) ]

Here, \(c\) is a boolean expression that can use any of the pattern variables that are introduced at generator patterns to its left. For each extracted value from the sequence for which the pattern match succeeds, the guarded condition is evaluated. Only if the condition also evaluates to True, a list element is added.

It is possible to use several pattern-generator pairs \(p\) \(g\) in one list comprehension. They are combined either in parallel with the & symbol or as a cartesian product with the , symbol.

  • In \(p_1\) \(g_1\) & \(p_2\) \(g_2\), values are extracted from \(g_1\) and \(g_2\) at the same index positions and matched against \(p_1\) and \(p_2\) respectively. The shortest generator determines termination of this value-extraction process.

  • In \(p_1\) \(g_1\) , \(p_2\) \(g_2\), for each extracted value from \(g_1\) that matches \(p_1\) all values from \(g_2\) are extracted and matched against \(p_2\).

Each and every one of the above ways to manipulate lists is already very expressive. However, they can be combined in arbitrary ways. This can be daunting at times, but once you get used to the expressive power, list comprehensions often prove to be the best tool for list processing tasks.

\(\varvec{\lambda }\) -Abstractions. Lambda-abstractions \(x\) -> \(e\) allow you to introduce anonymous functions ‘on the spot’. They typically occur in situations where an ad hoc function is required, for which it does not make much sense to come up with a separate function definition. This frees you from thinking of a proper identifier and perhaps a type signature as well. The bind combinator >>= is an excellent example of such a situation because in general you need to give a name \(x\) to the task value of the first task, and want to give an expression \(e\) that uses \(x\). If you weren’t interested in \(x\), you would have used the naïve then combinator >>| instead.

Modelling Side-Effects. In a pure functional programming language all results must be explicit function results. This implies that a changed state should also be a function result. The type of the Start function in Example 1 is *World -> *World, this indicates that it changes the world. There are two things worth noting at this moment:

  • The basic type World is annotated with the uniqueness attribute *. In a function type any argument can be annotated with this attribute. This enforces the property that whenever the function is evaluated, it has the sole reference to the corresponding argument value. This is useful because it allows the function implementation to destructively update that value without compromising the semantics of the functional programming language. This can only be done if the function body itself does not violate this uniqueness property. This is checked statically.

  • The basic type World represents the ‘external’ environment of a program. If the Start function has an argument, the language assumes that it is of type World. The language provides no other means to create a value of type World, so if an application is to do any interaction with the external environment, it must have a Start function with a uniquely attributed World argument.

Incorporating side-effects safely in a functional language has received a lot of attention in the functional language research community. For lazy functional languages a host of techniques has been proposed. Well-known examples are monads, continuations, and streams. For eager functional languages, the situation is less complicated because in these languages programs exhibit an execution order that is more predictable.

Signatures. A signature \(x\) :: \(t\) declares that identifier \(x\) has type \(t\). An identifier \(x\) starts with a lowercase or uppercase letter and has no whitespace characters. The type \(t\) can be either of the following forms.

  • It is one of the basic types, which are: Bool, Int, Real, Char, String, File, and World.

  • It is a type variable. Their identifiers start with a lowercase character.

  • It is a composite type, using one of the language type constructors [ ], , (,), and -> .

    • If \(t\) is a type, then [ \(t\) ] is the list-of- \(t\) type.

    • If \(t\) is a type, then \(t\) is the array-of- \(t\) type.

    • If \(t_1\) and \(t_2\) are types, then ( \(t_1\) , \(t_2\) ) is the tuple-of- \(t_1\) -and- \(t_2\) type. This generalizes to \(t_1\) upto \(t_n\) with \(2 \le n \le 32\), separating each type by ,. Hence, ( \(t_1\) , \(t_2\) , \(t_3\) ), ( \(t_1\) , \(t_2\) , \(t_3\) , \(t_4\) ) and so on are also tuple types.

    • If \(t_1\) and \(t_2\) are types, then \(t_1\) -> \(t_2\) is the function-of- \(t_1\) -to- \(t_2\) type. This generalizes to \(t_1\)...\(t_n\) -> \(t_{n+1}\), where \(t_1\)...\(t_n\) are the argument types, and \(t_{n+1}\) is the result type. The function argument types are separated by whitespace characters. So, \(t_1\) \(t_2\) -> \(t_3\), \(t_1\) \(t_2\) \(t_3\) -> \(t_4\) and so on are also function types.

  • It is a custom defined type, using either an algebraic type or a record type. Their type names are easily recognized because they always start with an uppercase character. Examples of algebraic and record types can be found in Sect. 3.3.

Signatures can be overloaded, in which case they are extended with one or more overloading constraints, resulting in \(x\) :: \(t\) | \( tc _1~a_1\) & ...& \( tc _n~a_n\). A constraint \( tc _i~a_i\) is a pair of a type class \( tc _i\) and a type variable \(a_i\) that must occur in \(t\). Note that \( tc _1~a\) & \( tc _2~a\) & ...& \( tc _n~a\) can be shorthanded to \( tc _1\) , \( tc _2\) , ..., \( tc _n~a\).

Overloading. Overloading is a common and useful concept in programming languages that allows you to use the same identifier for different, yet related, values or computations. In the host language Clean overloading is introduced in an explicit way: if you wish to reuse a certain identifier \(x\), then you declare it via a type class:

class \(x~a_1~\ldots ~a_n\) :: \(t\)

with the following properties:

  • the type variables \(a_1~\ldots ~a_n~(n > 0)\) must be different and start with a lowercase character;

  • the type scheme \(t\) can be any type that uses the type variables \(a_i\).

This declaration introduces the type class \(x\) with the single type class member \(x\). It is possible to declare a type class \(x\) with several type class members \(x_1\) ... \(x_k\):

class \(x~a_1~\ldots ~a_n\)

figure cw

It is customary, but not required, that in this case identifier \(x\) starts with an uppercase character. The identifiers \(x_i\) need to be different, and their types \(t_i\) can use any of the type variables \(a_i\).

Type classes can be instantiated with concrete types. This must always be done for all of its type variables and all type class members. The general form of such an instantiation is:

instance \(x~t'_1~\ldots ~t'_n~|~ tc _1~b_1\) & ...& \( tc _m~b_m\)

where ...

with the following properties:

  • the types \(t'_1~\ldots ~t'_n\) are substituted for the type variables \(a_1~\ldots ~a_n\) of the type class \(x\). They are not required to be different but they are not allowed to share type variables;

  • the types \(t'_i\) can be overloaded themselves, in which case their type class constraints \( tc _i~b_i\) are enumerated after \(|\) (which is absent in case of no constraints). The type variable \(b_i\) must occur in one of the types \(t'_i\);

  • the where keyword is followed by implementations of all class member functions. Of course, these implementations must adhere to the types that result after substitution of the corresponding type schemes \(t_i\).

Algebraic and \(\varvec{\exists }\) -Types. The BoxedTask type in Fig. 6 is an example of an algebraic type that is existentially quantified. Algebraic types allow you to introduce new constants in your program, and give them a type at the same time. The general format of an algebraic type declaration is:

:: \(t\) \(a_1\) ... \(a_m\) = \(d_1\) \(t_{11}\) ... \(t_{1c_1}\) | ...| \(d_n\) \(t_{n1}\) ...\(t_{nc_n}\)

with the following properties:

  • the type constructor \(t\) is an identifier that starts with an uppercase character;

  • the type variables \(a_i~(0 \le i \le m)\) must be different and start with a lowercase character;

  • the data constructors \(d_i~(1 \le i \le n)\) must be different and start with an uppercase character;

  • the data constructors can have zero or more arguments. An argument is either one of the type variables \(a_i\) or a type that may use the type variables \(a_i\).

From these properties it follows that all occurrences of type variables in data constructors (all right hand side declarations) must be accounted for in the type constructor (on the left hand side). With existential quantification it is possible to circumvent this: for each data constructor one can introduce type variables that are known only locally to the data constructor. A data constructor can be enhanced with such local type variables in the following way:

E. \(b_1\) ... \(b_k\) : \(d_i\) \(t_{i1}\) ... \(t_{ic_i}\) & \(tc_1~x_1\) & ...& \(tc_l~x_l\)

with the following properties:

  • the type variables \(b_j~(0 \le j \le k)\) must be different and start with a lowercase character;

  • the arguments of the data constructor \(d_i\) can now also use any of the existentially quantified type variables \(b_i\);

  • the pairs \(tc~x\) are type class constraints, in which \(tc\) indicates a type class and \(x\) is one of the existentially quantified type variables \(b_i\).

From these properties it follows that it does not make sense to introduce an existentially quantified type variable in a data constructor without adding information how values of that type can be used. There are basically two ways of doing this. The first is to add functions of the same type that handle these encapsulated values (in a very similar way to methods in classes in object oriented programming). The second is to constrain the encapsulated type variables to type classes.

Record Types. Record types are useful to create named collections of data. The parts of such a collection can be referred to by means of a field name. The general format of a record type declaration is:

:: \(t\) \(a_1\) ... \(a_m\) =  \(r_1\) :: \(t_1\) , ..., \(r_n\) :: \(t_n\)

with the following properties:

  • the type constructor \(t\) is an identifier that starts with an uppercase character;

  • the type variables \(a_i~(0 \le i \le m)\) must be different and start with a lowercase character;

  • the pairs \(r_i\) :: \(t_i~(1 \le i \le n)\) determine the components of the record type. The field names \(r_i\) must be different and start with a lowercase character. The types \(t_i\) can use the type variables \(a_i\).

Just like algebraic types, record types can also introduce existentially quantified type variables on the right-hand side of the record type. However, unlike algebraic types, their use can not be constrained by means of type classes. Hence, if you need to access these encapsulated values afterwards, you need to include function components within the record type definition.

Disambiguating Records. Within a program record field names are allowed to occur in several record types (the corresponding field types are allowed to be different). This helps you to choose proper field names, without worrying too much about their existence in other records. The consequence of this useful feature is that once in a while you need to explicit about the record value that is created (in case of records with exactly the same set of record field names) and when using record field selectors (either in a pattern match or with the . field notation). Type constructor names are required to be unique within a program, hence they are used to disambiguate these cases.

  • When creating a record value, you are obliged to give a value to each and every record field of that type. If a record has a field with a unique name, then it is clear which record type is intended. Only if two records have the same set of field names, you need to include the type constructor name \(t\) within the record value definition.

    ...  \(t\) | \(f_1\) = \(e_1\) , ..., \(f_n\) = \(e_n\)  ...

  • If a record pattern has at least one field with a unique name, then it is clear which record type is intended. The record pattern is disambiguated by including the type constructor name \(t\) in the pattern in an analogous way as described above when creating a record value, except that you do not need to mention all record fields and that the right hand sides of the fields are patterns rather than expressions:

    ...  \(t\) | \(f_1\) = \(p_1\) , ..., \(f_n\) = \(p_n\)  ...

  • If a record field selection \(e\) . \(f\) uses a unique field name \(f\), then it is clear which record type is intended. A record field selection can be disambiguated by including the type constructor name \(t\) as a field selector. Hence, \(e\) . \(t\) . \(f\) states that field \(f\) of record type \(t\) must be used.

Record Updates. Record values are defined by enumerating each and every record field, along with a value. Example 5 shows that new record values can also be constructed from old record values. If \(r\) is a record (or an expression that yields a record value), then a new record value can be created by specifying only what record fields are different. The general format of such a record update is:

\(r\) & \(f_1\) = \(e_1\) , ..., \(f_n\) = \(e_n\)

This expression creates a new record value that is identical to \(r\), except for the fields \(f_i\) that have values \(e_i\) (\(0 < i \le n\)) respectively. A record field should occur at most once in this expression.

Synonym Types. Synonym types only introduce a new type constructor name for another type. The general formal of a type synonym declaration is:

:: \(t'\) \(a_1\) ... \(a_n\) :== \(t\)

with the following properties:

  • the type constructor \(t'\) is an identifier that starts with an uppercase character;

  • the type variables \(a_i~(0 \le i \le n)\) must be different and start with a lowercase character;

  • the type \(t\) can be any type that uses the type variables \(a_i\). However, a synonym type is not allowed to be recursive, either directly or indirectly.

Synonym types are useful for documentation purposes of your model types, as illustrated in Example 4. Although the name \(t'\) must be new, \(t'\) does not introduce a new type: it is completely exchangeable with any occurrence of \(t\).

Strictness. In the signature of the basic task function return the first argument is provided with a strictness annotation, !. Recall that iTask is embedded in Clean, which is a lazy language. In a lazy language, computation is driven by the need to produce a result. As a simple example, consider the function const that does nothing but return its first argument:

figure cx

There is absolutely no need for const to evaluate argument y to a value. However, argument x is returned by const, so its evaluation better produces a result or otherwise const x y won’t produce a result either.

The more general, and more technical, way of phrasing this is the following. Suppose we have a function \(f\) that has a formal argument \(x\). Let \(e\) be a diverging computation (it either takes infinitely long or aborts without producing a result). If \((f~e)\) also diverges, then argument \(x\) is said to be strict in \(f\). Note that this is a property of the function, and not of the argument. In case of const, it is no problem that argument y might be a diverging computation because it is not needed by const to compute its result. The consequence is that with respect to termination properties, it does not matter if strict function arguments are evaluated before the function is called. In many cases, this increases the performance of the application because you do not need to maintain suspended computations (due to lazy evaluation), but instead can evaluate them to a result and use that instead.

The strictness property of function arguments is expressed in the function signature by prefixing the argument that is strict in that function with the ! annotation. In case of const, its signature is:

figure cy

Rights and permissions

Reprints and permissions

Copyright information

© 2015 Springer International Publishing Switzerland

About this chapter

Cite this chapter

Achten, P., Koopman, P., Plasmeijer, R. (2015). An Introduction to Task Oriented Programming. In: Zsók, V., Horváth, Z., Csató, L. (eds) Central European Functional Programming School. CEFP 2013. Lecture Notes in Computer Science(), vol 8606. Springer, Cham. https://doi.org/10.1007/978-3-319-15940-9_5

Download citation

  • DOI: https://doi.org/10.1007/978-3-319-15940-9_5

  • Published:

  • Publisher Name: Springer, Cham

  • Print ISBN: 978-3-319-15939-3

  • Online ISBN: 978-3-319-15940-9

  • eBook Packages: Computer ScienceComputer Science (R0)

Publish with us

Policies and ethics