8000 Releases · arainko/chanterelle · GitHub
[go: up one dir, main page]

Skip to content

Releases

v0.1.7

Choose a tag to compare

@arainko arainko released this 12 Nov 17:24
7ba3173

chanterelle 0.1.7

So, what's new?

.hoist

Adds a new .hoist modifier which (taken straight from the docs):

Hoist a single field up to the top of the transformation (based on the Mode that's currently in scope).
This will also hoist each fallible field that it encouters on the path to the selected field.

Example:

import chanterelle.*

val tup = (field1 = 1, field2 = (nested = Some(1)))
Mode.FailFast.option {
  // evaluates to Some((field1 = 1, field2 = (nested = 1)))
  tup.transform(_.hoist(_.field2.nested))
}

Note that this transformation can be further customized with .local and .regional:

  • .local hoists the currently selected set of fields (like in the example above, note that nestedField1 wasn't hoisted)
  • .regional hoists ALL possible fields in a given region.

An example with .regional:

import chanterelle.*
val tup = (field1 = Some(1), field2 = Some(2), field3 = Some(3), nested = (nestedField1 = Some(1)))

Mode.FailFast.option {
// evaluates to Some(field1 = 1, field2 = 2, field3 = 3, nested = (nestedField1 = 1))
tup.transform(_.hoist.regional)
}

Transformation Mode[F]

Mode is a capability that provides the ability to 'bubble up' nested wrapped values (i.e. ones wrapped in an F) up to the top, for example:

import chanterelle.*

Mode.FailFast.option {
  // evaluates to Some((field1 = 1, field2 = 2, field3 = 3))
  (field1 = Some(1), field2 = Some(2), field3 = Some(3)).transform(_.hoist)
}

Modes come in 3 flavors:

  • Accumulating - the least powerful, able to model error accumulation but the the whole structure needs to be known from the get go (in other words, it can't hoist nested fallible fields like F[F[Int]]),
  • FailFast - the intermediate in terms of power - can't model error accumulation, but can hoist nested fallible fields,
  • Parallel - the most powerful, a mix of both Accumulating and FailFast. It can model both error accumulation AND hoisting of nested fallible fields.

Note that a Mode is not really a typeclass since there isn't really a single instance for each F (for example, instances for Either could come in all sorts of shapes - sometimes it's best to use FailFast, other times Accumulating).
Modes are usually provided at the callsite with their .apply method that puts them in the implicit scope of a block of code (just like in the example above).

Interop typeclasses

chanterelle.interop.Collection.IntoIterator

Defined as:

opaque type IntoIterator[Elem, Collection] = IsIterableOnce[Collection] { type A = Elem }

An interop typeclass representing types that can be converted into iterators.
Instances of Collection.IntoIterator are usually created with Collection.IntoIterator.from, for example:

  import chanterelle.interop.*

  given [Elem]: Collection.IntoIterator[Elem, List[Elem]] =
    Collection.IntoIterator.from(_.iterator)

  // or for a Map-shaped collection:
  given [Key, Value]: Collection.IntoIterator[(Key, Value), Map[Key, Value]] =
    Collection.IntoIterator.from(_.iterator)

This typeclass requires its 2 type parameters to be fully-applied and that's because it abstracts over both 'classic' collection shapes (i.e. ones with a single elem type constructor - like List)
and Map-shaped ones (i.e. with a two elem type constructor - like HashMap).

Note that chanterelle will only consider Coll[_] and Map[_, _] shaped instances (like the two aforementioned examples) when looking for candidates during the expansion of its .transform macro.

An instance of Collection.IntoIterator and Collection.Builder (or Collection.Builder.Fallible) will allow chanterelle to consider a given type as a collection type which enables some additional combinators inside the .transform macro, like the .each operator that 'cuts through' collection shapes when configuring transformations.

chanterelle.interop.Collection.Builder

Defined as:

opaque type Builder[Elem, Collection] <: Builder.Fallible[[a] =>> a, Elem, Collection] =
    Builder.Fallible[[a] =>> a, Elem, Collection]

An interop typeclass that abstracts over collection builders where the collectiom being built can be empty, its fallible sibling Collect.Builder.Fallible allows for
constructing collections that are meant to be non-empty (eg. like cats' NonEmptyList).

Custom instances of Collection.Builder are usually meant to be built using Collection.Builder.from as the starting point and then transforming it into our target type with .transform, for example:

import chanterelle.interop.*

case class MyVector[A](underlying: Vector[A])

given [Elem]: Collection.Builder[Elem, MyVector[Elem]] =
  Collection.Builder.from[Vector[Elem]].transform(MyVector.apply)

...or if we're operating on a custom Map:

import chanterelle.interop.*

case class MyMap[K, V](underlying: Map[K, V])

given [K, V]: Collection.Builder[(K, V), MyMap[K, V]] =
  Collection.Builder.from[Map[K, V]].transform(MyMap.apply)

An instance of Collection.Builder (or Collection.Builder.Fallible) and Collection.IntoIterator will allow chanterelle to consider a given type as a collection type which enables some additional combinators inside the .transform macro,
like the .each operator that 'cuts through' collection shapes when configuring transformations.

This typeclass requires its 2 type parameters to be fully-applied and that's because it abstracts over both 'classic' collection shapes (i.e. ones with a single elem type constructor - like List)
and Map-shaped ones (i.e. with a two elem type constructor - like HashMap).

Note that chanterelle will only consider Coll[_] and Map[_, _] shaped instances when looking for candidates during the expansion of its .transform macro.

chanterelle.interop.Collection.Builder.Fallible

Defined as:

opaque type Fallible[F[_], Elem, Collection] <: Factory[Elem, F[Collection]] = Factory[Elem, F[Collection]]

An interop typeclass that abstracts over collection builders where the collection being built cannot be empty (or their construction is fallible in any other way).

Custom instances of Collection.Builder.Fallible are usually meant to be built using Collection.Builder.from as the starting point and then transforming it into our target type with .transform and finishing up with .fallible, for example:

import chanterelle.interop.*

case class NonEmptyList[A] private (head: A, tail: List[A])

object NonEmptyList {
  def fromList[A](list: List[A]): Option[NonEmptyList[A]] = ???
}

given [Elem]: Collection.Builder.Fallible[Option, Elem, NonEmptyList[Elem]] =
  Collection.Builder
    .from[List[Elem]]
    .transform(NonEmptyList.fromList)
    .fallible

...or if we're operating on a custom Map:

import chanterelle.interop.*

case class NonEmptyMap[K, V] private (underlying: Map[K, V])

object NonEmptyMap {
  def fromMap[K, V](map: Map[K, V]): Option[NonEmptyMap[K, V]] = ???
}

given [K, V]: Collection.Builder.Fallible[Option, (K, V), NonEmptyMap[K, V]] =
  Collection.Builder
    .from[Map[K, V]]
    .transform(NonEmptyMap.fromMap)
    .fallible

This typeclass requires its 2 type parameters to be fully-applied and that's because it abstracts over both 'classic' collection shapes (i.e. ones with a single elem type constructor - like List)
and Map-shaped ones (i.e. with a two elem type constructor - like HashMap).

Note that chanterelle will only consider Coll[_] and Map[_, _] shaped instances when looking for candidates during the expansion of its .transform macro.

chanterelle.interop.Mappable

Defined as:

trait Mappable[F[_]] {
  def map[A, B](fa: F[A], f: A => B): F[B]
}

Mappable is an interop typeclass that allows chanterelle to cut through wrapper types (by allowing .element calls inside path selectors inside the .transform macro). For example:

import chanterelle.*
import chanterelle.interop.*

case class MyWrapper[A](value: A)

object MyWrapper {
  given Mappable[MyWrapper] with {
    def map[A, B](fa: MyWrapper[A], f: A => B): MyWrapper[B] = fa.copy(value = f(fa.value))
  }
}

val tup = (field = MyWrapper(1))
tup.transform(_.update(_.field.element)(_ + 1)) // evaluates to (field = MyWrapper(2)), the instance of `Mappable` allows for the `.element` call to go through

Other stuff

So apart from all of the above there's also some deprecated and some new path selectors (.element has been deprecated on Option[A] | Iterable[A] and replaces (respectively) with .some and .each.

What's Changed

Read more

chanterelle 0.1.6

Choose a tag to compare

@arainko arainko released this 21 Jun 09:11
3791ad4

What's Changed

Full Changelog: v0.1.5...v0.1.6

chanterelle 0.1.5

Choose a tag to compare

@arainko arainko released this 12 Apr 19:45
95086fa

chanterelle 0.1.5

This release adds a new FieldName modifier: .transformViaType which allows users to feed in any match type to map over field names of a named tuple, example from the scaladoc:

type RenameSpecific[Source <: String] <: String = Source match {
     case "i_want_this_one_renamed" => "aight"
     case "andThisOneToo" => "thereYouGo"
     case _ => Source
}
val tup = (field = 1, i_want_this_one_renamed = 2, andThisOneToo = 3)
tup.transform(_.rename(_.transformViaType[RenameSpecific])) // yields (field: Int, aight: Int, thereYouGo: Int)

Remember that match types are turing-complete so you can do ANYTHING you wish with those (at a certain cost to your compiletimes, you've been warned)

What's Changed

  • Update README.md by @arainko in #83
  • .transformViaType - calculating field renames with match types by @arainko in #84

Full Changelog: v0.1.4...v0.1.5

chanterelle 0.1.4

Choose a tag to compare

@arainko arainko released this 12 Apr 17:17
95d8a92

chanterelle 0.1.4

This is a bugfix release - the rename mechanism now detects instances of fields being renamed to the same thing (which in the past resulted in a nondeterministic field deletion), see tests in #82

What's Changed

Full Changelog: v0.1.3...v0.1.4

chanterelle 0.1.3

Choose a tag to compare

@arainko arainko released this 15 Feb 11:56
4550fb1

chanterelle 0.1.3

This release features a milestone feature - deep merging of named tuples. Take a quick peek at the examples from the docs:

import chanterelle.*

val tup = (field1 = 1, field2 = (level1Field1 = 3, level1Field2 = (level2Field = 4)))
val mergee = (field2 = (level1Field3 = 5, level1Field2 = (anotherField = 6)))

val transformed = tup.transform(_.merge(mergee))

// evaluates to: 
// (field1 = 1, field2 = (level1Field1 = 3, level1Field2 = (level2Field = 4, anotherField = 6), level1Field3 = 5))

...or if you want to be more precise in applying your merges you can try the .regional modifier:

val tup = (field1 = 1, field2 = (level1Field1 = 3, level1Field2 = (level2Field = 4)))
val mergee = (level1Field2 = (anotherField = 6))

val transformed = tup.transform(_.merge(mergee).regional(_.field2))

// evaluates to:
// (field1 = 1, field2 = (level1Field1 = 3, level1Field2 = (level2Field = 4, anotherField = 6)))

The rules of merging are as follows:

  • named tuples are merged by field name,
  • fields from the named tuple we merge with (the mergee) take precedence,
  • nested named tuples (that don't come from modifications) and other merged values are recursed,
  • other values get completely overwritten using the value from the mergee.

At this time, there are some limitations on what you can do with a merged value in the same .transform block that I hope to address in the future (i.e. currently it's not possible to use .put on a merged value, things like that), in the meantime users can always chain multiple .transforms to achieve whatever they were going for.

What's Changed

New Contributors

Full Changelog: v0.1.2...v0.1.3

chanterelle 0.1.2

Choose a tag to compare

@arainko arainko released this 29 Dec 17:18
6401515

chanterelle 0.1.2

This release adds a new .rename modifier that allows for ad-hoc-ish field name transformations:

val tup = (anotherField = (field1 = 123, field2 = 123))

val transformed = tup.transform(_.rename(_.replace("field", "property").toUpperCase))
(ANOTHERFIELD = (PROPERTY1 = 123, PROPERTY2 = 123))

The blast radius of the renaming function can be further controlled with '.local' and '.regional':

val tup = (optField = Some((field = (lowerDown = 1))))

// '.local' renames the toplevel fields
val transformedLocal = tup.transform(_.rename(_.toUpperCase).local(_.optField.element))

// '.regional' makes it so that all the of fields underneath the path are transformed
val transformedRegional = tup.transform(_.rename(_.toUpperCase).regional(_.optField.element))
val transformedLocal = (optFiel
8000
d = Some((FIELD = (lowerDown = 1))))
val transformedRegional = (optField = Some((FIELD = (LOWERDOWN = 1))))

There's also a number of predefined case transformations inside the FieldName companion object:

val camel = (
  repoInfo = (
    fullName = "octocat/hello-world",
    createdAt = "2011-01-26T19:01:12Z",
  )
)

val snake = (
  repo_info = (
    full_name = "octocat/hello-world",
    created_at = "2011-01-26T19:01:12Z",
  )
)

val kebab = (
  `repo-info` = (
    `full-name` = "octocat/hello-world",
    `created-at` = "2011-01-26T19:01:12Z",
  )
)

val camelToSnake = camel.transform(_.rename(FieldName.camelCase.toSnakeCase))
val camelToKebab = camel.transform(_.rename(FieldName.camelCase.toKebabCase))
val snakeToCamel = snake.transform(_.rename(FieldName.snakeCase.toCamelCase))
val kebabToCamel = kebab.transform(_.rename(FieldName.kebabCase.toCamelCase))
val camelToSnake = (repo_info = (full_name = "octocat/hello-world", created_at = "2011-01-26T19:01:12Z"))
val camelToKebab = (repo-info = (full-name = "octocat/hello-world", created-at = "2011-01-26T19:01:12Z"))
val snakeToCamel = (repoInfo = (fullName = "octocat/hello-world", createdAt = "2011-01-26T19:01:12Z"))
val kebabToCamel = (repoInfo = (fullName = "octocat/hello-world", createdAt = "2011-01-26T19:01:12Z"))

Users can also define their own bundles of transformations by combaning various operations on FieldNames in a transparent inline def:

transparent inline def renamedAndUppercased(inline fieldName: FieldName) =
   fieldName.rename("someName", "someOtherName").toUpperCase

val tup = (someName = 1)
val transformed = tup.transform(_.rename(renamedAndUppercased))
(SOMEOTHERNAME = 1)

What's Changed

Full Changelog: v0.1.1...v0.1.2

chanterelle 0.1.1

Choose a tag to compare

@arainko arainko released this 30 Aug 17:02
be3d61b

chanterelle 0.1.1

This release brings support for Scala JS and Scala Native (thanks @plokhotnyuk!) along with a new feature - Either traversals:

val tup = (left = Left(1), right = Right("2"))
val transformed = tup.transform(
  _.update(_.left.leftElement)(_ + 1),
  _.update(_.right.rightElement)(_ + "-SUFFIXED")
)
// evaluates to this:
(left = Left(2), right = Right("2-SUFFIXED"))

and a bugfix for selecting tuple elements with .apply(N) rather than solely relying on ._N accessors (which only go up to 22!)

What's Changed

New Contributors

Full Changelog: v0.1.0...v0.1.1

chanterelle 0.1.0

Choose a tag to compare

@arainko arainko released this 01 Aug 18:49

chanterelle 0.1.0

chanterelle is a library for seamless named tuple interactions - think deep modifications, field removals, additions and lens-like ops. You know, the works.

Installation

libraryDependencies += "io.github.arainko" %% "chanterelle" % "0.1.0"

Documentation

The entry point of chanterelle is a single import:

import chanterelle.*

which brings in the .transform extension method defined on named tuples:

val input = (toplevelField = (nestedField = 1, fieldToUpdate = 2, optionalField = Some((anEvenMoreOptionalField = 3))))

val transformed = input.transform(
  _.update(_.toplevelField.fieldToUpdate)(_ + 1), // note the value of toplevelField.fieldToUpdate in the output
  _.remove(_.toplevelField.nestedField), // toplevelField.nestedField gets removed from the output value
  _.put(_.toplevelField.optionalField.element)((newField = 4)) // the element of an Option or a collection can be accessed with `.element`
)

...which, in turn, evaluates to:

(toplevelField = (fieldToUpdate = 3, optionalField = Some((anEvenMoreOptionalField = 3, newField = 4))))

Head on over to the README if you want to see more!

What's Changed

  • docs module with named tuple pretty-printing by @arainko in #3

New Contributors

Full Changelog: v0.0.1...v0.1.0

chanterelle 0.0.1

Choose a tag to compare

@arainko arainko released this 30 Jul 19:35

chanterelle 0.0.1

This is the very first release of chanterelle - this version has all of the MVP features that were initially planned i.e:

Support for:

✔️ named tuples
✔️ tuples
✔️ options
✔️ collections

Modifiers:

✔️ single-element .put
✔️ single-element .compute
✔️ update
✔️ remove (both named and positional tuples)

Other:

✔️ sane errors
✔️ optimized interpreter - turn non-modified branches into Leaves that just take the source value

What's Changed

New Contributors

Full Changelog: https://github.com/arainko/chanterelle/commits/v0.0.1

0