How to add a new model

Adding a model is quite similar to adding a new fact with the main differences being working within the Models directory and the creation of the model’s Haskell module in Model.hs.

  1. Add a new directory for your fact in fact-models/src/Models. Use CamelCase for the model’s name.

  2. In this directory, create a Model.dhall file in which to define your new model, as describe in anatomy of Model.dhall.

  3. To check the new model, run the following command from the same directory as the new Model.dhall file.

    cabal run fact-models:exe:fact -- build --model=NameOfModel

    This command also creates the files derived from Model.dhall such as: Model, Model.dhallType, etc.

  4. Add entries for the new model in:

    • fact-models/src/Models/package.dhall

    • fact-models/src/package.dhall in the model-srcs object

    • docs/modules/models/pages/models.adoc

  5. Implement the model in Haskell. Follow instructions in the section below.

Anatomy of Model.dhall file

See anatomy of a fact for a complete description of the 4 key elements of a Model.dhall file:

  1. description

  2. Type (see note below)

  3. predicate constructor Type

  4. examples

Note on a model’s Type

The Type for a model should be union type whose variants should be constructors of facts. In the example below, the following lines imports the facts package which can be used to define variants within the model type:

let facts = ../../Facts/package.dhall

let ExampleModel =
      < FooPlan : facts.plan.Type
      | Bar
      | BazValue : facts.tn_value.Type
      >

Complete example

{- |
{- tag::description[] -}
= ExampleModel

An example data model.

{- end::description[] -}
-}

let Model = ../../utils/Model.dhall

let facts = ../../Facts/package.dhall

let ExampleModel =
      < FooPlan : facts.plan.Type
      | Bar
      | BazValue : facts.tn_value.Type
      >

let exampleFact1
    : ExampleModel
    = ExampleModel.FooPlan facts.plan.examples.head

in Model
      ExampleModel
      exampleFact1
      ([] : List ExampleModel )

Creating the model’s Haskell module

  1. Add Model.hs in same directory as Model.dhall. A complete example of this file for the ExampleModel is shown below and should be used as a template for creating a new model. See the API docs (at this time this means reading the source files) for the construct* and derive* functions in fact-models/src/Utilties.hs. for more detailed instructions.

  2. In the cabal object in fact-models/src/package.dhall, add an entry for Models.MyNewModel in the library.exposed-modules field.

  3. Run nix run .#build-fact-models-manifests to update fact-models.cabal.

  4. Now run cabal build fact-models to check that the new Haskell module successfully builds.

  5. Add a Tests.hs module for the Model to add tests for the model. See an existing model’s Test.hs file for an example. Add tests model to fact-models test suite in fact-models/src/Tests.hs.

  6. Run cabal test all --test-show-details=always to be sure your tests run and succeed.

Complete example

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

module Models.ExampleModel.Model where

import Data.Aeson
import Data.Generics.Internal.VL.Lens (view, (^.))
import qualified Data.Generics.Product as L (HasField (field), getField)
import Dhall.TH
import FactModel

Dhall.TH.makeHaskellTypes
  ( constructEnumFact "PlanExchange"
      <> constructFact "Plan"
      <> constructSumFact "TNValue"
      <> constructModel "ExampleModel"
  )

------ Fact class instances

deriveFactClasses ''PlanExchange

deriving instance Ord PlanExchange

deriveFactClasses ''TNValue

deriveFactClasses ''Plan

------- Model class instances

deriveFactClasses ''ExampleModel