CLOVIS
12/07/2022, 7:56 PMsealed class Tree {
class Leaf(val x: Int) : Tree()
class Node(val x: Int, val children: Map<String, Tree>) : Tree()
}
What would be the Optics way of filtering nodes based on some criteria throughout the entire tree (recursively)?chiroptical
12/07/2022, 8:01 PMchiroptical
12/07/2022, 8:01 PMCLOVIS
12/07/2022, 8:01 PMCLOVIS
12/07/2022, 8:02 PM@optics
annotation and companion object
in the right places, I left it out for readabilitysimon.vergauwen
12/07/2022, 8:03 PMchildren
here are selected based on some key in String
?
I'm not sure if there is a way to focus on all recursive trees.. π€ You would have to specify the depth.
<http://Tree.node.children.at|Tree.node.children.at>(At.map(), "key").<http://node.children.at|node.children.at>(At.map(), "nested-key")
CLOVIS
12/07/2022, 8:05 PMsimon.vergauwen
12/07/2022, 8:05 PMPlated
that for example allowed modifying elements in Json that matches a predicateCLOVIS
12/07/2022, 8:07 PMfold
, but I can't wrap my head around implementing it for a treeCLOVIS
12/07/2022, 8:07 PMJannis
12/08/2022, 9:38 AMdeepOf plated f
in haskell, probably won't exist in many other langs because this is hard to get right on a non stacksafe implementation. This optic will focus all children recursively excluding matched ones. So it will recursively run plated
which extracts the immediate children, then runs the optic f
and if that focuses no elements it recurses through the children previously returned by plated
. Getting this correctly implemented is annoying (@simon.vergauwen I think I had this in the profunctor branch at some point. I can look it up if you are interested π
)
You can also do this neatly with recursion schemes, but those are even harder to get right on non stacksafe platforms and I do not recommend this outside of haskell. Even there only do it if you have lots and lots of recursive traversals on tree like structures.CLOVIS
12/08/2022, 9:39 AMJannis
12/08/2022, 9:46 AMplated
and deep = deepOf plated
are useful for generic tree traversals. Simply define/derive plated
for a type and you can easily write any sort of recursive Traversalsimon.vergauwen
12/08/2022, 10:51 AMPlated
and it was terribly slow. I can probably dig it up. I looked at it in Scala as well and it's similarly slowsimon.vergauwen
12/08/2022, 10:51 AMsimon.vergauwen
12/08/2022, 10:52 AMJannis
12/08/2022, 11:22 AMf.deepOf(g)
) into a Fold
and Traverse
side (overloaded so a user won't ever notice). The fold worked as follows: We start with the top level element in a queue. Dequeue one element, run the focusing optic g
and record if it executed (or was filtered out). If it was filtered out, use the extract optic (usually plate
) to get new elements, append them to the queue. Repeat until the queue is empty. Since I used `Applicative`'s the return value was Kind<F, Unit>
on a fold and combining all the intermediate return values was obvious (just combine the context using ap
.) This I assume is fast, a mutable buffer as a queue and it is working through a flattened structure.
The Traversal
side is more complex because the return type actually matters and isn't just Unit
wrapped in some context. I had to use DeepRecursiveFunction
but other than that the concept was very similar, just that now we can't just flatten everything because we need to plug the results into the correct places. That being said, it is probably still faster than any Eval
version.
Here are two links:
Fold: https://github.com/arrow-kt/arrow/pull/2407/files#diff-e1b4ae612d548a8678a928b28c8d98bcbb382e622e85a4d677b7bf56c8a68b7cR172-R192
Traversal: https://github.com/arrow-kt/arrow/pull/2407/files#diff-73e1ef7af4d30c50b1bd69be8111db7c8a90fa843a18371860ad6a3648a25203R247-R275
Honestly the traversal is very confusing, but should work. (tho it does require stable traversal order on the extract optic)Jannis
12/08/2022, 11:33 AMAlso nice to see you here again @Jannis βΊοΈ Hope you're doing well!Thanks π Had lots of things to deal with so I usually just catch up superficially on things. It's interesting to see how arrow developed, glad that the dsls around effects are still around and evolving. Your twitter screenshots look great! A bit sad to see what was cut, but given the lack of language support it's inevitable, otherwise arrow would remain niche forever.
Jannis
12/08/2022, 12:04 PMsimon.vergauwen
12/09/2022, 7:32 AMJson
. https://github.com/nomisRev/kotlinx-serialization-jsonpathsimon.vergauwen
12/09/2022, 7:39 AMA bit sad to see what was cut, but given the lack of language support it's inevitable, otherwise arrow would remain niche forever.What do you feel was lost? A lot was cut, that's for sure. Just looking for feedback, perhaps there are some gaps we can still try to figure out how to fill. I think biggest miss is proper multi-shot continuation. Beside being more idiomatic to Kotlin, due it lacking some language features the biggest win IMO was the drastically decrease in the learning curve. It's been much easier to get started with Arrow/FP for new-comers, and teach people FP in Kotlin. In my day-to-day, working on microservices I don't feel I've lost anything. As a library author, I had to shift my mindset. Instead of composing higher-level things, I quite often have to build something from scratch. For more complex use-cases we'll probably need compiler plugins, that's also how I hope to take Optics further. (Due to lack of
Generic
and typeclasses
) but was kind-of the same before...Jannis
12/09/2022, 5:03 PMIt was horrible slow when I tried to use, and implement it overYeah anything that uses.Json
Sequence.plus
is in my experience very slow (such as the universe
impl, which as far as I can tell is the only recursive one there. Ignoring rewrite which recurses on the optic to apply not the structure).
I think if you want a decently fast implementation on a non-lazy non-stacksafe language you most likely need something similar to what I did.
The haskell implementation of deepOf
is a one-liner btw. Laziness and stacksafety coupled with a ~32 year old strong compiler can do magic πJannis
12/09/2022, 5:04 PMI think biggest miss is proper multi-shot continuation.This mostly. Like why not at least give the option of cloning the continuation. Like optimize for linear continuations all you want, but if someone really wants to pay the price for cloning a continuation, let him do so. There is probably a technical reason for this, but likely nothing insurmountable.
Jannis
12/09/2022, 5:09 PMBeside being more idiomatic to Kotlin, due it lacking some language features the biggest win IMO was the drastically decrease in the learning curveThe old HKD definitions and typeclasses were both new to many and hard to use at the same time. Like with monad tutorials, they all get it wrong. No one cares what a monad is, what `beginners need to know is that you can use
<-
in a do
block to extract a value. But if even that is annoying for lack of syntax sugar or other caveats, then its just another hurdle for beginners.
I do really miss applicative combinators tho. Having used them more and more for Haxl
like dsl's and parsers they are super convenient for optimizing static structures (such as concurrent requests or parsers)Jannis
12/09/2022, 5:09 PMGeneric
. This alone saves so much boilerplatesimon.vergauwen
12/09/2022, 5:12 PMthey are super convenient for optimizing static structures (such as concurrent requests or parsers)Yes... but in my production usage of Kotlin I haven't had a lot of need for this. It's still achievable but you need to build it on a special basis. We have a
Haxl
like library in Scala @ 47 and I've ported it to Kotlin. It works but zip
is weird if you have bind
, it's "as hard" manually optimising with a cache. π
I.e. using parZip
instead of zip
for concurrent requests. Albeit it can be parameterised at the edge if you have a special data type for it.simon.vergauwen
12/09/2022, 5:13 PMThere is probably a technical reason for this, but likely nothing insurmountable.IIRC this requires a language to be pure, right? I think the language authors talked about this ones, and the probably is mutability in the language IIRC but I could be mistaken.
Jannis
12/09/2022, 5:13 PMIt works butThis is also true in haskell. Hence why facebook invented and addedis weird if you havezip
, it's "as hard" manually optimising with a cache. πbind
ApplicativeDo
simon.vergauwen
12/09/2022, 5:14 PMsimon.vergauwen
12/09/2022, 5:15 PMJannis
12/09/2022, 5:16 PMIIRC this requires a language to be pure, right?If you want sensible results in multishot mode yes. But this is doable on user level right? So make it clear to any user that mutability is a global thing and will change on multiple runs. We have to clone the stack anyway, so it is only mutable if we use references to mutable objects, but that is a user decision
Jannis
12/09/2022, 5:16 PMRight, but that wouldn't have been feasible with the old Arrow either I thinkRight, applicatives just don't work well in languages without decent partial application support
Jannis
12/09/2022, 5:18 PMThe old bind with reflection was really bad πHonestly was it? It really just gave access to the stack and made it cloneable. It was terrible at interacting with other suspend code, but that is because kotlin focused that feature super hard on concurrency and not on general use as continuations
simon.vergauwen
12/09/2022, 5:18 PMbut that is a user decisionRight but which is what makes it so hard for a language, or even library to expose it. It's fine inside of a library if the author knows what he's doing but I would also not trust this in an application.
simon.vergauwen
12/09/2022, 5:19 PMHonestly was it?Okay, maybe depends on what angle you look at it from π Reaching out to unstable bytecode, and interacting with it is quite bad in general. Also it limited us to the JVM, so for Kotlin it wasn't a great solution.
Jannis
12/09/2022, 5:19 PM"kotlin focused that feature super hard on concurrency and not on general use as continuations"btw I am thinking about implementing linear delimited continuations in the haskell rts and this has been very high on things to think about: ie how much do I want this to be about scheduling/concurrency
Jannis
12/09/2022, 5:20 PMReaching out to unstable bytecode, and interacting with it is quite bad in general. Also it limited us to the JVM, so for Kotlin it wasn't a great solution.That is true. It was quite clever imo, but very unsafe and when kotlin multiplatform started to grow also not usable
simon.vergauwen
12/09/2022, 5:20 PM