arrow-kt / arrow

Λrrow - Functional companion to Kotlin's Standard Library
http://arrow-kt.io
Other
6.19k stars 448 forks source link

["Request"] Partial function application "invoke" no longer available #2693

Open dmstocking opened 2 years ago

dmstocking commented 2 years ago

What version are you currently using? Arrow Core 1.0.1

What would you like to see? It looks like when the new git repository was made. partials.kt's invoke function was not brought over. I can't find an explanation as to why. So, I am not sure if this was an oversight or an intention choice. I am trying to do the same thing that https://github.com/arrow-kt/arrow/issues/814 was trying to do. The proposed solution is no longer available though as of https://github.com/arrow-kt/arrow/commit/1cd9a90ade7300dde98a7499fa5cbbc60ee514f6#diff-c32792a648907577ba1b8c6f75dd29ceef90404be1348a501f8f136ef9e8681b. Was this on purpose? How do you partially apply multiple arguments besides just doing partial1(..) multiple times?

nomisRev commented 2 years ago

Hey @dmstocking,

This was an intentional removal because it blows up the binary quite a lot, and it's typically not very useful in Kotlin. Might I ask you what your use-case is?

Typically it's more straightforward to just create an anonymous lambda instead, and capture the values inside.

fun theFn(string1: String, string2: String, string3 : String) = string1 + string2 + string3
val partiallyApplied = { str: String -> theFn("Hello", "World", str) }

listOf("!", "?", ".").map(partiallyApplied)

or simply

listOf("!", "?", ".").map { theFn("Hello", "World", it) }

Not sure if your use case is different from this, but this typically results in more idiomatic Kotlin. Additionally, Kotlin heavily optimizes lambdas so that this results in much nicer and optimized bytecode.

dmstocking commented 2 years ago

We were trying to build two variants of an object for testing. One needed a version that had a real dependency that used Netty and another was using an EmbeddedChannel, so that we didn't need to actually connect to something. Anyway the only difference between the two classes was a few arguments. So, I wanted to remove the duplicated code, and I actually used a lambda at one point. The problem though is it is a fairly big lambda. Anyway, let me show you how the changes went down.

Note that I changed some of the names so that they are shorter. Most of the real code has more descriptive names that force putting arguments on separate lines.

// the real connection
        val connection = Connection(
            ChannelFactory(eventLoopGroup, true, logger, Deserializer(), Serializer(), remote),
            logger,
        )
// vs the fake
        val connection = Connection(
            EmbeddedChannelFactory(true, logger, Deserializer(), Serializer(), remote),
            logger,
        )

The last 4 arguments are all the same. So, why not just make a function that creates those and you just pass the lambda on what you want to create? That is exactly what I did, but it turns into.

// the real connection
        val connection = createConnection { logger, deserializer, serializer, remote ->
            ChannelFactory(eventLoopGroup, true, logger, deserializer, serializer, remote)
        }
// vs the fake
        val connection = createConnection { logger, deserializer, serializer, remote ->
            EmbeddedChannelFactory(true, logger, deserializer, serializer, remote)
        }

With Partial Application, I could do.

// the real connection
        val connection = createConnection(::ChannelFactory(eventLoopGroup)(true))
// vs the fake
        val connection = createConnection(::EmbeddedChannelFactory(true))

Which I felt looked really nice and was easier to maintain.

serras commented 2 years ago

@dmstocking are you still interested in pursuing this? Maybe we can find a nicer design which doesn't require tons of utility functions in the library.

dmstocking commented 2 years ago

I would love some way to make this easier. I don't have any suggestions though. I haven't thought about this in a while. Was there any thoughts on how to do that? (I am sort of assuming you want to clear out old tickets if nothing is going to be done with them)

nomisRev commented 2 years ago

I think the best way forward to support such functionality, this includes all functionality such as partial application, currying, function composition, etc to be implemented through a Kotlin compiler plugin.

Such that you can add these functions in an n-arity way to the language. I don't see any other way of doing this in a typed way, without redeclaring all the different arities.