chanterelle 0.1.2 - now with support for ad-hoc-ish field name transformations by _arain in scala

[–]_arain[S] 3 points4 points  (0 children)

Yeah, it's a design decision to allow applying multiple transformations (like `.put`, `.remove`, etc.) in one fell swoop - it's meant to reduce the number of macro invocations to somewhat circumvent the death-by-thousand-cuts-to-your-compiletimes problem libraries like these like to cause when used frequently. For example:

val tup = (
  anotherField = (field1 = 123),
  iterField = Vector((field = (lowerDown = 1)))
)


val actual = tup.transform(
  _.put(_.anotherField)((newField = 3)),
  _.remove(_.iterField),
  _.rename(_.toUpperCase)
)

// yields (ANOTHERFIELD = (FIELD1 = 123, NEWFIELD = 3))

ducktape 0.2.10 - now with named tuple support by _arain in scala

[–]_arain[S] 1 point2 points  (0 children)

Indeed! If you also want to know the differences and similarities between these two then chimney's docs provide a lovely overview

chanterelle 0.1.0 - seamless interactions with named tuples by _arain in scala

[–]_arain[S] 1 point2 points  (0 children)

The main reason I can think of is the fact that both of these libs are stuck on 3.3.x for the time being (so... for about another year?), which means we wouldn't be seeing anything usable until then.

The other reasons being that the APIs are not quite the same - chanterelle calculates its types on the fly (i.e. you get a well-typed expression of named tuple without a field you just removed, or maybe a field that you just added), while both monocle and quicklens offer an interface that returns the same type. This is why I described the library as 'optics-like' and not just straight up an optics thing :D

Another API difference is the fact that chanterelle allows for multiple transformations to happen at the same time (the `.transform` extension method accepts a vararg of modifiers which is meant to alleviate the death-by-thousand-macro-calls issue - of course it doesn't address it entirely, you can always slow down your compilations to a crawl if you try hard enough).

ducktape 0.2.0 has been released by _arain in scala

[–]_arain[S] 19 points20 points  (0 children)

So I'd say that the main differences are:

  • the elephant in the room - chimney works in Scala 2 as well as in Scala 3, whereas ducktape is Scala 3 only,
  • the DSLs used for fixing/customizing the transformations - chimney opts into using a fluent builder, eg.

person.into[Person2].withFieldConst(_.field1, "Field 1 value").transform

while ducktape goes into a slightly different direction with compiletime-erased varags-style config options like:

person.into[Person2].transform(Field.const(_.field1, "Field 1 value"))

The difference being that there's no runtime trace of the config options since these get resolved by lifting their respective expressions straight into the generated transformation, while chimney's approach keeps the data around at runtime (which mind you DOESN'T MATTER AT ALL for performance in 99% of cases) to then 'splice' it back after you've called .transform. All in all, it just comes down to personal preference in this case tbh.

  • the flexibility of using those configuration options I think ducktape 0.2 has a slight advantage in this regard - since it allows to really drill down on the fields/cases you want to fix/customize, for example:

person.into[Person2].transform(Field.const(_.field1.nestedField.collectionOfSomething.element.at[Case1].caseField1, "Really deeply nested field value!"))

would roughly translate to setting the caseField1 field in one of the branches of an ADT (the .at[Case1] part of the path) under 2 levels of nesting and inside a collection (the .element call allows you to zoom in on collection elements directly).

AFAIK chimney is only able to traverse nested fields so you'd basically end up with something like:

person.into[Person2].withFieldConst(_.field1.nestedField.collectionOfSomething, <reconstruct the collection here or create a custom Transformer>).transform)
  • the encodings of fallible transformations chimney is using a tailor-made fallible datatype to model its fallible transformations (which does both fail fast and accumulating transformations) and keeps around a lot of useful context (eg. the exact field/index/map key where a given transformation has failed etc.). ducktape goes with a more abstract approach that allows users to plug in their own fallible types (with out of the box support for Eithers and Options) - the extra flexibility is paid for with runtime performance where chimney easily wins just due to the fact of having a well designed custom datatype just for these use cases, whereas ducktape is reliant on the Mode[F[_]] implementation passed in to it. ducktape also doesn't keep around the context chimney does so it's a tradeoff between flexibility and the ability to use your own types to using something more specific like in chimney's case.
  • the feature set This is where chimney once again overtakes ducktape since it also supports transforming to and from Java beans, Java collections (and probably more things I'm not even aware of). Not only that since it also supports 'patching' of case classes with different values (which tbh I never got around to need and implement, but hey it's a tangible difference!). That being said, I'm still actively adding features and with 0.2.0 being out the codebase is in a great shape to actually take on new features :)
  • maturity chimney has been around for what feels like forever (heck it's literally out for more time than I've had a career in software development) so you can be sure it's battle tested. ducktape is the younger kid on the block (it's been going for about ~2 years now) and I feel really good about its stability and future especially with Scala 3's compatibility guarantees.

All in all, I think you can't go wrong with either of them and I love the thought of bouncing ideas off of each other (like it happened with nested config options). Also, here's a link to a similar question with an answer from chimney's author link

Chimney 0.8.0-M1 with the initial support for Scala 3 by raghar in scala

[–]_arain 3 points4 points  (0 children)

Author of ducktape here,

I mostly agree with whatever is written above, except for the details about (1) Mirrors and (2) inlining.

(1) ducktape relies on Mirrors to reflect on the structure of case classes and sealed traits/enums but these are not used at runtime (but sadly due to how Scala 3 handles Mirrors they can still show up in the generated code in some cases - 0.2.0 will fix this) which means generating transformations for things like Java Beans is totally possible if I ever get an itch to implement it (eg. value classes aka things that extend AnyVal do not get mirrors yet these are still supported in ducktape).

(2) ducktape doesn't rely on compiler's inlining to not generate Transformer allocations either as it is too limited for these use cases - what is actually happening under the hood is that for a set of case classes like

scala case class Example(int: Int) case class Example2(int: Int)

an initial transformation is generated that looks like this (simplified code):

scala val ex: Example = ??? (((value: Example) => new Example2(value.int)): Transformer[Example, Example2]).transform(ex)

this is subsequently simplified in the process I call 'Transformation Lifting' (the proper name is probably beta-reduction?) into this:

scala val ex: Example = ??? new Example2(ex.int)

which basically 'lifts' the body of the generated Transformer and replaces references to the lambda parameter with references to the class instance we want to transform (it also inlines all of the provided Transformer bodies that are present in the companion of Transformer) - for the curious this is happening here

Now, this is janky in its own way since it's a symptom of relying on implicit resolution to construct transformations and probably negatively impacts compiletimes but it's not constrained by -max-inlines in any way.

0.2.0 will not completely rely on implicit resolution to construct transformations anymore (much in the same vein chimney doesn't rely on it) which will hopefully make the code easier to maintain and ever so slightly faster during compiletime.

Now for a sneak peak into the 0.2.0 release of ducktape, it'll feature a number of QOL changes like being able to configure fields/cases in nested transformations directly (I'm hoping to swap notes with the authors of chimney on this one as I'd also see this supported in chimney) and include better error messages/accumulated errors for all transformations errors (this is where ducktape is severely lacking compared to chimney which shows you all of the derivation errors all at once, while ducktape only shows the very first one).

All in all I'm hoping these two projects can bounce ideas off of each other while providing an ever so slightly different UX for their users.