Skip to content
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

Add SplitByTypes observables #116

Merged
merged 13 commits into from
Nov 9, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.raquo.airstream.split

import com.raquo.airstream.core.{Observable, BaseObservable}
import scala.annotation.compileTimeOnly

/**
* `MatchSplitObservable` served as macro's data holder for macro expansion.
*
* For example:
*
* ```scala
* fooSignal.splitMatch
* .handleCase { case Bar(Some(str)) => str } { (str, strSignal) => renderStrNode(str, strSignal) }
* .handleCase { case baz: Baz => baz } { (baz, bazSignal) => renderBazNode(baz, bazSignal) }
* ```
*
* will be expanded sematically into:
*
* ```scala
* MatchSplitObservable.build(fooSignal, ({ case baz: Baz => baz }) :: ({ case Bar(Some(str)) => str }) :: Nil, handlerMap)
* ```
*/

opaque type MatchSplitObservable[Self[+_] <: Observable[_] , I, O] = Unit

object MatchSplitObservable {

@compileTimeOnly("splitMatch without toSignal/toStream is illegal")
def build[Self[+_] <: Observable[_] , I, O](
observable: BaseObservable[Self, I],
caseList: List[PartialFunction[Any, Any]],
handlerMap: Map[Int, Function2[Any, Any, O]]
): MatchSplitObservable[Self, I, O] = throw new UnsupportedOperationException("splitMatch without toSignal/toStream is illegal")

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.raquo.airstream.split

import com.raquo.airstream.core.{Observable, BaseObservable}
import scala.annotation.compileTimeOnly

/** `MatchTypeObservable` served as macro's data holder for macro expansion.
*
* For example:
*
* ```scala
* fooSignal.splitMatch
* .splitType[Baz] { (baz, bazSignal) => renderBazNode(baz, bazSignal) }
* ```
*
* will be expanded sematically into:
*
* ```scala
* MatchTypeObservable.build[*, *, *, Baz](
* fooSignal,
* Nil,
* handlerMap,
* ({ case t: Baz => t })
* )
* ```
*
* and then into:
*
* ```scala
* MatchSplitObservable.build(
* fooSignal,
* ({ case baz: Baz => baz }) :: Nil,
* handlerMap
* )
* ```
*/

opaque type MatchTypeObservable[Self[+_] <: Observable[_], I, O, T] = Unit

object MatchTypeObservable {

@compileTimeOnly("splitMatch without toSignal/toStream is illegal")
def build[Self[+_] <: Observable[_], I, O, T](
observable: BaseObservable[Self, I],
caseList: List[PartialFunction[Any, Any]],
handlerMap: Map[Int, Function2[Any, Any, O]],
tCast: PartialFunction[T, T]
): MatchTypeObservable[Self, I, O, T] =
throw new UnsupportedOperationException(
"splitMatch without toSignal/toStream is illegal"
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.raquo.airstream.split

import com.raquo.airstream.core.{Observable, BaseObservable}
import scala.annotation.compileTimeOnly

/** `MatchSingletonObservable` served as macro's data holder for macro expansion.
*
* For example:
*
* ```scala
* fooSignal.splitMatch
* .splitValue(Tar)(tarSignal => renderTarNode(tarSignal))
* ```
*
* will be expanded sematically into:
*
* ```scala
* MatchTypeObservable.build[*, *, *, Baz](
* fooSignal,
* Nil,
* handlerMap,
* ({ case Tar => Tar })
* )
* ```
*
* and then into:
*
* ```scala
* MatchSplitObservable.build(
* fooSignal,
* ({ case Tar => Tar }) :: Nil,
* handlerMap
* )
* ```
*/

opaque type MatchValueObservable[Self[+_] <: Observable[_], I, O, V0, V1] = Unit
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

V0 and V1 should never be different, but we cannot just do MatchValueObservable[Self[+_] <: Observable[_], I, O, V] because after type erasure, compiler cannot differentiates MatchValueObservable[Self[+_] <: Observable[_], I, O, V] from MatchTypeObservable[Self[+_] <: Observable[_], I, O, T].

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH the implications of this are over my head, but it sounds like it's just an unfortunate implementation detail that we have to deal with. In that case, fine by me, I certainly don't have better ideas on this.

I think we're all good now, thanks for all the updates!

I am super pumped for these new operators, this will be one of the first things that I will merge for 18.x, proooobably in a few weeks (but apologies in advance if my 18.x gets delayed a bit, honestly, it's possible, but I'll do my best to carve out some time for it asap).

Copy link
Contributor Author

@HollandDM HollandDM Sep 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ambiguity happens because of the opaque types. I changed them back to value classes to remove this implementation hack. It should be easier to read and understand now.


object MatchValueObservable {

@compileTimeOnly("splitMatch without toSignal/toStream is illegal")
def build[Self[+_] <: Observable[_], I, O, V0, V1](
observable: BaseObservable[Self, I],
caseList: List[PartialFunction[Any, Any]],
handlerMap: Map[Int, Function2[Any, Any, O]],
vCast: PartialFunction[V0, V1]
): MatchValueObservable[Self, I, O, V0, V1] =
throw new UnsupportedOperationException(
"splitMatch without toSignal/toStream is illegal"
)

}
Loading