Repository navigation
Releases
v0.1.7
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:
.localhoists the currently selected set of fields (like in the example above, note thatnestedField1wasn't hoisted).regionalhoists 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 likeF[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 bothAccumulatingandFailFast. 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)
.fallibleThis 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 throughOther 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
- Update sbt, scripted-plugin to 1.12.13 by @scala-steward in #106
- Update sbt-scalajs, scalajs-library_2.13, ... to 1.22.0 by @scala-steward in #105
- Update scalafmt-core to 3.11.2 by @scala-steward in #109
- Update munit to 1.3.4 by @scala-steward in #108
- Update sbt-typelevel-ci-release to 0.8.7 by @scala-steward in #112
- Update scalafmt-core to 3.11.3 by @scala-steward in #110
- Update scalafmt-core to 3.11.4 by @scala-steward in #116
- Update sbt-scalafmt to 2.6.2 by @scala-steward in #115
- Update sbt, scripted-plugin to 1.12.14 by @scala-steward in #113
- Update sbt-mdoc to 2.9.1 by @scala-steward in #114
- make PathSelecto...
chanterelle 0.1.6
What's Changed
- Update README.md by @arainko in #89
- Update scalafmt-core to 3.11.0 by @scala-steward in #88
- Update sbt-scalafmt to 2.6.0 by @scala-steward in #87
- Update munit to 1.3.0 by @scala-steward in #85
- Update sbt-mdoc to 2.9.0 by @scala-steward in #86
- Update sbt, scripted-plugin to 1.12.10 by @scala-steward in #90
- Update sbt, scripted-plugin to 1.12.11 by @scala-steward in #91
- Update sbt-scalafmt to 2.6.1 by @scala-steward in #92
- Update scalafmt-core to 3.11.1 by @scala-steward in #93
- Update sbt-typelevel-ci-release to 0.8.6 by @scala-steward in #94
- Update munit to 1.3.1 by @scala-steward in #96
- Update auxlib, clib, javalib, nativelib, ... to 0.5.12 by @scala-steward in #95
- Update scala3-library, ... to 3.8.4 by @scala-steward in #99
- Update munit to 1.3.2 by @scala-steward in #98
- Update munit to 1.3.3 by @scala-steward in #100
- Update sbt, scripted-plugin to 1.12.12 by @scala-steward in #102
- Update sbt-scalafix to 0.14.7 by @scala-steward in #101
- [Issue #97] normalize and simplify match type invocations by @arainko in #104
Full Changelog: v0.1.5...v0.1.6
chanterelle 0.1.5
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
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
- update readme with 0.1.3 by @arainko in #64
- Update sbt, scripted-plugin to 1.12.3 by @scala-steward in #65
- Use Source.Ref instead of raw Exprs in Plans and transformations by @arainko in #67
- Update sbt-typelevel-ci-release to 0.8.5 by @scala-steward in #68
- Update sbt, scripted-plugin to 1.12.4 by @scala-steward in #69
- Update munit to 1.2.3 by @scala-steward in #72
- Update scala3-library, ... to 3.8.2 by @scala-steward in #71
- Update sbt-scalafix to 0.14.6 by @scala-steward in #70
- Update sbt, scripted-plugin to 1.12.5 by @scala-steward in #73
- Update munit to 1.2.4 by @scala-steward in #74
- Update sbt, scripted-plugin to 1.12.6 by @scala-steward in #75
- Update sbt, scripted-plugin to 1.12.8 by @scala-steward in #77
- Update scala3-library, ... to 3.8.3 by @scala-steward in #78
- Update sbt, scripted-plugin to 1.12.9 by @scala-steward in #81
- Update auxlib, clib, javalib, nativelib, ... to 0.5.11 by @scala-steward in #80
- Update sbt-scalajs, scalajs-library_2.13, ... to 1.21.0 by @scala-steward in #79
- [Issue #62] add ambiguous renames detection by @arainko in #82
Full Changelog: v0.1.3...v0.1.4
chanterelle 0.1.3
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
- Fix typo in README.md for accessing Map keys by @ritschwumm in #48
- Update sbt-scalajs, scalajs-library_2.13, ... to 1.20.2 by @scala-steward in #49
- Update sbt, scripted-plugin to 1.12.0 by @scala-steward in #50
- Update scalafmt-core to 3.10.4 by @scala-steward in #51
- Update scalafmt-core to 3.10.6 by @scala-steward in #57
- Update sbt, scripted-plugin to 1.12.2 by @scala-steward in #58
- Update munit to 1.2.2 by @scala-steward in #55
- Update auxlib, clib, javalib, nativelib, ... to 0.5.10 by @scala-steward in #53
- Update scalafmt-core to 3.10.7 by @scala-steward in #60
- [Issue #7] Deep merging by @arainko in #59
- Update scala3-library, ... to 3.8.1 by @scala-steward in #52
- use
.runtimeCheckedinstead of unchecked by @arainko in #63
New Contributors
- @ritschwumm made their first contribution in #48
Full Changelog: v0.1.2...v0.1.3
chanterelle 0.1.2
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
- Update README.md by @arainko in #19
- Update sbt, scripted-plugin to 1.11.6 by @scala-steward in #21
- Update scala3-library, ... to 3.7.3 by @scala-steward in #22
- Update sbt-scalajs, scalajs-library_2.13, ... to 1.20.1 by @scala-steward in #20
- Update munit to 1.2.0 by @scala-steward in #25
- Update scalafmt-core to 3.9.10 by @scala-steward in #24
- internalize 'removed' in
OfFieldby @arainko in #26 - Update sbt-typelevel-ci-release to 0.8.1 by @scala-steward in #27
- Update sbt, scripted-plugin to 1.11.7 by @scala-steward in #28
- Update sbt-typelevel-ci-release to 0.8.2 by @scala-steward in #30
- Update sbt-scalafmt to 2.5.6 by @scala-steward in #36
- Update auxlib, clib, javalib, nativelib, ... to 0.5.9 by @scala-steward in #31
- Update sbt-scalafix to 0.14.4 by @scala-steward in #29
- Update munit to 1.2.1 by @scala-steward in #32
- Update sbt-mdoc to 2.8.0 by @scala-steward in #33
- Update scala3-library, ... to 3.7.4 by @scala-steward in #37
- Update sbt-typelevel-ci-release to 0.8.3 by @scala-steward in #38
- Update scalafmt-core to 3.10.2 by @scala-steward in #39
- Update sbt-mdoc to 2.8.1 by @scala-steward in #40
- Update sbt-scalafix to 0.14.5 by @scala-steward in #41
- Update sbt-typelevel-ci-release to 0.8.4 by @scala-steward in #42
- Update sbt-mdoc to 2.8.2 by @scala-steward in #43
- Update scalafmt-core to 3.10.3 by @scala-steward in #44
- [Issue #5] field renaming by @arainko in #45
- add renaming docs by @arainko in #46
Full Changelog: v0.1.1...v0.1.2
chanterelle 0.1.1
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
- Version is 0.1.0 by @arainko in #9
- Update scala3-library to 3.7.2 by @scala-steward in #10
- Update scalafmt-core to 3.9.9 by @scala-steward in #11
- Update sbt, scripted-plugin to 1.11.4 by @scala-steward in #12
- [Issue #4] Fix .apply(N) selectors on tuples by @arainko in #13
- Add Scala.js and ScalaNative support by @plokhotnyuk in #15
- Update sbt, scripted-plugin to 1.11.5 by @scala-steward in #16
- Clean up the build by @arainko in #17
- [Issue #6] add support for Either travelsals by @arainko in #18
New Contributors
- @plokhotnyuk made their first contribution in #15
Full Changelog: v0.1.0...v0.1.1
chanterelle 0.1.0
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
New Contributors
Full Changelog: v0.0.1...v0.1.0
chanterelle 0.0.1
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
- Update sbt, scripted-plugin to 1.11.3 by @scala-steward in #1
- Update scalafmt-core to 3.9.8 by @scala-steward in #2
New Contributors
- @scala-steward made their first contribution in #1
Full Changelog: https://github.com/arainko/chanterelle/commits/v0.0.1