I am an absolute beginner in functional programming and Kotlin, trying to solve exercises that I created from questions I'm asking myself; my current question being "How to put in practice functional programming onto real world applications using a Ports and Adapters architecture?"
Currently learning about the Either
monad, I have the following function in which Perhaps<T>
is just a renamed Either<Err, T>
for use with exception handling.
This function takes a RequestModel
containing arbitrary HTTP parameters, and may Perhaps
return a CountBetweenQuery
which is just a data class containing two LocalDate
.
private fun requestCountBetweenQueryA(model: RequestModel): Perhaps<CountBetweenQuery> {
return try {
Perhaps.ret(CountBetweenQuery(extractLocalDateOrThrow(model, "begin"), extractLocalDateOrThrow(model, "end")))
} catch (e: UnsupportedTemporalTypeException) {
Perhaps.Fail(Err.DATE_FORMAT_IS_INVALID)
} catch (e: DateTimeException) {
Perhaps.Fail(Err.DATE_FORMAT_IS_INVALID)
}
}
private fun extractLocalDateOrThrow(it: RequestModel, param: String): LocalDate =
LocalDate.from(DateTimeFormatter.ISO_DATE.parse(it.parameters.first { it.key == param }.value))
In an OO language, I would refactor this so that exception handling either way below in a common exception handler, or higher above (where duplicated code is extracted into a single method). Naturally, I want to turn my extractLocalDateOrThrow
into a perhapsExtractLocalDate
as part of my exercise:
private fun perhapsExtractLocalDate(it: RequestModel, param: String): Perhaps<LocalDate> = try {
Perhaps.ret(LocalDate.from(DateTimeFormatter.ISO_DATE.parse(it.parameters.first { it.key == param }.value)))
} catch (e: UnsupportedTemporalTypeException) {
Perhaps.Fail(Err.DATE_FORMAT_IS_INVALID)
} catch (e: DateTimeException) {
Perhaps.Fail(Err.DATE_FORMAT_IS_INVALID)
}
I have struggled for an hour trying to figure out how to call the constructor of CountBetweenQuery
while preserving the continuation passing style.
This is what I came up with:
private fun requestCountBetweenQueryB(me: RequestModel): Perhaps<CountBetweenQuery> {
val newCountBetweenQueryCurried: (begin: LocalDate) -> (end: LocalDate) -> CountBetweenQuery =
::CountBetweenQuery.curried()
return Perhaps.ret(newCountBetweenQueryCurried)
.bind { function -> perhapsExtractLocalDate(me, "begin").map(function) }
.bind { function -> perhapsExtractLocalDate(me, "end").map(function) }
}
At first I had expected to use return
and apply
because the two method calls perhapsExtractLocalDate
are independent, therefore I would use an applicative style. Instead I was unable to figure out how to avoid using bind
, which is from my understanding implies a monadic style.
My questions are:
If my understanding is correct, how can I turn this into applicative style?
Are there any gross mistakes made in the implementations above? (i.e. idioms, misuse of currying)