a function that takes in 2 reactive streams and re...
# arrow
d
a function that takes in 2 reactive streams and returns a new one, would it be pure ?
p
I take that there’s some background to this question
any function that’s a chain of Stream operation without side-effects has a good chance of being pure
for example, parMapN in Arrow, or merge and amb in RxJava
they return a value that’s not executed yet, so it’s pure in that sense
fun destroyWorldIfImpure(): Stream<Unit> = Stream { destroyWorld() }
calling
destroyWorldIfImpure()
would not trigger
destroyWorld
unless you do something with the
Stream
such as
unsafeRun
and such
d
that is a dark example 😂
👻 3
d
@pakoito what if the function internally folds() the streams, or maybe has a closure variable that accumulates stuff
p
@David Stone it’s the Entity Component System world, right? 😛
😅 1
@Davide Bertola I’d ask whether that accumulator is accessible externally, for example. Local mutability is generally okay
purity is no side-effects (preferably not internally unless completely safe), and referential transparency (you can replace the function call with its result)
d
Copy code
fun test(s1: Stream<Int>, s2: Stream<Boolean>): Stream<Int> {
    var c = 0
    return s1.map { 
        c += it
        return c
    }
}
ok you kind of answered
the strange thing is that if you want to test something like this you can't just think of passing values and getting a result
d
in general, functional purity just means there are no observable side effects (outside of your computer heating up a bit, of course)
d
but you have to pass things and then also make those things behave in a certain way over time
d
so if you can call test with the same s1 and s2, you’ll always get the same result
d
i guess the function itself is pure but testing it is still a matter of generating a number of behaviours and observing the results over time
p
which makes testing have a certain duality
either you’re testing a composition of simple behaviors, and your test will have to reimplement its logic to understand the result. Let’s call these test A
or you have some domain logic for which there’s a clear input-output correlation. Those are tests B
to test A you test each function that composes the chain. These come pre-tested in the library generally, these are your “laws” as we call them. Those aren’t tested in consumer programs
to test B you have to generate a set of inputs and test against those. We call that property-based testing. You generate thousands of test cases that permutate numbers, or strings, or complex types built from simpler types. Then you apply your domain logic function for the expected value
d
who is we and why do you call it "property-based" ?
d
“we” would be the industry as a whole
☝🏼 1
and it’s called property based because it validates certain properties
the idea being that you specify invariants “this must never return null” or “all strings that are returned from this function must contain 4 vowels” or something and then the framework you’re using (usually some derivative of quickcheck) throws a ton of different inputs at it to get it to violate those invariants
p
Kotlin’s is called KoTest
one of the advantages of QuickCheck derivatives is case reduction, meaning that for certain inputs it can give you the minimal repro case. For example, say that your function fails by exception on input 12345. The test runner then creates a set of inputs to check whether it fails above a number or because it’s positive or negative, or it fails on non-zero…depending on the reduction engine
it’s easy for numbers, but let’s say your function fails if a user address in a list has more than 100 characters
thta’s harder to find without reduction
d
ok
d
in the spirit of reduction, let’s say we take the reactive streams out of the question here. is a function that takes two lists and returns a new list pure?
for instance, zip has exactly that signature
d
back to my example you would not really be testing the function calling it multiple times with different parameters. You would use the function to setup the "reactive chain" and then you'd use such techniques to feed input streams and observe the output stream
d
right, just like writing a test for
zip
with two empty lists wouldn’t really be testing anything
emptyList() zip emptyList() is kinda pointless. 😄
p
you would need to write at least 2 tests, one with success and one with error, for example
one for empty
and each is a different generator
Copy code
testWith(User.gen(), User.gen()) { user1, user2 ->
  mergeToPair(Stream.of(user1), Stream.of(user2)).runBlockingEither() == Either.right(user1 to user2)
}
Copy code
testWith(User.gen(), Exception.gen()) { user1, exc ->
  mergeToPair(Stream.of(user1), Stream.error(exc)).runBlockingEither() == Either.left(exc)
}
Copy code
testWith(User.gen(), Exception.gen()) { user1, exc ->
  mergeToPair(Stream.error(exc), Stream.of(user1)).runBlockingEither() == Either.left(exc)
}
etc etc etc
d
great I feel like i am not alone. I am the only one in my team who is trying to think functional
p
it’s okay, it’s a process. Introduce things little by little