{"id":16964992,"url":"https://github.com/propensive/wisteria","last_synced_at":"2025-03-17T08:37:47.032Z","repository":{"id":50393785,"uuid":"367317385","full_name":"propensive/wisteria","owner":"propensive","description":"Easy, fast, transparent generic derivation of typeclass instances in Scala","archived":false,"fork":false,"pushed_at":"2025-01-30T17:45:22.000Z","size":2958,"stargazers_count":20,"open_issues_count":3,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-10T19:53:49.163Z","etag":null,"topics":["coproduct-types","derivation","generic-derivation","product-types","scala","sum-types","typeclass-derivation","typeclass-instances","typeclasses"],"latest_commit_sha":null,"homepage":"https://soundness.dev/wisteria/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/propensive.png","metadata":{"files":{"readme":".github/readme.md","changelog":null,"contributing":".github/contributing.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-14T09:41:59.000Z","updated_at":"2025-01-30T17:45:26.000Z","dependencies_parsed_at":"2024-02-01T08:29:37.338Z","dependency_job_id":"8b837df9-46fd-4c5a-8a89-7668b486b1c4","html_url":"https://github.com/propensive/wisteria","commit_stats":null,"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fwisteria","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fwisteria/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fwisteria/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fwisteria/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/propensive","download_url":"https://codeload.github.com/propensive/wisteria/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243852499,"owners_count":20358271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coproduct-types","derivation","generic-derivation","product-types","scala","sum-types","typeclass-derivation","typeclass-instances","typeclasses"],"created_at":"2024-10-13T23:44:47.198Z","updated_at":"2025-03-17T08:37:47.016Z","avatar_url":"https://github.com/propensive.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"[\u003cimg alt=\"GitHub Workflow\" src=\"https://img.shields.io/github/actions/workflow/status/propensive/wisteria/main.yml?style=for-the-badge\" height=\"24\"\u003e](https://github.com/propensive/wisteria/actions)\n[\u003cimg src=\"https://img.shields.io/discord/633198088311537684?color=8899f7\u0026label=DISCORD\u0026style=for-the-badge\" height=\"24\"\u003e](https://discord.com/invite/MBUrkTgMnA)\n\u003cimg src=\"/doc/images/github.png\" valign=\"middle\"\u003e\n\n# Wisteria\n\n__Simple, fast and transparant generic derivation for typeclasses__\n\n__Wisteria__ is a generic macro for automatic materialization of typeclasses for datatypes composed from product\ntypes (e.g. case classes) and coproduct types (e.g. enums). It supports recursively-defined datatypes\nout-of-the-box, and incurs no significant time-penalty during compilation.\n\n## Features\n\n - derives typeclasses for case classes, case objects, sealed traits and enumerations\n - offers a lightweight but typesafe syntax for writing derivations avoiding complex macro code\n - builds upon Scala 3's built-in generic derivation\n - works with recursive and mutually-recursive definitions\n - supports parameterized ADTs (GADTs), including those in recursive types\n - supports both consumer and producer typeclass interfaces\n - fast at compiletime\n - generates performant runtime code, without unnecessary runtime allocations\n\n\n## Availability\n\nWisteria 0.23.0 is available as a binary for Scala 3.5.0 and later, from [Maven\nCentral](https://central.sonatype.com). To include it in an `sbt` build, use\nthe coordinates:\n```scala\nlibraryDependencies += \"dev.soundness\" % \"wisteria-core\" % \"0.23.0\"\n```\n\n\n\n\n\n\n\n## Getting Started\n\nWisteria makes it easy to derive typeclass instances for product and sum types,\nby defining the rules for composition and delegation as simply as possible.\n\nThis is called _generic derivation_, and given a typeclass which provides some\nfunctionality on a type, it makes it possible to automatically extend that\ntypeclass's functionality to all product types, so long as it is available for\neach of the product's fields; and optionally, to extend that typeclass's\nfunctionality to all sum types, so long as it is available for each of the\nsum's variants.\n\nIn other words, if we know how to do something to each field in a product, then\nwe can do the same thing to the product itself; or if we can do something to\neach variant of a sum, then we can do the same thing to the sum itself.\n\n### Terminology\n\n#### Sums and Products\n\nIn this documentation, and in Wisteria, we use the term _product_ for types\nwhich are composed of a specific sequence of zero or more values of other\ntypes. Products include case classes, enumeration cases, tuples and singleton\ntypes, and the values from which they are composed are called _fields_. The\nfields for any given product have fixed types, appear in a canonical order and\nare labelled, though for tuples, the labels only indicate the field's position.\nSingletons have no fields.\n\nLikewise, we use the term _sum_ for types which represent a single choice from\na specific and fixed set of disjoint types. Sum types include enumerations and\nsealed traits. Each of the disjoint types that together form a sum type is\ncalled a _variant_ of the sum.\n\nFrom a category-theoretical perspective, products and sums are each others'\nduals, and thus fields and variants are duals.\n\nIn the following example,\n```scala\nsealed trait Temporal\n\nenum Month:\n  case Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec\n\ncase class Date(day: Int, month: Month, year: Int) extends Temporal\ncase class Time(hour: Int, minute: Int)\ncase class DateTime(date: Date, time: Time) extends Temporal\n```\nwe can say the following:\n- `Temporal` is a sum type\n- `Date` and `DateTime` are variants of `Temporal`\n- `Date`, `Time` and `DateTime` are all product types\n- `day`, `month` and `year` are fields of `Date`\n- `hour` and `minute` are fields of `Time`\n- `date` and `time` are fields of `DateTime`\n- `Month` is a sum type\n- `Jan` through to `Dec` are all product types, all singletons, and all\n  variants of `Month`\n- the type, `(Month, Int)` (representing a month and a year) would be a product\n  type, and a tuple\n\n#### Typeclasses\n\nA typeclass is a type (usually defined as a trait), whose instances provide\nsome functionality, through different implementations of an abstract method on\nthe typeclass, corresponding to different types which are specified in one of\nthe typeclass's type parameters. Instances are provided as contextual values\n(`given`s), requested when needed through `using` parameters, and resolved\nthrough contextual search (implicit search) at the callsite.\n\nWhere necessary, we distinguish clearly between a typeclass _interface_ (the\ngeneric trait and abstract method) and a typeclass _instance_ (a `given`\ndefinition which implements the aforesaid trait). The term _typeclass_ alone\nrefers to the typeclass interface.\n\nThe exact structure of a typeclass interface varies greatly, but typically, a\ntypeclass is a trait, with a single type parameter, and a single abstract\nmethod, where the type parameter appears either in the method's return type or\nin one or more of its parameters.\n\nWe call typeclasses whose type parameter appears in their abstract method's\nreturn type _producers_, because they produce new instances of the parameter\ntype. Typeclasses whose type parameter appears in their abstract method's\nparameters, _consumers_ because existing instances of the parameter type are\ngiven to them. (The term _consumer_ shouldn't be misinterpreted to imply that\nany value is \"used up\" in applying the typeclass's functionality; it will be\npassed into a method, but will continue to exist for as long as references to\nit continue to exist.)\n\nProducers may be covariant (indicated by a `+` before their type\nparameter), and consumers may be contravariant (indicated by a `-` before their\ntype parameter). But either can be defined as invariant.\n\nFor example,\n```scala\ntrait Size[ValueType]:\n  def size(value: ValueType): Double\n```\nis an invariant consumer typeclass interface for getting a representation (as a\ndouble) of the size of an instance of `ValueType`. It might have instances\ndefined as:\n```scala\nobject Size:\n  given Size[Boolean] = new Size[Boolean]:\n    def size(value: Boolean): Double = 1.0\n\n  given Size[Char]:\n    def size(value: Char): Double = 2.0\n\n  given Size[String] = _.length.toDouble\n```\nand even,\n```scala\ngiven [ElementType](using size: Size[ElementType]): Size[List[ElementType]] =\n  _.map(size.size(_)).sum\n```\nwhich constructs new typeclass instances for `List`s on-demand, and which\nrequires a typeclass instance corresponding to the type of the `List`'s\nelements. Since `Size` is a single-abstract-method (SAM) type, it can be\nimplemented as a simple lambda corresponding to the abstract method.\n\nAnother typeclass example would be,\n```scala\ntrait Default[+ValueType]:\n  def apply(): ValueType\n```\nwhich is a covariant producer typeclass interface.\n\n### Derivation\n\nWisteria lets us say, for a _particular_ typeclass interface but for _any_\nproduct type, \"if we have instances of the typeclass available for every field,\nthen we can construct a typeclass instance for that product type\", and provides\nthe means to specify how they should be combined.\n\nDually, we can say that, for a _particular_ typeclass instance but for _any_\nsum type, \"if we have instances of the typeclass available for every variant of\nthe sum, then we can construct a typeclass instance for that sum type\", and\nprovides the means to specify how the instances should be combined.\n\nNaturally, fields and variants may themselves be products or sums, so generic\nderivation may be applied recursively.\n\nHence, if we define all our datatypes out of products and sum types of \"simple\"\ntypes, then for a particular typeclass interface, we can define typeclass\ninstances for the simple types plus a generic derivation mechanism, and\ntypeclass instances will effectively be available for every datatype.\n\nGeneric derivation for sum types is not always needed or even desirable, so we\nwill start by exploring product derivation.\n\n### Deriving Products\n\n#### Consumer Typeclasses\n\nA typical example of a consumer typeclass is the `Show` typeclass. It provides\nthe functionality to take a value, and produce a string representation of that\nvalue, and could be defined as,\n```scala\ntrait Show[ValueType]:\n  def show(value: ValueType): Text\n```\nwith an extension method to make it easier to apply the typeclass:\n```scala\nextension [ValueType: Show](value: ValueType)\n  def show: Text = summon[Show[ValueType]].show(value)\n```\n\nGeneralizing over all products (and hence, all possible field types),\nour task is to define _how_ a product type should be shown, if we're provided\nwith the means to show each of its fields.\n\nSo, if we have `Show` instances for `Int`s and `Text`s, then we want to be able\nto derive a `Show` instance for a type such as:\n\n```scala\ncase class Person(name: Text, age: Int)\n```\n\nHowever, in the general case, we do not know how many fields there will be or\nwhat their types are, so we cannot rely on any of these details in our generic\nderivation definition.\n\nTo use Wisteria, we need to import the `wisteria` package,\n```scala\nimport wisteria.*\n```\nand add the `ProductDerivation` trait to the companion object of the type we\nwant to define generic derivation for, along with the stub for the `join`\nmethod, like so:\n```scala\nobject Show extends ProductDerivation[Show]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]: Show[DerivationType] = ???\n```\n\nThe signature of `join` must be defined exactly like this:\n- it must be `inline`\n- its type parameter must be a subtype of `Product`\n- it must have a context bound on `ProductReflection`\n- its return type must be an instance of the typeclass, parameterized on the\n  method's type parameter\n\nGiven the return type, we know that we need to construct a new\n`Show[DerivationType]` instance, so we can start with the definition,\n```scala\nobject Show extends ProductDerivation[Show]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]: Show[DerivationType] =\n    new Show[DerivationType]:\n      def show(value: DerivationType): Text = ???\n```\n\nWe will implement `show` by calling the method `fields`, which is available as\na protected method inside `ProductDerivation`, and which allows us to map over\neach field in the product to produce an array of values, by means of a\npolymorphic lambda. `fields` also takes an instance of the product type, so it\ncan provide the actual field value from the product inside the lambda.\n\nHere's what a call to `fields` looks like:\n```scala\nobject Show extends ProductDerivation[Show]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]\n          : Show[DerivationType] =\n    new Show[DerivationType]:\n      def show(value: DerivationType): Text =\n        val array: IArray[Nothing] = fields(value):\n          [FieldType] =\u003e field =\u003e\n            ???\n\n        ???\n```\n\nThe polymorphic lambda may be unfamiliar syntax, but it can be thought of as\nequivalent to as a lambda equivalent of a polymorphic method. So if the lambda\nfor,\n```scala\ndef transform(field: Field): Text\n```\nis, `Field =\u003e Text`, then the lambda for,\n```scala\ndef transform[FieldType](field: FieldType): Text\n```\nis, `[FieldType] =\u003e FieldType =\u003e Text`.\n\nThis is necessary because each field will potentially have a different type,\nbut in the context of the `fields` method, we know nothing about what these\ntypes are, but it _is_ useful to be able to name the type. The lambda variable,\n`field`, has the type `FieldType`.\n\nAlthough we can refer to `field`'s type as `FieldType` in the lambda body, we\nstill have almost no information at all about the properties of this type. The\none thing we can assert, however, is that another occurrence of `FieldType` is\nat least referring to the _same_ type.\n\nTherefore, an instance of `Show[FieldType]`, regardless of where it comes from,\n_will_ be able to show an instance of `FieldType`.\n\nBy default, Wisteria will make just such an instance available contextually\nwithin the lambda body.\n```scala\n[FieldType] =\u003e field =\u003e\n  summon[Show[FieldType]].show(field)\n```\n\nSo, for each field this lambda is invoked on, a `Show[Int]`, `Show[Text]` or\n`Show[Person]` (or whatever type necessary) is summoned and supplied to it\ncontextually as a `Show[FieldType]`. It's also available contextually by name\nas `context, so we can also write,\n```scala\n[FieldType] =\u003e field =\u003e\n  context.show(field)\n```\nbut since it's contextual we can use the extension method above, and so it is\nsufficient to write, `[FieldType] =\u003e field =\u003e field.show`.  ```\n\n\nThis gives us enough to construct an array of `Text` values corresponding to\neach field in a product, which we can join together to surround the :\n```scala\nobject Show extends ProductDerivation[Show]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]\n          : Show[DerivationType] =\n    new Show[DerivationType]:\n      def show(value: DerivationType): Text =\n        val array: IArray[Text] = fields(value):\n          [FieldType] =\u003e field =\u003e\n            field.show\n\n        array.join(t\"[\", t\", \", t\"]\")\n```\n\nThis definition is sufficient to generate new (and working) contextual instances\nof `Show` for product types. Given the definition of `Person` above,\n`Person(t\"George\", 19).show` would produce the string, `[George, 19]`.\n\nSimilar to the `fields` method, another method, `contexts`, is provided for accessing the typeclasses\ncorresponding to each field, without using a preexisting instance of the derivation type for dereferencing.\n\n#### Labels\n\nThis is close to what we need, but we would also like to include the type name.\nThis is available as a protected method of `ProductDerivation` called,\n`typeName`, so we can adjust the last line to, `array.join(t\"$typeName[\", t\",\n\", t\"]\")`, and our new derivation will produce the string, `Person[George,\n19]`.\n\nBut we can go further. The name of each field can also be included in the\nstring output. The value `label` is provided as a named contextual value inside\n`fields`'s lambda, so we can access the label for any field from within the\nlambda. Changing the definition to,\n```scala\n[FieldsType] =\u003e field =\u003e\n  t\"$label:${field.show}\"\n```\nwill change the output to `Person[name:George, age:19]`.\n\n#### Special Product types\n\nWe might also like to provide different behavior for certain kinds of product\ntype; singletons and tuples. Singletons have no fields, so the brackets could\nbe omitted for these products. And tuples' names are not so meaningful, so\nthese could be omitted.\n\nTwo methods returning boolean values, `singleton` and `tuple` can be used to\ndetermine whether the current product type is a singleton or a tuple. The\nimplementation of `join` can be adapted to provide different strings in these\ncases.\n\n#### Full Example\n\nSince `Show` is a SAM type, we can also simplify the implementation and write\nthe implementation of `join` as a lambda. A full implementation would look like\nthis:\n```scala\nobject Show extends ProductDerivation[Show]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]\n          : Show[DerivationType] =\n    value =\u003e\n      if singleton then typeName else\n        fields(value):\n          [FieldType] =\u003e field =\u003e if tuple then field.show else t\"$label=$field\"\n        .join(if tuple then t\"[\" else t\"$typeName[\", t\", \", t\"]\")\n```\n\n#### Complementary Values\n\nSome typeclasses operate on two values of the same type. An example is the `Eq`\ntypeclass for determining structural equality of two values:\n```scala\ntrait Eq[ValueType]:\n  def equal(left: ValueType, right: ValueType): Boolean\n```\n\nWhen defining the `join` method for `Eq`, we could use the `fields` method to\nmap over the fields of either `left` or `right`, but not both.\n\nOne solution would be to construct arrays of the field values of `left`, the\nfield values of `right` and the `Eq` typeclasses corresponding to each field.\n(Although the field values, and hence their corresponding typeclass instances\nwill be different from each other, the types of the elements of the left and\nright arrays will at least be pairwise-compatible.) We could then iterate over\nthe three arrays together, applying the each typeclass to its corresponding\nleft and right field value, and then aggregating the results.\n\nWhile possible, this would be inefficient and would require a significant\ncompromise of typesafety: inside the lambda, a value and a typeclass will be\ntyped according to `FieldType`, and therefore uniquely compatible with each\nother. But as soon as they are aggregated into an array, independent of each\nother, their types would become incompatible, erased to `Any` or `Nothing`, and\ncould only be combined with explicit `asInstanceOf` casts.\n\nWisteria avoids this by making it possible, within the `fields` lambda of one\nproduct value, to access the field value, from another product value, which\ncorresponds to the field in the current lambda, using the `complement` method,\nand to provide it with the same type so that it is compatible with that field's\ncontextual typeclass instance.\n\nHere's a full implementation of `Eq`:\n```scala\nobject Eq extends ProductDerivation[Eq]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]: Eq[DerivationType] =\n    (left, right) =\u003e\n      fields(left):\n        [FieldType] =\u003e leftField =\u003e\n          context.equal(leftField, complement(right))\n      .foldLeft(true)(_ \u0026\u0026 _)\n```\n\n#### Producer Product Typeclasses\n\nProducer typeclasses can also be generically derived. Wheras a _consumer typeclass_ will receive a pre-existing\ninstance of the derivation type as input, and produce a value of some invariant type, a _producer typeclass_\nwill take an invariant type as input, and will construct a new instance of the derivation type.\n\nAn example of a producer typeclass would be a simple `Random` typeclass which takes a long \"seed\" value as input\nand constructs a random new instance from that seed. A `Random` instance for a generic product type should\nproduce a new product instance, all of whose field values are chosen randomly.\n\nHere is the definition of `Random`:\n```scala\ntrait Random[+ValueType]:\n  def next(seed: Long): ValueType\n```\n\nFor a producer typeclass derivation, The `join` signature will be identical, but instead of the `fields` method,\nwe will need to use the `construct` method to construct a new instance, without taking an existing instance of\nthe product type as input. A call to `fields` will also take a polymorphic lambda specifying the field type, but\nsince we have no preexisting instance, and therefore no fields, its lambda variable is a reference to the\ntypeclass instance which can be used to instantiate the new field value.\n\n```scala\nobject Random extends ProductDerivation[Random]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]: Random[DerivationType] = seed =\u003e\n    construct:\n      [FieldType] =\u003e random =\u003e\n        ???\n```\n\nIn fact, since we know nothing about the type of the field in the context of the lambda (except that we have a\nname for it), the typeclass instance, which shares the same type in its parameter, is our _only_ means of\nconstructing a new instance for that field.\n\nTherefore, by parametricity, the only sensible way to implement the method is to invoke the `next` method, like\nso:\n```scala\nobject Random extends ProductDerivation[Random]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]: Random[DerivationType] = seed =\u003e\n    construct:\n      [FieldType] =\u003e random =\u003e random.next(seed)\n```\n\nCalling `constuct`, specifying how each field's value will be computed, will return a new instance of the\nproduct, `DerivationType`. Since `Random` is a SAM type, this expression of `Long =\u003e DerivationType` provides\na suitable implementation for the new typeclass.\n\n#### Monadic Producer Product Typeclasses\n\nOften your producer will return a type construct, like `Option` or `Either`,\nfor example:\n```scala\ntrait Parser[T]:\n  def parse(input: String): Either[Exception, T]\n```\n\nIn this case there is a method called `constructWith`, which can be used in\nplace of `construct`, and allows you to specify polymorphic `pure` and `bind`\n(a.k.a. `flatMap`) functions over your type constructor to help traverse\nproducer typeclass results.\n\nHere is an example usage:\n\n```amok\nsyntax  scala\nhighlight  [InputType..flatMap  This is a polymorphic `bind` function\nhighlight  [Monadic..(_)  This is a polymorphic `pure` function\n##\nobject Parser extends ProductDerivation[Parser]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]: Parser[DerivationType] = input =\u003e\n    constructWith[DerivationType, Either]\n     ([InputType, OutputType] =\u003e _.flatMap,\n      [MonadicType] =\u003e Right(_),\n      [FieldType] =\u003e context =\u003e context.parse(input))\n```\n\n### Deriving Sum Types\n\nDeriving sums, or coproducts, is possible by making a choice of which of their variants is represented by the\nsum type. Deriving sums may be omitted for many typeclasses, since it's not as commonly useful as deriving\nproducts. But if it is desired in addition to product derivation, a typeclass's companion object will need to\nextend `Derivation` instead of `ProductDerivation`, and define an additional `split` method.\n\nHere are the adjusted stub implementations for the `Show` typeclass:\n```scala\nobject Show extends Derivation[Show]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]\n          : Show[DerivationType] = ???\n\n  inline def split[DerivationType: SumReflection]\n          : Show[DerivationType] = ???\n```\n\nNote that `split`'s signature is similar to `join`'s, but lacks the subtype constraint on `DerivationType` and\nuses a `SumReflection[DerivationType]` instead of a `ProductReflection`. An implementation of `split` will have\nsome similarities with a `join` implementation, but will use `variant` and `delegate` methods instead of\n`fields` and `construct`.\n\n#### Consumer Sum Types\n\nTo show an instance of a typeclass, we will use the `variant` method to inspect a preexisting instance of the\nderivation type and apply a lambda to the one variant which matches. This is a dual of the `fields` method for\nsum types, but unlike `fields` the lambda will apply _only_ to the matching variant; not to every variant.\n\nLike `fields`, though, we have no greater knowledge about the type of that variant in the context of the lambda,\nso once again, we will specify a polymorphic lambda which takes a `VariantType` type parameter. We do, however,\nhave one more piece of useful information about `VariantType` which we didn't know about a field's type:\n`VariantType` must be a subtype of the derivation type. Therefore, we specify the lambda type variable as\n`[VariantType \u003c: DerivationType]`:\n```scala\ninline def split[DerivationType: SumReflection]\n        : Show[DerivationType] =\n  value =\u003e\n    variant(value):\n      [VariantType \u003c: DerivationType] =\u003e variant =\u003e\n        ???\n```\n\nSo, in the body of the `variant` lambda, we now have an instance of `VariantType`, which we know to be a subtype\nof `DerivationType`. This is actually exactly the same value as `value`, but its type has been refined—to a type\nwhich is more precise; but also abstract.\n\nAs was the case with `fields`'s lambda, we have some additional context available in this lambda: `context`\nis an instance of `Show[VariantType]` and `label` is the name of the variant.\n\nA trivial implementation of this lambda would just call `variant.show`, since the contextual `Show[VariantType]`\nvalue is available.\n\n```scala\ninline def split[DerivationType: SumReflection]\n        : Show[DerivationType] =\n  value =\u003e\n    variant(value):\n      [VariantType \u003c: DerivationType] =\u003e variant =\u003e variant.show\n```\n\n#### Complementary Variants\n\nWhen we provided the product derivation for `Eq`, we used the `complement` method to get the corresponding field\nwith the correct type inside the body of `fields`. The same is possible inside the body of `variant`, but it\nreturns an `Optional` value, since an unrelated value of the same sum type is, by no means, guaranteed to be\nthe _same_ variant: if the other value is a different variant, then it would not make sense to resolve that\nvalue with the same type—and so an `Unset` value is returned from `complement`.\n\nIf, however, both values represent the same variant, then we can access that value, safely typed with the same\ntype.\n\nHere is an implementation of `split` for `Eq`:\n```scala\ninline def split[DerivationType: SumReflection]\n        : Eq[DerivationType] =\n  (left, right) =\u003e\n    variant(left):\n      [VariantType \u003c: DerivationType] =\u003e leftValue =\u003e\n        complement(right).let(context.equal(leftValue, _)).or(false)\n```\n\nThe interpretation of this implementation is that if the left and right sum types represent the same variant,\nthen we use `context`, the typeclass instance that is common to both, to compare them. Otherwise, since they are\nevidently different, we return `false`.\n\nTherefore, a complete implementation of `Eq` is as simple as:\n```scala\ntrait Eq[ValueType]:\n  def equal(left: ValueType, right: ValueType): Boolean\n\nobject Eq extends Derivation[Eq]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]: Eq[DerivationType] =\n    (left, right) =\u003e\n      fields(left):\n        [FieldType] =\u003e left =\u003e context.equal(left, complement(right))\n      .foldLeft(true)(_ \u0026\u0026 _)\n\n  inline def split[DerivationType: SumReflection]: Eq[DerivationType] =\n    (left, right) =\u003e\n      variant(left):\n        [VariantType \u003c: DerivationType] =\u003e left =\u003e\n          complement(right).let(context.equal(left, _)).or(false)\n```\n\n#### Producer Sum Typeclasses\n\nAs with the `construct` method for product types, the `delegate` method is used for producer sum types which\nmust return a new instance of the derivation type, without having a preexisting value to work with. While\n`variant` can unambiguously resolve which of the variants its parameter value represents, just from its runtime\ntype, the method of discerning which variant is required from its input will depend on the type of that input,\nand is not guaranteed to succeed.\n\nImagine defining a `Decoder` type which reads values from strings, and we expect the variant's type to be\nencoded at the start of the string, for example, `\"Developer:Hamza,39\"` and `\"Manager:Jane,52,2\"` could both be\nrepresentations of instances of the sum type:\n```scala\nenum Employee:\n  case Developer(name: Text, age: Int)\n  case Manager(name: Text, age: Int, level: Int)\n```\n\nWe would like to inspect the part of the string before the `:` and delegate to either the `Developer` or\n`Manager` variants accordingly.\n\nBut the typeclass could be passed the string, `\"Director:Beatrice,47\"`, and no variant would exist in the\n`Employee` sum type to delegate to.\n\nAs its first parameter, `delegate` expects the name of the variant (i.e. its `label` value) to delegate to. Its\nsecond parameter is another polymorphic lambda. As with `construct` which had no `field` lambda variable,\n`delegate` has no `variant` lambda variable, and (likewise) offers the matching variant's context.\n\nFor our `Decoder` example, we have:\n```scala\nobject Decoder extends Derivation[Decoder]:\n  inline def split[DerivationType: SumReflection]\n          : Decoder[DerivationType] =\n    text =\u003e\n      val prefix = text.cut(t\":\").head\n      delegate(prefix):\n        [VariantType \u003c: DerivationType] =\u003e decoder =\u003e\n          ???\n```\n\nHaving discerned which variant's decoder should be used, we can then use this to decode the text following the\n`:`, like so:\n```scala\nobject Decoder extends Derivation[Decoder]:\n  inline def split[DerivationType: SumReflection]\n          : Decoder[DerivationType] =\n    text =\u003e\n      text.cut(t\":\") match\n        case List(prefix, content) =\u003e delegate(prefix):\n          [VariantType \u003c: DerivationType] =\u003e decoder =\u003e\n            decoder.decode(content)\n```\n\n#### Derivation for Sum with all `Singleton` variants\n\nSometimes it is useful to derive a typeclass _only_ for enums of singleton variants, such as,\n```scala\nenum Country:\n  case De, Fr, Gb\n```\nbut not for enumerations with one or more structural cases such as:\n```amok\nsyntax  scala\nhighlight  En..ct)  English has multiple dialects\nhighlight  Eo       Esperato has no dialects; it is a singleton\n##\nenum Language:\n  case En(dialect: Dialect)\n  case Eo\n```\nThe `choice` method returns `true` if every case in a sum type is a\nsingleton, that is a \"straight choice\" between them. This also applies to sealed traits of case objects.\n\nHere is an example of its use deriving `Show`:\n```scala\ntrait Show[ValueType]:\n  def show(value: ValueType): String\n\nobject Show extends Derivation[Show]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]\n          : Show[DerivationType] =\n    value =\u003e ???\n\n  inline def split[DerivationType: SumReflection]\n          : Show[DerivationType] =\n    value =\u003e\n      inline if !choice then compiletime.error(\"cannot derive\") else\n        variant(value): [VariantType \u003c: DerivationType] =\u003e\n          variant =\u003e typeName.s+\".\"+variant.show\n```\n\nNote that `inline if` is used to ensure that `choice` is evaluated at\n_compiletime_, enabling the error branch (`compiletime.error`) to be retained\nor eliminated. If it is retained, compilation will fail.\n\n### Optional Derivation\n\nBy default, derivation will fail at compiletime if a field's or variant's corresponding typeclass instance\ncannot be found by contextual search. This is usually the desired behavior because it indicates the absence of\ndefinitions which are inherently necessary.\n\nBut it's not unusual to want generic derivation to succeed, accepting that we should provide a fallback option\nwhen a contextual value is not found. This can be achieved by importing `derivationContext.relaxed` in the scope\nwhere `join` and `split` are defined.\n\nThe presence of this import will change the signature of methods such as `fields` slightly, so that the\ncontextual value provided to its lambda is an `Optional[Typeclass]` instead of a `Typeclass` instance. This\nmeans that there will no longer be a contextual `Typeclass` available, so any calls which expect one will fail\nto compile, but there will be a contextual `Optional[Typeclass]` value instead, and various control methods on\n`Optional` values can be used to work with such a type.\n\nWe could take the `Show` example from earlier and adjust it to fall back to a field's `toString` value if a\n`Show` typeclass does not exist for that type:\n```scala\nobject Show extends ProductDerivation[Show]:\n  inline def join[DerivationType \u003c: Product: ProductReflection]\n          : Show[DerivationType] =\n    value =\u003e\n      fields(value):\n        [FieldType] =\u003e field =\u003e context.layGiven(field.toString.tt)(field.show)\n      .join(t\"[\", t\", \", t\"]\")\n```\n\nThis adjusted version refers to the contextual `Show[FieldType]` value, which is available as `context` inside\nthe lambda, and uses `layGiven` to provide the fallback option in the first parameter block, with the original\ncode (for when the typeclass *is* available) in the second block. This is made possible because when the\n`Optional` value is present, `layGiven` injects its value contextually into this parameter block.\n\n### Default Values\n\nCase classes may be defined with default values for some of their fields. These default values, if available,\ncan be useful during derivation. As one example, a JSON or XML decoder may construct a product instance from\nvalues provided at runtime, but could choose to use that product's default field values whenever a field's value\nis missing from the runtime input.\n\nA contextual `Default[Optional[FieldType]]` instance called `default` is available within the lambda body of\n`fields` and `contexts`, and calling `default()` within either of these contexts will provide an\n`Optional[FieldType]`.\n\n### Frequently-asked Questions\n\n*How can I avoid generic derivation failing when a typeclass for one or more parameters is missing?*\n\nInclude the import,\n```scala\nimport wisteria.derivationContext.relaxed\n```\nin the context where `join` and `split` are defined. This will transform the type of the typeclass value\ncorresponding to the field from `TypeclassType[ValueType]` to `Optional[TypeclassType[ValueType]]`. Normally,\nthis also means that the typeclass will need to be applied explicitly.\n\n*How can I use other unrelated typeclasses in a `join` or `split` implementation?*\n\nThe signatures of `join` and `split` cannot be changed, so it is impossible to include other typeclass instances\nin their implementations. But both are inline methods, so `summonInline` and `summonFrom` can be used to summon\ninstances of other typeclasses at compiletime, whether these relate to the derivation type or a field type.\n\n*How can I use Wisteria for generic derivation without making the generically-derived typeclasses available to implicit search?*\n\nUse a non-companion object extending `Derivation` or `ProductDerivation` for the definitions of `join` and\n`split`, and call the inline `derived` method on that object, passing in the derivation type.\n\n*Why is a generically-derived typeclass instance not being found when it is summoned?*\n\nThis is usually because typeclass instances relating to one or more field or variant values cannot be found. To\ntest this theory, try compiling an explicit call to the inline `derived` method at the callsite where\ncontextual search is failing.\n\n*Why is another contextual instance being selected by contextual search instead of a generically-derived one?*\n\nAssuming the generically-derived typeclass instance *is* a valid candidate for selection, this is probably\nbecause the derived candidate has a lower priority. Since the `given` instance is defined in either\n`ProductDerivation` or `Derivation`, which is typically inherited by the typeclass's companion object, its\npriority is naturally lower than `given` instances defined in the body of that companion object.\n\nOne solution would be to artificially reduce the priority of the undesired contextual instances, for example by\nadding an additional `(using DummyImplicit)` parameter, or moving the definition to an inherited trait.\n\nAnother solution is to define `join` and `split` in an unrelated (non-companion) object, and to define an inline\ngiven called `derived` directly in the companion object, like so:\n```scala\nobject Unrelated extends ProductDerivation[Typeclass]:\n  def join[DerivationType \u003c: Product: ProductReflection]\n          : Typeclass[DerivationType] = ???\n\nobject Typeclass:\n  inline given derived[DerivationType]: Typeclass[DerivationType] =\n    Unrelated.derived\n```\n\n* How can I resolve a derived contextual instance conflicting with another, with an ambiguity error?\n\nTwo contextual values are ambiguous if both match the expected type and the\ncompiler is unable to find a reason why one should be chosen over the other.\nThere are several ways of changing the _priority_ of `given` values, but in\nmore complex cases, this can have the unintended consequence of causing a new\nambiguity elsewhere with a different contextual value.\n\nThe most reliable way to avoid this problem is to select the set of `given`\ndefinitions that can be ambiguous, and to be explicit about their priority\nusing `compiletime.summonFrom`.\n\nTo transform an existing set of ambiguous `given`s, first change them from\n`given`s into ordinary `def`s. For instances derived by Wisteria, this requires\nthe derivation to be implemented outside the companion object (see above).\nThen, define the `derived` `given` as:\n```scala\ninline given derived[ValueType]: DerivationType[ValueType] =\n  compiletime.summonFrom:\n    // cases\n```\n\nWe will specify one case for each of the previous `given` definitions, in the\norder that they should be attempted.\n\nEach case should be a type pattern, a `given case` or a wildcard pattern which\nwill use the presence of a contextual instance of the specified type (at the\ncallsite) to determine if that particular case should match. For example, if we\nwant to define derivation for a `Debug` typeclass which returns the \"best\"\nstring value for a particular type, we could write it as follows:\n```scala\nobject Debug:\n  inline given derived[ValueType]: Debug[ValueType] = value =\u003e\n    compiletime.summonFrom:\n      case encoder: Encoder[ValueType] =\u003e encoder.encode(value)\n      case given Show[ValueType]       =\u003e value.show\n      case _                           =\u003e value.toString\n```\n\nIn plain English, this could be interpreted as,\n - if there is an `Encoder` for `value`'s type, use it to encode the value\n - if there is a `Show` for `value`'s type, make it available in-scope on the\n   right-hand side of the case clause, and use it to `show` the value\n - otherwise, just use the `value`'s `toString` method\n\n*How can I generically-derive a typeclass for a type which indirectly refers to its own type in its fields?*\n\nA recursive type such as `Tree`,\n```scala\nenum Tree:\n  case Leaf\n  case Branch(left: Tree, value: Int, right: Tree)\n```\ncannot be derived in-place, and should be explicitly defined on that type's companion object. The easiest way to\ndo this is to add a `derives` clause to the companion. For example,\n```scala\nobject Tree derives Typeclass\n```\n\n*Why does the compiler fail during derivation with a long message that mentions that, `given instance derived in trait Derivation does not match type...`?*\n\nThis is usually because the polymorphic lambda's type variable for `delegate` or `variant` is missing its upper\nbound. It is essential that the type variable is specified as `[VariantType \u003c: DerivationType]` and not just,\n`[VariantType]`.\n\n*Why does the compiler report a type mismatch between the derivation type and `Product`?*\n\nThis is usually because the derivation type in the signature of `join` is missing the `\u003c: Product` constraint.\n\n\n## Status\n\nWisteria is classified as __maturescent__. For reference, Soundness projects are\ncategorized into one of the following five stability levels:\n\n- _embryonic_: for experimental or demonstrative purposes only, without any guarantees of longevity\n- _fledgling_: of proven utility, seeking contributions, but liable to significant redesigns\n- _maturescent_: major design decisions broady settled, seeking probatory adoption and refinement\n- _dependable_: production-ready, subject to controlled ongoing maintenance and enhancement; tagged as version `1.0.0` or later\n- _adamantine_: proven, reliable and production-ready, with no further breaking changes ever anticipated\n\nProjects at any stability level, even _embryonic_ projects, can still be used,\nas long as caution is taken to avoid a mismatch between the project's stability\nlevel and the required stability and maintainability of your own project.\n\nWisteria is designed to be _small_. Its entire source code currently consists\nof 646 lines of code.\n\n## Building\n\nWisteria will ultimately be built by Fury, when it is published. In the\nmeantime, two possibilities are offered, however they are acknowledged to be\nfragile, inadequately tested, and unsuitable for anything more than\nexperimentation. They are provided only for the necessity of providing _some_\nanswer to the question, \"how can I try Wisteria?\".\n\n1. *Copy the sources into your own project*\n   \n   Read the `fury` file in the repository root to understand Wisteria's build\n   structure, dependencies and source location; the file format should be short\n   and quite intuitive. Copy the sources into a source directory in your own\n   project, then repeat (recursively) for each of the dependencies.\n\n   The sources are compiled against the latest nightly release of Scala 3.\n   There should be no problem to compile the project together with all of its\n   dependencies in a single compilation.\n\n2. *Build with [Wrath](https://github.com/propensive/wrath/)*\n\n   Wrath is a bootstrapping script for building Wisteria and other projects in\n   the absence of a fully-featured build tool. It is designed to read the `fury`\n   file in the project directory, and produce a collection of JAR files which can\n   be added to a classpath, by compiling the project and all of its dependencies,\n   including the Scala compiler itself.\n   \n   Download the latest version of\n   [`wrath`](https://github.com/propensive/wrath/releases/latest), make it\n   executable, and add it to your path, for example by copying it to\n   `/usr/local/bin/`.\n\n   Clone this repository inside an empty directory, so that the build can\n   safely make clones of repositories it depends on as _peers_ of `wisteria`.\n   Run `wrath -F` in the repository root. This will download and compile the\n   latest version of Scala, as well as all of Wisteria's dependencies.\n\n   If the build was successful, the compiled JAR files can be found in the\n   `.wrath/dist` directory.\n\n## Contributing\n\nContributors to Wisteria are welcome and encouraged. New contributors may like\nto look for issues marked\n[beginner](https://github.com/propensive/wisteria/labels/beginner).\n\nWe suggest that all contributors read the [Contributing\nGuide](/contributing.md) to make the process of contributing to Wisteria\neasier.\n\nPlease __do not__ contact project maintainers privately with questions unless\nthere is a good reason to keep them private. While it can be tempting to\nrepsond to such questions, private answers cannot be shared with a wider\naudience, and it can result in duplication of effort.\n\n## Author\n\nWisteria was designed and developed by Jon Pretty, and commercial support and\ntraining on all aspects of Scala 3 is available from [Propensive\nO\u0026Uuml;](https://propensive.com/).\n\n\n\n## Name\n\nWisteria is a flowering plant, much like magnolia is, and Wisteria is a derivative of Magnolia.\n\nIn general, Soundness project names are always chosen with some rationale,\nhowever it is usually frivolous. Each name is chosen for more for its\n_uniqueness_ and _intrigue_ than its concision or catchiness, and there is no\nbias towards names with positive or \"nice\" meanings—since many of the libraries\nperform some quite unpleasant tasks.\n\nNames should be English words, though many are obscure or archaic, and it\nshould be noted how willingly English adopts foreign words. Names are generally\nof Greek or Latin origin, and have often arrived in English via a romance\nlanguage.\n\n## Logo\n\nThe logo shows a hazy, floral shape in pale colors.\n\n## License\n\nWisteria is copyright \u0026copy; 2025 Jon Pretty \u0026 Propensive O\u0026Uuml;, and\nis made available under the [Apache 2.0 License](/license.md).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fwisteria","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpropensive%2Fwisteria","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fwisteria/lists"}