Mixing data types

While drafting clauses in ClauseBase is certainly not “programming” in the traditional sense, ClauseBase borrows some best-practices from traditional programming languages. Legal experts who have never done any traditional programming are often confused by these practices.

One such issue is the concept of data types — aka the issue of not mixing apples and oranges. This page deliberately takes a deep-dive into dealing with these types, to fully explain how they work in ClauseBase.

Natural language versus conditions & calculations

A first important idea to grasp is the distinction between natural language mode and conditions/calculations mode.

The standard mode in ClauseBase is the natural language mode. This is the mode you will implicitly arrive in when starting to type something in a clause. It will consist of “human” language stuff, such as words in English/French/Dutch, punctuation and numbering, etc. For example, the following clause entirely consists of the natural language mode elements:

A clause that consists entirely of natural language mode elements will be completely static, i.e. aside from the numbering and formatting of the clause, this clause will not change appearance. To insert dynamic elements, you insert conditions and calculations, for example:

As soon as you type in an opening accolade { you will leave the natural language mode, and enter either

  • the condition mode, for which the result’s type is always true or false); or
  • the calculation mode (for which the result’s type depends on the kind of calculation being made).

In the example above, #contract^value < 500 EUR and #contract^value < 2000 EUR are written in the condition mode, while apples^amount * #apples^unit-price is written in the calculation mode.

After the closing accolade }you will in any case leave these modes, and return to the natural language mode. However, the switch between the modes is more complex than just looking at the opening and closing accolade. For example, in paragraph 1. above, the parts between the colon (:) and the vertical bar (|) are once again in the natural language mode, i.e. the words low and medium and high.

Calculation mode

As the name implies, calculation mode expects to perform calculations. The input to these calculations consists of a mix of countable elements and optional operators — e.g. 5 + 24 or 5 * 4.5 EUR, but also 12 months * 4 or 2020_11_23 + 3 days, because you can perform interesting calculations with durations and dates.

Instead of using directly countable elements, you can also work with elements that ultimately boil down to numbers. For example, in apples^amount * #apples^unit-price, the datafields “amount” and “unit-price” can be assumed to contain countable things. When, for example, the number 5 would be assigned to apples^amount and the #apples^unit-price would be assigned the value 0.4 EUR, ClauseBase will happily perform the calculation for you and return the result (2 EUR).

Note that natural text cannot, as such, be used in calculation mode. After all, text is — as such such — not countable. It does not make sense to, for example, perform a calculation to divide the words “the Buyer buys an apple” by the words “the Seller sells a pear”.

Condition mode

Writing in the condition mode will be writing in calculation mode, with one exception. Condition mode is meant to ultimately result in true or false, and will therefore contain a left side with a calculation, a comparison operator and a right side with yet another calculation.

For example, in the condition apples^amount * #apples^unit-price < 2000 EUR, you will see a left side (containing a calculation on apples), a comparison-operator (smaller than <), and a right side (with a single currency-value).

Data types

In ClauseBase, as in most programming languages, you can only calculate with elements of the same data type, and you can only compare elements of the same data type. For example, it does not make sense to add 25 EUR to a date, or to compare the text “alpha” to the duration of 5 months.

ClauseBase offers the following basic data types:

Data typeExamples
whole number0; 5; 21456
floating point number4.56; 2457.8975
currency number0 EUR; 56.34 EUR
text"alpha"; "Main Street 56 Brussels"
date5 January 2020; 8 July 2022
duration3 years; 4 months; 2 days
true/falsetrue

In addition, the following special data types are also offered.

Data typeExample
list of elements (any of the other types allowed)5, 6 EUR, 3 January 2020
clause text1. Alpha
* Beta
** Gamma
clause part@ALPHA
defined term#contract
datafield reference#contract^value
nothing / undefined

The three different modes result in three different data types:

ModeResult
natural language modealways results in clause text
calculation moderesult type will depend on the elements used — e.g. 3 + 3 results in a whole number
condition modealways results in true/false

Supported currencies

ClauseBase currently supports the following currencies:

CurrencySymbol used in ClauseBase
eurosEUR
British poundGBP
United States dollarUSD
Japanese YenJPY
Australian dollarAUD
Canadian dollarCAD
Swiss francCHF
yuanCNY
Kenyan shillingKSH
Hong Kong dollarHKD
New Zealand dollarNZD
Swedish kronaSEK
South Korean wonKRW
Singapore dollarSGD
Norwegian kroneNOK
Mexican pesoMXN
Indian rupeeINR
Russian rubleRUB
South African randZAR
Turkish liraTRY
Brazilian realBRL
New Taiwan dollarTWD
Danish kroneDKK
Polish złotyPLN
Thai bahtTHB
Indonesian rupiahIDR
Hungarian forintHUF
Czech korunaCZK
Israeli shekelILS
Chilean pesoCLP
Phillipine pesoPHP
UAE dirhamAED
Colombian pesoCOP
Saudi riyalSAR
Malaysian ringgitMYR
Romanian leuRON

Conversions between data types

While it is not possible to perform calculations or comparisons with elements of different data types, it is possible to convert an element to another data type. This conversion can either be done explicitly or implicitly.

Explicit conversions

Explicit conversions can be achieved using special functions. For example, the special function @extract-number will take a currency number (e.g., 5.5 EUR) and return the floating point number (5.5). Similarly, the sole purpose of the special function @str is to convert the argument passed to it into text — e.g. @str(3 months) will, depending on the styling settings, result in the text 3 months when the active language is English.

At first glance, this may look like nothing changed; under the hood, however, the type of this element changed from a duration to a text, meaning that the operations that can be applied to a duration element (e.g., addition or subtraction) will suddenly no longer work, while the operations that can be applied to a text element (e.g., capitalization) will suddenly become possible.

If the active language is not English, the conversion will be much more visible. For example, @str(3 months) may also be converted to “trois (3) mois” in French, with a styling setting where numbers are always converted to both letters and numbers.

Most special functions will perform something more than solely converting an argument from one data type to another data type. Examples:

  • @round and @floor will convert the floating point number passed to them, and then either round that number or truncate the decimal part, returning a whole number
  • @comma-split will take a text element and return a list element that contains individual text elements (e.g. @comma-split("alpha, beta, gamma") will return a list with text elements "alpha", "beta" and "gamma".

Implicit conversions of basic data types

It would be cumbersome to always have to explicitly convert data types — e.g., imagine that in order to take 5.5 times the duration of a contract, you would have to write 5.5 * @float(@months-in(#contract^duration))

The underlying reasoning is as follows: you are performing a multiplication of a floating point number and a duration, which is not possible. There exists a special function @months-in that takes a duration and returns a whole number that corresponds to the amount of months in that duration. However, you would then have to perform yet another conversion from this whole number to the floating point number. You would therefore have to use the hypothetical @float function.

To avoid these situations, ClauseBase will implicitly perform conversions for you when no ambiguity is involved:

Data type 1Data type 2Resulting data typeExample
whole nrfloating point nrfloating point nr5 + 5.5 results in 10.5
whole nrcurrency nrcurrency nr200 + 300 EUR results in 500 EUR
floating point nrcurrency nrcurrency nr229.5 + 300 EUR results in 529.5 EUR
date (*)durationdate2018_7_12 + 1 month results in 2018_8_12
list (**)(any data type)list@list("alpha", "beta") + "gamma" results in @list("alpha", "beta", "gamma")
  • (*) When mixing a date with a duration, note that the order and operation matters. The conversion will only be applied with date +/- duration. If you change the ordering (e.g., duration first) or the operation (e.g. multiplication), the result will be an error.
  • (**) When mixing a list with, the order and operation matters: the conversion will only be applied with list +/- element, not with element +/- list.
    • When using the - operator, the element(s) at the right side are removed from the list at the left side. For example, @list("alpha", "beta") - "alpha" and @list("alpha", "beta") - @list("alpha") both result in @list("beta").
    • When using the + operator, the result depends on the right-side:
      • If both the left & right side are lists, then the result will be a concatenation of the two lists, with all the elements of the right list appended to the left list, irrespective of whether they were already present in the left list. For example, @list("alpha", "beta", "gamma") + @list("alpha", "delta") will result in @list("alpha", "beta", "gamma", "alpha", "delta"). (If you want a union of the list, where elements-already-present are not added again, use the @union special function instead of +).
      • If the right side is not a list, then it will be added as a new element of the left list, irrespective of whether the right side element was already present in the list. For example, @list("alpha", "beta") + "alpha" will result in @list("alpha", "beta", "alpha"). (If you want a union of the list and the single element, where the right-side element is not added again to the left list, use the @union special function instead of +).

Implicit conversions of undefined

When using datafields, values will often not be present, because they have not (yet) been filled in by the end-user. Within a calculation/condition, to avoid errors, ClauseBase will try to convert this undefined value to a sensible default value:

DatatypeConverted intoExample
number055 * undefined results in 0
currency0 (same valuta)5 EUR + undefined results in 5 EUR
5 EUR * undefined results in 0 EUR
durationduration with length 05 days + undefined results in 5 days
textempty textempty text is simply ignored in the output
listempty list@list("alpha") + undefined results in @list("alpha")
Note that the date field is missing from the table above: an undefined will never be implicitly converted to a date field. (After all, there exists no date that would make sense here: what could possibly be considered “date zero”?)
Tip: when you do not like these implicit conversions of unavailable values inside a calculation, you may want to use an exclamation point after the hash.

For example, while you may be happy with {#apples^amount * 0.2 EUR} resulting in 0 EUR when the amount of apples has not yet been completed by the end-user, it may equally be the case that you absolutely need the user to fill in this amount. If so, then use {#apples^!amount * 0.2 EUR} instead — this will result in a box that invites the user to fill in this value, signaling that the calculation simply cannot be completed without a value assigned to the amount of apples.

Implicit conversions to true/false in a condition

In condition mode, the following implicit conversions are carried out to convert a data type to true/false:

Data typeConverts to true whenConverts to false when
TextNon-empty text valueEqual to the empty-text value (“”)
(Whole/floating/currency) numberNot equal to 0Equal to 0
DurationAmount higher than 0Amount equal to 0
ListNot emptyEmpty (no elements)
DateAssigned some valueNever
Undefined (e.g., datafield without value)NeverAlways

Note that in an if-then-else condition, you can omit the operator and right-hand side of the comparison for the sake of brevity; ClauseBase will then automatically insert = true. For example:

  • Instead of writing {#contract^is-dutch-law = true: ... } you can also write {#contract^is-dutch-law: ... }. If the datafield is-dutch-law is a true/false datafield, then no implicit conversions will occur.
  • Instead of writing {#contract^value > 0: ... } you can also write {#contract^value = true: ... } or the shorter version{#contract^value: ... }. In both of these cases, the contract-value whole number datafield will be converted into a true/false, meaning it will result in true if a non-zero value is assigned to this datafield; it will instead result in false when no value is assigned at all, or when the value that is assigned to this datafield, is equal to zero.

Implicit duration conversions

When combining duration elements of different units in a calculation, conversions may be necessary — e.g., when you add 2 days to 3 months. You should be aware that such conversions will in many cases result in rounding errors and unexpected results, because a duration does not contain any context about the month or year it relates to.

ClauseBase will convert durations with different units into the lowest time unit, e.g. year + months will be converted to months, year + days will be converted into days, and months + weeks will be converted into weeks. ClauseBase tries to use sensible averages during this conversion, but unexpected results are bound to happen due to the lack of context.

The bottom line is that you should try to avoid these implicit conversions as much as possible.

For example, ClauseBase happens to convert 27 months + 1 day to 822 days, because it first converts 27 months to two years (= 730 days) and 3 months (= 3 times average of 30.417 days, so 91 days), and then adds yet another day. An equally sensible result would, however, be 811 days (= 27 * 30 days + 1 day).

Similarly, ClauseBase happens to convert a month into 4.345 weeks, so that 3 months + 1 week gets converted to 14 weeks. Other software or people would perhaps answer 13 weeks.

Implicit conversions to clause text

ClauseBase allows you insert a datafield directly into natural text. For example:

While perhaps not immediately obvious, this is also a situation where apples (clause text) are combined with oranges (number datafield). To avoid that you would have to insert all kinds of ugly codes to allow this, ClauseBase will implicitly convert this datafield into clause text for you.

In most situations, this conversion will be very straightforward. However, do note the following.

  • A (whole/floating point/currency) number and a date value be converted into text in accordance with the locale styling rules (e.g., for the decimals style, currency symbol placement, short/long date format, etc.).
  • A true/false value will get translated — e.g., Alpha {true}. will be converted into Alpha true in English and Alpha vrai in French.
  • A list value will have its elements printed consequentially with spaces in between — e.g. @list("alpha", "beta", "gamma") will be converted into alpha beta gamma. If you want to have comma’s plus and/or etc. in such list, or perhaps convert it into a bullet-list, then you need to use one of the special functions — e.g., @enumerate-andor(@list("alpha", "beta", "gamma")).

Avoided implicit conversions

Some conversions are deliberately not performed, because they would be ambiguous:

  • Adding a (whole/floating/currency) number to a duration or date. For example, 3 months + 3 results in an error because it will be difficult for the software to figure out whether you want the result to be a duration (6 months) or a whole number instead (6).
  • Adding a number to a date. For example, 2020_04_07 + 3 results in an error, because it is not clear whether you would like to add 3 years, 3 months, 3 weeks or 3 days instead.

Other conversions are deliberately not performed because they make no sense:

  • Mixing text and whole/floating/currency numbers. It will usually be obvious that this does not make sense — e.g. "alpha" + 6 — but this may not always be intuitive to those new to ClauseBase.

    For example, if you create a text datafield #contract^value, then ClauseBase will treat the contents of this datafield as text, and issue an error when you try to do a calculation such as #contract^value * 2. This error will make perfect sense when the end-user would type in “real” text (such as the word “Paris”), because you can obviously not multiply Paris by two. However, when an end-user would type in a number (e.g., 3500), then it will not be so obvious why the software is complaining.

Disallowed operations

While not a data type conversion issue, you should also note that some operations are not allowed between elements of the same data type:

  • You cannot mix different currencies — e.g., adding {200 EUR + 300 USD} results in an error.
  • You cannot add dates to each other — e.g. {2020_04_03 + 2020_05_3} results in an invalid operation error.

Dealing with the data type of if/then/else

An if/then/else construction will normally result in clause text. For example,

1. Upon drafting @CONTRACT, the parties agreed to have @CONTRACT screened by the authorities. If @CONTRACT would turn out to be ...

CONTRACT = {#contract^with-annexes: this Employment And Restructuring Contract (ERC) including its annexes | else: this ERC}

The contents of the internal snippet CONTRACT will be clause text, because its sole element is an if/then/else construction between curly braces. Such clause text can be inserted into the main body of the clause without any conversion being necessary.

However, it is also possible to use the if/then/else construction to return other data values. You can do this by wrapping the content of the if/then/else after the colon (:) between curly braces. For example:

1. Buyer will pay an amount equal to {@INTEREST * 1.5} before 1st January. If this payment is not successfully performed, it will be automatically increased to {@INTEREST * 2}.

INTEREST = {#contract^jurisdiction = "dutch": {0.05 * #contract^value} | else: {0.08 * #contract^value} }

It is interesting to analyse why you will get a “value is not a number” error when you omit the two pairs of curly braces in the if/then/else construction:

1. Buyer will pay an amount equal to {@INTEREST * 1.5} before 1st January. If this payment is not successfully performed, it will be automatically increased to {@INTEREST * 2}.

INTEREST = {#contract^jurisdiction = "dutch": 0.05 * #contract^value | else: 0.08 * #contract^value }

The reason is that ClauseBase will do the following:

  • When encountering the {@INTEREST * 1.5}, it will replace the @SNIPPET with the contents of the internal snippet, arriving at {#contract^jurisdiction = "dutch": 0.05 * #contract^value | else: 0.08 * #contract^value } * 1.5
  • It will then execute the if/then/else construction, and check whether the contract’s jurisdiction is equal to “dutch”. Assuming this is indeed the case, ClauseBase will notice that the associated fragment it has to insert into the main part of the clause is 0.05 * #contract^value.
  • Because this fragment is not wrapped in curly braces, ClauseBase will treat the fragment as clause text. For example, if the contract’s value is currently set to 1000, and the current styling happens to be metric, it would convert the fragment into the text “0.05 * 1.000” . Notice that:
    • because the fragment is treated as clause text, ClauseBase will treat the 0.05 literally, i.e. not consider it to be a number. Accordingly, despite the fact that the styling settings are set to metric (so that a floating point number such as 0.05 would be printed with a comma as “0,05”), ClauseBase will print it as “0.05”
    • the contract’s value (1000) is a whole number that is inserted into clause text, and will therefore get converted to clause text, respecting all the styling settings (such as the metric style’s dot between the 1 and the three zeroes)
  • Next, it will try to multiply the clause text “0.05 * 1.000” by the number 1.5, which obviously does not make sense because you cannot multiply text by a number. It will therefore issue a warning “value is not a number”.

Pursuant to article 3§4 of the Civil Code, #Buyer shall ...

Examples

Why is shall pay 10.20 EUR printed as “shall pay 10.20 EUR”, while my styling settings dictate metric style with a euro-symbol?

The software is treating the 10.20 EUR as a part of natural text, and therefore prints it literally, without applying any styling to it. While, as humans, we clearly spot a special pattern (i.e., a value + currency) here, the software will blindly accept whatever natural text you insert, without further analysis.

Similar issues would arise when saying shall pay on shall pay 3 * 10 EUR — it would be printed as such.

The solution is to state shall pay {10.20 EUR}. This causes the software to be alerted that instead the “10.20 EUR” is something special that should not be taken literally as natural text.

Why do I see an error message with Pursuant to article 3§4 of the Civil Code, #Buyer shall ... ?

The symbol § has a special meaning to the — as is also the case for symbols such as [ ], < >, | and { } .

When you insert a §, the software assumes you are using a special reference to another clause. The solution is to turn the “3§4” into text that will be treated literally by the software, such as a text data type:

Pursuant to article {"3§4"} of the Civil Code, #Buyer shall ...

I get an “incompatible argument types” error when using the following clause:

#Buyer shall pay interest equal to @days-between(#contract^start-date, @today)

The @days-between special function requires you to pass two dates as arguments.

The second argument (@today) is always available, so the problem is most likely that #contract^start-date has not yet been assigned a value.

In such case, this datafield will result in an undefined value, which is incompatible with (and will never be implicitly converted to) a date.

The software does not accept the following clause (shows a yellow error):

#Buyer shall pay interest equal to @days-between({#contract^prolonged: #contract^prolongation-date | else: #contract^start-date}, @today)

The ClauseBase grammar does not allow you to use the if/then/else construct (the stuff between curly braces) as an argument to a special function. You can, however, change it into an internal snippet, and write:

#Buyer shall pay interest equal to @days-between(@DATE, @today)

DATE = {#contract^prolonged: #contract^prolongation-date | else: #contract^start-date}

The software will accept this — the yellow error will go away — but you will then face an “incompatible argument types” error. The reason is that the if/then/else construct in the internal snippet results in clause text, while the @days-between function requires a date as its first argument.

The solution is to embed the date-fields in curly braces within the if/then/else. This ensures that the if/then/else does not convert the date-field in text when constructing its result.

#Buyer shall pay interest equal to @days-between(@DATE, @today)

DATE = {#contract^prolonged: {#contract^prolongation-date} | else: {#contract^start-date}}

Was this article helpful?
Dislike
Conjugations