-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove unlawful alternative #29
base: master
Are you sure you want to change the base?
Remove unlawful alternative #29
Conversation
0437db1
to
a8a0863
Compare
Observable does not always satisfy Alternative's distributivity law
a8a0863
to
f4ed3a1
Compare
This is weird, all the following libraries define an |
Neither of them tests The test in this PR shows that distributivity of |
@raveclassic does the actual problem stem from the ap: (fab, fa) => zip(fab, fa).pipe(rxMap(([f, a]) => f(a))) @mlegenhausen what do you think |
@gcanti Exactly. But I'm not sure what will be the impact of this... |
If |
Also the following fails: it('array.sequence', () => {
const timeline = {
a: '--a--------|',
b: '----b------|',
c: 'c-----d----|',
r: '----x-y----|'
}
new TestScheduler(assert.deepStrictEqual).run(({ cold, expectObservable }) => {
const fa = cold(timeline.a)
const fb = cold(timeline.b)
const fc = cold(timeline.c)
const fr = array.sequence(R.observable)([fa, fb, fc])
expectObservable(fr).toBe(timeline.r, { x: ['a', 'b', 'c'], y: ['a', 'b', 'd'] })
})
}) And this is how |
@raveclassic never mind, |
Definitly a massive break just from a practical standpoint in all the years I have used rxjs I have "almost" never used |
So what is the decision, @gcanti? |
@raveclassic this is a theoretical problem: what's the meaning of
or, in other words, given two or, in other words, is it possible to define |
IMHO from a practical point of view it seems only to be relevant that two
Seems misleading as we can't define it for |
@mlegenhausen well, actually we can, the trivial one import { Eq } from 'fp-ts/lib/Eq'
import { constTrue } from 'fp-ts/lib/function'
import { Task } from 'fp-ts/lib/Task'
export function getEq<A>(): Eq<Task<A>> {
return { equals: constTrue }
} |
I'd say that two observables are equal if values, time (discrete according to some virtual scheduler, like marbles) and order of their events are equal on a certain fixed time period of observation. The same applies for Task although we don't test the time of its value because we have no virtual scheduler. |
@raveclassic so |
If both are subscribed in a single moment (the first |
This doesn't seem right as equality, Let's say you define a const fetchWeather: Task<Weather> = ...
const today = fetchWeather
...
today()
// later...
const tomorrow = fetchWeather
...
tomorrow() You likely get different data but how is it possible that I'm not saying that is not useful to reason about what happens after you run a task (or subscribe to an observable), but that's a slippery slope. Is what actually happens at runtime related to the meaning of TLDR: IMO you can't define an |
I'm not a pro in the field of FRP, but const today = Date.now();
const tomorrow = today + 1000 * 60 * 60 * 24;
fetchWeather(today) !== fetchWeather(tomorrow); Related (but out of scope): I'm studying this paper and it introduces a so called "Now"-computation being essentially an explicit way to declare dependency on time. There's also a library implementing that concept: https://github.com/funkia/hareactive#now |
Thats not the signature of I think this is important, cause the computation behind it is the same |
It doesn't really matter. We can describe declare const forecast: Forecast<Weather>;
const nowForecast = forecast(performance.now()); The same as we would do for usual Task: declare const forecast: IO<Promise<Weather>>;
const nowForecast = forecast(); // <-- time is implicit here I do not insist on introducing these concepts but IMO they describe what happens in the most accurate way. To be honest, Summing up, |
Take a look at the const liftE = <A>(E: Eq<A>): Eq<Observable<A>> => {
const arrayE = getEq(E)
return {
equals: (x, y) => {
const scheduler = new TestScheduler(assert.deepStrictEqual)
const xas: Array<A> = []
x.pipe(subscribeOn(scheduler)).subscribe(a => xas.push(a))
const yas: Array<A> = []
y.pipe(subscribeOn(scheduler)).subscribe(a => yas.push(a))
scheduler.flush() // <-------- run the timeline
assert.deepStrictEqual(xas, yas)
return arrayE.equals(xas, yas)
}
}
} The same could be done for |
Sorry my fault. |
@raveclassic you can't define an
import { IO } from 'fp-ts/lib/IO'
let counter = 0
const increment: IO<number> = () => counter++ How can you define an This is not possible: import { Eq } from 'fp-ts/lib/Eq'
const eq: Eq<IO<number>> = {
equals: (x, y) => x() === y() // <= you can't run the side effects here
} Here's another possible implementation of class IO<A> {
readonly A: A
readonly type: 'IO'
}
class Increment extends IO<number> {
readonly what_to_do = 'please increment the counter'
}
class Decrement extends IO<number> {
readonly what_to_do = 'please decrement the counter'
}
// other IO recipes...
const increment: IO<number> = new Increment()
let counter = 0
function interpreter<A>(recipe: IO<A>) {
if (recipe instanceof Increment) {
counter++
} else if ( ... ) {
}
} All You can consider type IO<A> = () => A as a recipe with its own little, specific interpreter packed together. However only the runtime is allowed to execute these recipes. |
We cannot prove laws of observables (the way proofs work in category theory) if we don't lawfully implement the observables ourselves. We are choosing to not implement observables ourselves so we must assume that observables is a black box. So because we cannot prove the laws, we can brute force it by using runtime equality and running it for infinite cases. Proving wrong is perhaps simpler. Find a counter example. Note: Keep in mind that runtime equality can give false negatives/positives as @gcanti noted unless runtime equality is based on lossless serialization of the data, assuming the data is serializable. |
This PR replaces Alternative instance with Plus instance because Observable does not always satisfy distributivity law. See test for more info.