I'm an FP beginner, working up my basic understand...
# arrow
b
I'm an FP beginner, working up my basic understanding so that I can use Arrow. Hope this is not considered off-topic, but I would like to know if what I'm doing here is a valid FP approach, or if it's my OOP experience muddying the waters.
It's pretty simple really. I have some data represented by a class
Model
, and I want to support converting this data to JSON, Avro, and Turtle for instance. So, similar to the Collections API in Kotlin, it naturally occurred to me to implement
toJson
,
toAvro
and
toTurtle
methods for this. There is no state kept here, so these are pure functions for all intents and purposes (but of course the source data is
this
, instead of a passed in parameter). Is this a valid approach within FP? If not, then why not, and what would be the way to deal with it here? Thanks
Come to think of it, extension functions might be better. This keeps this data representing class from cluttering from a possibly expanding set of output functions. Also easier to extend. Still curious about your thoughts
r
hi @Bart Kleijngeld, yes your approach is generally valid and widespread in Kotlin FP.
Additionally this community is not extremely opinionated as to what means Pure FP. There is always a balance between correctness, ergonomics and philosophy that is applied when FP appears in a language.
🙌 1
✔️ 1
It’s more fun to see the pros and cons of going full FP typed on an approach vs another that is more ergonomic and I think in general here we like to see things from all angles and discuss specific use cases that help us all learn.
If I where to implement your lib I would use extension functions to keep specific theird party integrations separate from the core data types that are common to all of them or form your base API.
In those extensions functions
this
is actually not an outer reference but the name of the receiver
paramater
of the function.
It’s a passed in parameter but when you use it as receiver you obtain the ability to not have to prefix its members offering more concise syntax and access to the receiver members or extensions.
b
Thank you @raulraja, for the clarification!
a
we have some posts about domain modelling and arrow in 47's blog, which may be of interest to you https://www.47deg.com/blog/tags/arrow/
🙌 1
b
Thanks for pointing it out. I've already started reading some articles on your site 🙂, on the use of sealed and data classes for emulating algebraic data types. Really helpful as a companion to my reading of the Functional Programming in Kotlin book.
To follow up on my question: I'm trying to find where the a sensible line is drawn between choosing regular functions, or flexible approaches like extension functions. I mean, what about constructors? Say I have several ways to load a model, would it be idiomatic from an FP standpoint to implement constructors on the
Model
class for, say, a file input and a
Reader
input? And more generally I'm trying to understand how to distinguish OO smells creeping in, versus a legitimately flexible approach towards FP.
r
Do you have a small code example of such class you want to model? We can discuss how it may look like from different angles. In terms of classes and constructors we start with the assumption that allocations is not an effect we care to track like I/O and others. If immutable, classes are usually seen as partially applied functions where the constructor is a partial application. Usually the approach is not to make as pure FP or pure OOP as possible but instead the general approach people follow with FP in the JVM is to use Object and classes as scoped modules, and functions inside our outside as operators that operate over their arguments or over immutable state that is accesible from the current scope.
🙌 1
As for Network or async I/O input
suspend
takes care of what otherwise you would call
IO
in other langs like Haskell or Scala. In the case of Kotlin IO effect tracking is built on top of continuations and the community in general prefers
suspend
and imperative style for monadic ops vs passing higher order functions via flatMap or similar.
It’s interesting but Kotlin not being a pure FP declared lang has easier encoding for monadic effects in terms of the ergonomics than other mainstream FP langs.
b
Yes, things like Arrow's monadic comprehensions look incredible, and I definitely get the impression you're right that Kotlin's multi-paradigm nature contributes to better ergonomics
Furthermore: just you requesting a small code example has proven useful, haha. To keep it to the point, I think I'd like to focus on how to use the code I'm writing. So, as discussed, adding extension functions for transforming some data object to other shapes can be done nicely:
Copy code
// model: Model

model.toJson()  // Nicer in my opinion.
transformToJson(model)
This is a simple example of your point of what you may consider the partially applying role of immutable classes. Now, I have a similar question for initializing a data model like this from several sources, such as
File
or
Reader
. So:
Copy code
val reader: Reader = File("input.txt").reader()

val model = Model.fromReader(reader)  // (1)
val model = readModelFromFile(reader)  // (2)
In this case, I wouldn't know of any other way to do (1) without using constructors. I dislike that, since you get data and logic mixed up in your files (this is so clean about extension functions). (Also note that in this example,
Reader
implementations could have side effects, which I guess would definitely not be desirable here, but I hope the example it still clear enough) I guess the remainder of my question would be: is (2) simply the easiest/preferred way to go? Or is (1) achievable and potentially desirable?
r
model.toJson()
is nice and would implement this as extension function to avoid coupling the data model to how it’s serialized. Same applies to (1) and (2) both can be expressed as extension functions if Model declares a companion in the case (1) or extending over reader or the model
you can ad-hoc extend any type
Copy code
class Model {
  companion object
}

fun Model.Companion.fromReader(reader: Reader): Model = TODO()

fun Reader.model(): Model = TODO()
b
Ah, that's
Reader
extension is yet another flavor indeed. And that companion object usage was just what was missing from my understanding. Nice, thank you! Great explanations 🙂
r
no problem, happy to help, the downside of companions is that not all types have them. ex:
List.Companion
and other java related types you don’t own.
1
Another new feature to look into that will change the way you code is multiple context receivers available as preview in kotlin 1.6.20 for the JVM
there you can define functions like
Copy code
context(Model.Companion, Reader)
fun model(): Model = TODO()
this can be used to declared your code as “functions that require capabilities”
so all your functions are pure and you push the responsibility of implementations to an edge where you provide the instance for the context that you want to use as impl.
b
I'm following somewhat, but not entirely, but it sounds like a very nice way indeed to code separation of purity and effects. Will look into that as soon as I've got some my basics improved. Nice heads up!