All Downloads are FREE. Search and download functionalities are using the official Maven repository.

.discipline_2.12.0.11.2-M1.source-code.Laws.scala Maven / Gradle / Ivy

The newest version!
package org.typelevel.discipline

import scala.collection.SortedMap

import org.scalacheck.{Prop, Properties}

/**
 * Root trait of the law cake.
 *
 * Defines a wrapper around scalacheck's `Properties` ([[RuleSet]]), and some
 * default implementations.
 *
 * Extend this trait if you want to define a set of laws.
 */
trait Laws {

  /**
   * This trait abstracts over the various ways how the laws of a type class
   * can depend on the laws of other type classes. An instance of this trait is
   * called a ''rule set''.
   *
   * For that matter, we divide type classes into ''kinds'', where the classes
   * of one kind share the number of operations and meaning. For example,
   * `Semigroup`, `Monoid` and `Group` all belong to the same kind. On the
   * other hand, their additive variants also belong to a common kind, but to
   * a different one.
   *
   * Users of this trait should extend the outer trait [[Laws]] and create
   * specialized subtypes for each kind of type class. (See
   * [[DefaultRuleSet]] for an example.)
   *
   * Consider this example hierarchy:
   * 
   * Semigroup
   *     |   \
   *  Monoid   AdditiveSemigroup
   *     |   \        |
   *  Group     AdditiveMonoid
   *         \        |
   *            AdditiveGroup
   * 
* They all define their own laws, as well as a couple of parent classes. * If we want to check the laws of `AdditiveGroup`, we want to avoid checking * properties twice, i.e. do not want to check `Monoid` laws via `Group` and * also via `AdditiveMonoid`. * * To address this problem, we define the parent in the same kind as * ''parent'', and other parents as ''bases''. In this example, the parent of * `AdditiveGroup` is `AdditiveMonoid`, and its only basis is `Group`. On the other * hand, the parent of `Group` is `Monoid`, and it does not have any bases. * * The set of all properties of a certain class is now defined as union of * these sets: * - the properties of the class itself * - recursively, the properties of all its parents (ignoring their bases) * - recursively, the set of ''all'' properties of its bases * * Looking at our example, that means that `AdditiveGroup` includes the * `Monoid` law only once, because it is the parent of its basis. The * same laws are ignored by its parent `AdditiveMonoid`, hence no redundant * checks occur. * * Of course, classes can have multiple parents and multiple (named) bases. * The only requirement here is that ''inside one kind'', the identifier of * a property is unique, since duplicates are eliminated. To avoid name * clashes ''between different kinds'', the names of properties pulled in * via a basis are prefixed with the name of the basis. * * For better type-safety, ''parents'' are only allowed to come from the * same outer instance of [[Laws]], whereas ''bases'' are allowed to come * from anywhere. */ trait RuleSet { def name: String def bases: Seq[(String, Laws#RuleSet)] def parents: Seq[RuleSet] def props: Seq[(String, Prop)] private def collectParentProps: SortedMap[String, Prop] = SortedMap(props: _*) ++ parents.flatMap(_.collectParentProps) /** Assembles all properties. For the rules, see [[RuleSet]]. */ final def all: Properties = new AllProperties(name , bases ,collectParentProps) } /** * Convenience trait to mix into subclasses of [[RuleSet]] for * rule sets which only have one parent. */ trait HasOneParent { self: RuleSet => def parent: Option[RuleSet] final def parents = parent.toList } /** * Convenience class for rule sets which may have a parent, but no bases. */ class DefaultRuleSet( val name: String, val parent: Option[RuleSet], val props: (String, Prop)* ) extends RuleSet with HasOneParent { val bases = Seq.empty } /** * Convenience class for rule sets without parents and bases. */ class SimpleRuleSet( name: String, props: (String, Prop)* ) extends DefaultRuleSet(name, None, props: _*) /** Empty rule set. */ def emptyRuleSet: RuleSet = new SimpleRuleSet( name = "" ) } class AllProperties(name: String, bases: Seq[(String, Laws#RuleSet)], collectParentProps: => SortedMap[String, Prop]) extends Properties(name) { for { (baseName, baseProps) ← bases.sortBy(_._1) (name, prop) ← baseProps.all.properties } property(baseName + ":" + name) = prop for ((name, prop) ← collectParentProps) property(name) = prop } // vim: expandtab:ts=2:sw=2




© 2015 - 2024 Weber Informatics LLC | Privacy Policy