arrow-kt / arrow

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

["Request"] Feedback to make Arrow more approachable #3303

Open schielek opened 9 months ago

schielek commented 9 months ago

What version are you currently using?

1.2.0

What would you like to see?

What's great?

Arrow's raise DSL is a masterpiece of engineering. It has all the benefits of type-safe error handling without compromising green path programming and therefore fosters readability.

Concrete vs. Abstract

I love that function names in the raise DSL are very concrete. fun raise(r: Error): Nothing clearly indicates that we deal with error handling here. A person not familiar with Arrow or even Kotlin can understand what the code does. Either.Left on the other hand is abstract. It takes time to get used to the convention that Either.Left models errors and Either.Right models the green path, but I feel like it is an unnecessary price to pay if I use Either only for error handling. (For other cases I have sealed data types and would never long for Either.)

Suggestions for some Renaming/Aliases

getOrRaise instead of/along with bind

I propose to introduce getOrRaise as an alias for bind. It goes along with other functions such as getOrElse, getOrNull, and getOrNone.

either {
    // result is of type Either here
    result.getOrElse {  }
    result.getOrNull()
    result.getOrNone()
    result.bind() // abstract and unreadable
    result.getOrRaise() // does not exist :(
}

get and error instead of/along with left and right

As mentioned before left and right are rather abstract.

// result is of type Either here
// Right cases have 'get' functions indicating success/green path
result.getOrElse {  }
result.getOrNull()
result.getOrNone()

// Left cases only have 'left' functions. There isn't a readable alternative indicating errors
result.leftOrNull()
result.onLeft {  }
result.mapLeft {  }

// These would be better but do not exist
result.errorOrNull()
result.onError { }
result.mapError { }

parMap and parZip

This is not part of the raise DSL but belongs into a similar category since it affects readability. Some function names such as parMap and parZip are directly taken from Haskell. While it is apparent to Haskell developers what these functions do, it might not be for the average Kotlin developer. I suggest going with the Kotlin/Java philosophy of avoiding abbreviations where possible. parallelMap and parallelZip have five more characters but can save a lot of headache when reading code.

Note

This issue is rather meant as a feedback or an impulse for a discussion than a feature request. I would be happy if some people would join the conversation and leave their thoughts.

serras commented 9 months ago

Hi @schielek, thanks for your feedback!

We usually try to strike a balance between Kotlin-idiomatic names and names coming from the functional programming sphere. Left is one of that examples: the name itself is very common in Haskell, Scala, and similar languages. It's not that we think it's perfect, but rather we try that every material out there that talks about functional error handling would be similarly applicable to Arrow.

Personally, I like some of your suggestions like getOrRaise, but I would not replace those functions, but rather provide both versions. That way we cover both camps: bind is a very recognizable name from somebody used to the "monadic lingo", whereas getOrRaise follows Kotlin idioms more closely.

serras commented 9 months ago

On the other hand, I think that renaming parZip to parallelZip would not make such a difference, and we would be breaking source compatibility.

schielek commented 9 months ago

@serras Thanks for your kind words, Alejandro.

  1. I agree that changing the semantics of or adding semantics to a popular construct like Left is a dangerous decision. While providing some value to the Kotlin-idiomatic faction it could mean a lot of anger for the Haskell/Scala faction.
  2. I appreciate that you consider getOrRaise a viable alias and Kotlin-idiomatic alternative to bind. Depending on what other community members think I would be very happy to see this in the future.
  3. I still think it does make a difference to rename parMap, but that could just be me. Kotlin provides decent tools for deprecation and the Kotlin team has shown the communities acceptance for those deprecation cycles e.g. in the stdlib.
CLOVIS-AI commented 9 months ago

As user, I think getOrRaise is a great name—however I am worried that having two names for the same thing will force users to know both names to be able to read code, which makes it even harder. Unless one is clearly favored compared to the other (e.g. by deprecating one of them), I don't think having both will help readability.

With regards to Either.Left/Right, as I said on Slack, I don't think this is a big issue: with context receivers, almost the entire Arrow API is Raise-based, not Either-based. Since creating your own either-like datatype is just matter of implementing bind and the DSL constructor (trivial with fold), it becomes very simple to not use Either at all if a team finds it unclear.