Entities: Creating Custom Types in LogiQL

LogiQL is a typed language and few typed languages are useful without the ability to create custom types. Like most things in LogiQL, types are represented as predicates. For instance, there is a string predicate for textual values, and an int predicate for numeric values. In our “LogiQL in 30 minutes” tutorial we represented ice creams as strings to keep things simple:

//lang:logiql
cost[icecream] = c ->
  string(icecream), int(c).

price[icecream] = p ->
  string(icecream), int(p).

This works, but does not take full advantage of the LogiQL type system. As your application grows, how do you keep track of all those things encoded as strings and integers? Wouldn’t it be much nicer to just create an icecream type so that we all understand what we’re talking about — and likely save space in our workspace because we don’t have to duplicate those string values all over the place?

For this reason LogiQL supports entities which are a way to define custom types. In its most basic form, defining an entity is as simple as:

//lang:logiql
icecream(ic) -> .

This states: “there exists such a thing as icecream.” However, to be able to actually create instances of ice creams, we need to define either a reference mode predicate, or one or more constructor predicates.

Reference modes

Let’s start with a reference mode (or refmode for short) example:

//lang:logiql
icecream(ic), icecream_name(ic:name) -> string(name).

In a single clause, the icecream and icecream_name predicates define the icecream entity and its refmode predicate. Note the ic:name syntax, which indicates we’re defining a refmode. We can now use our refmode predicate to create instances of the icecream entity. For instance:

//lang:logiql
+icecream(ic), +icecream_name[ic] = "Popsicle Lemon".

which states: “There exists an icecream ic, and the name of this icecream ic is ‘Popsicle Lemon.'”

We can now use our icecream entity in our cost and price predicates:

//lang:logiql
cost[icecream] = c ->
  icecream(icecream), int(c).

price[icecream] = p ->
  icecream(icecream), int(p).

However, a naming convention that is recommended is to clearly indicate that these predicates can be seen as attributes of the icecream entity by naming it along the lines of <entity>_<attribute>:

//lang:logiql
icecream_cost[icecream] = c ->
  icecream(icecream), int(c).

icecream_price[icecream] = p ->
  icecream(icecream), int(p).

We can now create instances of ice cream and set their price and cost as follows:

//lang:logiql
+icecream(ic),
+icecream_name[ic] = "Popsicle Lemon",
+icecream_cost[ic] = 25,
+icecream_price[ic] = 50.

Note the use of commas to make sure this is one clause and all ic‘s refer to the same icecream instance.

For convenience, it’s possible to use the value of the refmode predicate in place of actual entity values. The LogicBlox compiler will automatically figure out what is intended. In addition, any refmode that did not yet exist will automatically be created. So we can replace the previous four lines with:

//lang:logiql
+icecream_cost["Popsicle Lemon"] = 25.
+icecream_price["Popsicle Lemon"] = 50.

As a result, a fact will automatically be added to icecream_name with a new entity instance for the “Popsicle Lemon” name.

Sidebar: Table versus Key-Value Modeling

Previously, we have stated that predicates can be thought of as tables in a SQL database. This is true, but does not accurately represent how predicates are typically used in LogicBlox applications. Let’s illustrate this with an example.

People familiar with SQL databases may be tempted to create a predicate like this:

//lang:logiql
icecream(name, cost, price) ->
  string(name), int(cost), int(price).

That is: use one predicate that keeps all information related to ice creams. This is the SQL table way.

While doing this is technically possible in LogiQL, it is not common practice in LogiQL programs. Instead, LogicBlox is best used as a key-value store. That is: by creating a predicate per “attribute”. In fact, this is what we have been doing thus far. We created a price predicate, a cost predicate, and now an icecream_name predicate. An extra advantage of this approach, from a modeling perspective, is that way can create predicates with any numbers of arguments. For instance, we can model a predicate that tracks whether a type of ice cream is in stock as follows:

//lang:logiql
icecream_in_stock(ic) -> icecream(ic).

Without the need to implement the more awkward:

//lang:logiql
icecream_in_stock[ic] = val -> icecream(ic), bool(val).

Similarly, as we have seen for our sales predicate, we can can use any number of keys to model things like “in week week, the number of sales of ice cream type ic in store store was value” in a very natural way:

//lang:logiql
sales[week, ic, store] = val -> week(week), icecream(ic), store(store), int(val).

Constructors

To create multi-column primary keys you need to use constructors rather than refmodes. For instance, if you prefer to split out the ice cream type and taste and have the combination be the “primary key” of the entity. In this case constructors can be used as follows:

//lang:logiql
icecream(ic) -> .

icecream_from_type_taste[type, taste] = ic ->
  string(type), string(taste), icecream(ic).

lang:constructor(`icecream_from_type_taste).

The first clause defines that icecream is an entity (without a refmode — you cannot use both refmodes and constructors for a single entity). The second clause defines a predicates that maps the combination of an ice cream type and a taste to an icecream entity value. The third clause defines that this icecream_from_type_taste predicate is a constructor.

We can now make instances of our entity using:

//lang:logiql
+icecream(ic), +icecream_from_type_taste["Popsicle", "Lemon"] = ic.

And set cost and price for the Popsicle Lemon ice cream using:

//lang:logiql
+icecream_cost[icecream_from_type_taste["Popsicle", "Lemon"]] = 25.
+icecream_price[icecream_from_type_taste["Popsicle", "Lemon"]] = 50.

Subtypes

LogiQL also supports the creation of entity type hierarchies. For instance, if at some point we may want to start selling different things than just ice creams, we can hedge our bets and start out with a sellable entity:

//lang:logiql
sellable(s) -> .

We can then create a subtype of sellable, simply by stating that if there exists an ice cream ic there also exists a sellable ic:

//lang:logiql
icecream(ic) -> sellable(ic).

In this case, however, we do need to also explicitly state that icecream is an entity itself:

//lang:logiql
lang:entity(`icecream).

In the future we can define other sellable goods, like candy bars:

//lang:logiql
candybar(cb) -> sellable(cb).
lang:entity(`candybar).

Conclusion

New value types can be defined in LogiQL using entities, which support more descriptive data models by referring to the types by name rather than some primitive type representation of the value. LogiQL has two ways to support creating entity values: refmodes and constructors. Refmodes are a good option when the identifier of an entity is a single primitive value, constructors can be used for multiple keys as well. To learn more, the reference manual contains more information on entity predicates, as well as refmode predicates and constructor predicates.

0 Comments

Leave a reply

© Copyright 2023. Infor. All rights reserved.

Log in with your credentials

Forgot your details?