There's a pattern that appears a lot when I try to...
# arrow
j
There's a pattern that appears a lot when I try to code more functionally.
Copy code
sealed class A {
    data class B(val b: String) : A()
    data class C(val c: Int) : A()
}

class D

val a: A = TODO()

fun f(a: A): D {
    when (a) {
        is A.B -> { TODO() }
        is A.C -> { TODO() }
    }
}
I end up with so many of these `when`s throughout my code. Am I doing something wrong? Or is my allergy to it due to coming from doing only OOP for years? With OOP
A
would have some abstract method that
B
and
C
would implement.
b
I believe you're not doing wrong this is basically how you apply pattern matching over ADTs in Kotlin, of course you can use some typeclasses in some cases to avoid this but sometime you will need to do it (since you're working with adt / gadt) and many languages use this kind of when approach to pattern matching, in a really personal opinion I prefer a more declarative solution for this like haskell / elixir provides us ( thinking about it that I've created this solution for Kotlin: https://github.com/bloderxd/Jota ) but it is just my opinion, like I said many languages use this kind of imperative solution to pattern matching.
❤️ 2
But as I said maybe typeclasses that provides a
A -> B
transformation would help to decrease its usage in some cases
b
Using
when
is pretty consistent with mathematicians combining partial functions to yield a whole function, for example:
f(x) = { x | [[ x > 2 -> x + 2 ]]; [[x <= 2 -> -x]] }
read as
when x > 2 then yield x + 2; otherwise when x <= -2 then yield -x
Also, in the example you gave, depending on the application, it may not be desirable for
A
to have an abstract method that
B
and
C
implement, especially if a future
D
or
E
that extended
A
had no need for such a method
p
what you’ve written is correct
mechanically, the results are the same
the difference is that now you’ve decoupled the operations and the data
so you can create operations bound for any context
and it’s easier to write them, rather than mocking, spying or subclassing each implementation
j
Thanks @bloder, @Bob Glamm, @pakoito!
@bloder I'm interested in further exploring something you mentioned:
... maybe typeclasses that provides a 
A -> B
 transformation ...
Roughly speaking, how would I go about this? I know what typeclasses are in the abstract. And I'm familiar with the common ones in Arrow (e.g. applicative, functor, monad). And I know how Arrow can take care of boilerplate code-gen for custom typeclasses. But identifying and appropriately defining a custom typeclass is new territory for me.
b
@julian it depends a lot of your use cases, I say about this kind of approach remembering some cases that I've seen in my last company, for example we had a kind of either monad and in some place of our code we had a function
fun x (a: Result<T>): Result<F>
, people there were transforming
T
in
F
by getting
T
value and applying a pattern mathing in it then transforming in
F
just to return a
Result<F>
instead of just map it using
Result
monad api that already had a pattern matching for error use cases. I think it´s good to take a look in your code and places that ure applying pattern matching and try to figure out how typeclasses can help (maybe some times foldables can help maybe other times just simple functors can help like this case of my last company).
Other approach that I've already tested (but this case was using Exceptions, not good) was create a type that composes my behaviors instead of explicit apply a pattern matching: https://github.com/bloderxd/result#error-cases-with-composition
😍 1
j
Thanks @bloder!