├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── core ├── src │ └── commonMain │ │ └── kotlin │ │ └── dolphins │ │ ├── foundation │ │ ├── functions │ │ │ ├── Predef.kt │ │ │ └── Composition.kt │ │ ├── types │ │ │ ├── channel │ │ │ │ └── Channel.kt │ │ │ └── either │ │ │ │ └── Either.kt │ │ ├── Kind.kt │ │ ├── ShadyException.kt │ │ ├── typeclasses │ │ │ ├── Contravariant.kt │ │ │ ├── Monad.kt │ │ │ ├── Consume.kt │ │ │ ├── Channel.kt │ │ │ ├── Shift.kt │ │ │ ├── Functor.kt │ │ │ ├── Stream.kt │ │ │ ├── Applicative.kt │ │ │ └── BiFunctor.kt │ │ └── Conest.kt │ │ ├── core │ │ ├── Core.kt │ │ ├── Mono.kt │ │ ├── FunDeps.kt │ │ ├── Fin.kt │ │ ├── Structure.kt │ │ ├── Handler.kt │ │ └── Feature.kt │ │ └── patterns │ │ └── GhostMutation.kt └── build.gradle.kts ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── sbt.xml ├── misc.xml ├── compiler.xml └── modules │ └── core │ ├── dolphins.dolphins-core.commonMain.iml │ ├── dolphins.dolphins-core.commonTest.iml │ ├── dolphins.dolphins-core.jvmMain.iml │ └── dolphins.dolphins-core.jvmTest.iml ├── settings.gradle ├── rxjava ├── src │ └── main │ │ └── kotlin │ │ └── dolphins │ │ └── rx │ │ ├── types │ │ ├── RxContext.kt │ │ ├── RxChannel.kt │ │ ├── RxHandle.kt │ │ └── Rx.kt │ │ ├── feature │ │ ├── Utilities.kt │ │ ├── FunDepBag.kt │ │ ├── RxFeature.kt │ │ └── RxHandler.kt │ │ └── instances │ │ └── flowable │ │ ├── Consume.kt │ │ ├── Functor.kt │ │ ├── Shift.kt │ │ ├── Applicative.kt │ │ ├── Monad.kt │ │ ├── Stream.kt │ │ └── Channel.kt └── build.gradle.kts ├── gradlew.bat ├── .gitignore ├── README.md └── gradlew /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happy-bracket/dolphins/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/functions/Predef.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.functions 2 | 3 | fun self(value: A): A = value -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/types/channel/Channel.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.types.channel 2 | 3 | interface ChannelVal -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/Kind.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation 2 | 3 | interface Kind 4 | typealias Kind2 = Kind, B> -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'dolphins' 2 | include 'core' 3 | findProject(':core')?.name = 'dolphins-core' 4 | include 'rxjava' 5 | findProject(':rxjava')?.name = 'dolphins-rxjava' 6 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/core/Core.kt: -------------------------------------------------------------------------------- 1 | package dolphins.core 2 | 3 | data class Core( 4 | val initialState: S, 5 | val initialEffects: Set, 6 | val update: (S, M) -> Pair> 7 | ) -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/functions/Composition.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.functions 2 | 3 | fun compose(bc: (B) -> C, ab: (A) -> B): (A) -> C = 4 | { 5 | bc(ab(it)) 6 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/ShadyException.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation 2 | 3 | class ShadyException(className: String) : Exception( 4 | "The instance of this $className is shady and behaves not according to laws for the sake of performance." 5 | ) -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/Contravariant.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind 4 | 5 | interface Contravariant { 6 | 7 | fun Kind.preMap(f: (A) -> B): Kind 8 | 9 | } -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/types/RxContext.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.types 2 | 3 | import dolphins.foundation.typeclasses.ExecContext 4 | import io.reactivex.Scheduler 5 | 6 | class RxContext(val scheduler: Scheduler) : ExecContext 7 | 8 | fun ExecContext.fix() = (this as RxContext).scheduler -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/types/RxChannel.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.types 2 | 3 | import dolphins.foundation.types.channel.ChannelVal 4 | import io.reactivex.subjects.Subject 5 | 6 | class RxChannel(val subject: Subject) : ChannelVal 7 | 8 | fun ChannelVal.fix(): RxChannel = this as RxChannel -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/Monad.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind 4 | 5 | interface Monad : Applicative { 6 | 7 | fun just(value: A): Kind 8 | 9 | fun Kind.flatMap(f: (A) -> Kind): Kind 10 | 11 | } -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/feature/Utilities.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.feature 2 | 3 | import dolphins.rx.types.fix 4 | import io.reactivex.disposables.Disposable 5 | 6 | fun RxFeature<*, Ev, *, *>.rxMutate(event: Ev): Disposable = 7 | mutate(event).fix() 8 | 9 | fun RxFeature.rxState() = 10 | state().fix() -------------------------------------------------------------------------------- /.idea/sbt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/types/RxHandle.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.types 2 | 3 | import dolphins.foundation.typeclasses.Handle 4 | import io.reactivex.disposables.Disposable 5 | 6 | class RxHandle(val disposable: Disposable) : Handle { 7 | override fun release() { 8 | disposable.dispose() 9 | } 10 | } 11 | 12 | fun Handle.fix() = (this as RxHandle).disposable -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/core/Mono.kt: -------------------------------------------------------------------------------- 1 | package dolphins.core 2 | 3 | typealias MonoFeature = Feature 4 | 5 | fun MonoFeature( 6 | deps: FunDeps, 7 | core: Core, 8 | handler: Handler 9 | ): MonoFeature = 10 | Feature( 11 | deps, 12 | core, 13 | Identity(deps), 14 | handler 15 | ) -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/Consume.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind 4 | 5 | /** 6 | * Describes imperative consumption of a delayed computation, with a handle of type [H] to cancel it 7 | */ 8 | interface Consume { 9 | 10 | fun Kind.consume(f: (A) -> Unit): Handle 11 | 12 | } 13 | 14 | interface Handle { 15 | 16 | fun release() 17 | 18 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IDE 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/core/FunDeps.kt: -------------------------------------------------------------------------------- 1 | package dolphins.core 2 | 3 | import dolphins.foundation.typeclasses.* 4 | 5 | class FunDepsBag( 6 | private val channel: Channel, 7 | private val shift: Shift, 8 | private val consume: Consume, 9 | private val stream: Stream 10 | ) : FunDeps, Channel by channel, Stream by stream, Shift by shift, Consume by consume 11 | 12 | interface FunDeps : Channel, Stream, Shift, Consume 13 | -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/feature/FunDepBag.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.feature 2 | 3 | import dolphins.core.FunDepsBag 4 | import dolphins.rx.instances.flowable.Channel 5 | import dolphins.rx.instances.flowable.Consume 6 | import dolphins.rx.instances.flowable.Shift 7 | import dolphins.rx.instances.flowable.Stream 8 | import dolphins.rx.types.Rx 9 | 10 | val RxDepBag = 11 | FunDepsBag( 12 | Rx.Channel, 13 | Rx.Shift, 14 | Rx.Consume, 15 | Rx.Stream 16 | ) -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/types/Rx.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.types 2 | 3 | import dolphins.foundation.Kind 4 | import io.reactivex.Observable 5 | 6 | class ForRx private constructor() 7 | typealias RxOf = Kind 8 | 9 | class Rx(val flow: Observable) : RxOf { 10 | companion object 11 | } 12 | 13 | fun RxOf.fix() = (this as Rx).flow 14 | fun Observable.unfix() = Rx(this) 15 | fun RxOf.fixed(f: Observable.() -> Observable): RxOf = 16 | fix().let(f).unfix() -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/Channel.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.types.channel.ChannelVal 5 | 6 | interface Channel { 7 | 8 | fun through(): ChannelVal 9 | 10 | fun conflated(): ChannelVal 11 | fun conflated(default: A): ChannelVal 12 | 13 | fun ChannelVal.write(value: A): Kind 14 | 15 | fun ChannelVal.suspendRead(): Kind 16 | 17 | } -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/Conest.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation 2 | 3 | interface Conested 4 | 5 | typealias ConestedType = Kind, A> 6 | 7 | typealias CounnestedType = Kind, B> 8 | 9 | @Suppress("UNCHECKED_CAST") 10 | fun CounnestedType.conest(): ConestedType = this as ConestedType 11 | 12 | @Suppress("UNCHECKED_CAST") 13 | fun ConestedType.counnest(): CounnestedType = this as CounnestedType -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/core/Fin.kt: -------------------------------------------------------------------------------- 1 | package dolphins.core 2 | 3 | /** 4 | * Represents abstract middleware. 5 | * [Fin] can see into internal [Structure] of [Feature], making dirty changes to it (although, without altering types) 6 | * or simply observing, and emitting a new structure. 7 | * On creation, [Feature] runs all [Fin]s that it is passed to, computing final [Structure] that it will work upon. 8 | */ 9 | interface Fin { 10 | 11 | fun examine(structure: Structure): Structure 12 | 13 | } -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/Shift.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind 4 | 5 | /** 6 | * For concurrent primitives, provides ways to change execution context. 7 | */ 8 | interface Shift { 9 | 10 | fun io(): ExecContext 11 | fun computation(): ExecContext 12 | fun Kind.shiftTo(execContext: ExecContext): Kind 13 | 14 | } 15 | 16 | interface UiShift : Shift { 17 | 18 | fun main(): ExecContext 19 | 20 | } 21 | 22 | interface ExecContext -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/types/either/Either.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.types.either 2 | 3 | sealed class Either { 4 | 5 | data class Left(val value: L) : Either() 6 | data class Right(val value: R) : Either() 7 | 8 | fun fold(ifLeft: (L) -> C, ifRight: (R) -> C): C = 9 | when (this) { 10 | is Left -> ifLeft(value) 11 | is Right -> ifRight(value) 12 | } 13 | 14 | companion object 15 | } 16 | 17 | fun T.left() = Either.Left(this) 18 | fun T.right() = Either.Right(this) -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/instances/flowable/Consume.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.instances.flowable 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.typeclasses.Consume 5 | import dolphins.rx.types.ForRx 6 | import dolphins.rx.types.Rx 7 | import dolphins.rx.types.RxHandle 8 | import dolphins.rx.types.fix 9 | 10 | private val RxConsumeInstance: Consume = 11 | object : Consume { 12 | 13 | override fun Kind.consume(f: (A) -> Unit): RxHandle = 14 | RxHandle(fix().subscribe(f)) 15 | 16 | } 17 | 18 | val Rx.Companion.Consume 19 | get() = RxConsumeInstance -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/Functor.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.ShadyException 5 | 6 | interface Functor { 7 | 8 | fun lift(f: (A) -> B): (Kind) -> Kind 9 | 10 | fun Kind.fmap(f: (A) -> B): Kind = 11 | lift(f)(this) 12 | 13 | } 14 | 15 | interface ShadyFunctor : Functor { 16 | 17 | override fun lift(f: (A) -> B): (Kind) -> Kind = 18 | throw ShadyException("Functor") 19 | 20 | override fun Kind.fmap(f: (A) -> B): Kind 21 | 22 | } -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/Stream.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind 4 | 5 | /** 6 | * Identifies that [F], being a monad, can produce more than one value and thus different instances of `F<_>` 7 | * can be composed accordingly. 8 | */ 9 | interface Stream : Monad { 10 | 11 | fun Kind.scan(acc: B, f: (B, A) -> B): Kind 12 | 13 | fun merge( 14 | vararg ss: Kind 15 | ) : Kind 16 | 17 | fun merge( 18 | ss: List> 19 | ): Kind 20 | 21 | fun Kind.take(number: Int): Kind 22 | 23 | } -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/instances/flowable/Functor.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.instances.flowable 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.typeclasses.Functor 5 | import dolphins.foundation.typeclasses.ShadyFunctor 6 | import dolphins.rx.types.ForRx 7 | import dolphins.rx.types.Rx 8 | import dolphins.rx.types.fix 9 | import dolphins.rx.types.unfix 10 | 11 | private val RxFunctorInstance : Functor = 12 | object : ShadyFunctor { 13 | 14 | override fun Kind.fmap(f: (A) -> B): Kind = 15 | fix().map(f).unfix() 16 | 17 | } 18 | 19 | val Rx.Companion.Functor 20 | get() = RxFunctorInstance -------------------------------------------------------------------------------- /rxjava/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | kotlin("jvm") 5 | `maven-publish` 6 | } 7 | 8 | group = "dolphins" 9 | version = "0.1.0" 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation(kotlin("stdlib-jdk8")) 17 | api(project(":dolphins-core")) 18 | api("io.reactivex.rxjava2:rxjava:2.2.15") 19 | } 20 | 21 | publishing { 22 | publications { 23 | create("maven") { 24 | groupId = "dolphins" 25 | artifactId = "rx-dolphins" 26 | version = "0.1.0" 27 | 28 | from(components["kotlin"]) 29 | } 30 | } 31 | } 32 | 33 | tasks.withType { 34 | kotlinOptions.jvmTarget = "1.8" 35 | } -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/instances/flowable/Shift.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.instances.flowable 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.typeclasses.ExecContext 5 | import dolphins.foundation.typeclasses.Shift 6 | import dolphins.rx.types.* 7 | import io.reactivex.schedulers.Schedulers 8 | 9 | private val RxShiftInstance: Shift = 10 | object : Shift { 11 | 12 | override fun computation(): ExecContext = 13 | RxContext(Schedulers.computation()) 14 | 15 | override fun io(): ExecContext = 16 | RxContext(Schedulers.io()) 17 | 18 | override fun Kind.shiftTo(execContext: ExecContext): Kind = 19 | fix().observeOn(execContext.fix()).unfix() 20 | 21 | } 22 | 23 | val Rx.Companion.Shift 24 | get() = RxShiftInstance -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/Applicative.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.ShadyException 5 | 6 | interface Applicative : Functor { 7 | 8 | fun lift2(f: (A, B) -> C): (Kind, Kind) -> Kind 9 | 10 | fun zip(a: Kind, b: Kind, f: (A, B) -> C): Kind = 11 | lift2(f)(a, b) 12 | 13 | fun pair(a: Kind, b: Kind): Kind> = 14 | zip(a, b, ::Pair) 15 | 16 | } 17 | 18 | interface ShadyApplicative : Applicative { 19 | 20 | override fun lift2(f: (A, B) -> C): (Kind, Kind) -> Kind = 21 | throw ShadyException("Applicative") 22 | 23 | override fun zip(a: Kind, b: Kind, f: (A, B) -> C): Kind 24 | 25 | } -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/feature/RxFeature.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.feature 2 | 3 | import dolphins.core.Core 4 | import dolphins.core.Feature 5 | import dolphins.core.Handler 6 | import dolphins.core.Identity 7 | import dolphins.rx.types.ForRx 8 | 9 | typealias RxFeature = Feature 10 | typealias RxMonoFeature = RxFeature 11 | 12 | fun RxFeature( 13 | core: Core, 14 | coeffects: Handler, 15 | effects: Handler 16 | ): RxFeature = 17 | Feature( 18 | RxDepBag, 19 | core, 20 | coeffects, 21 | effects 22 | ) 23 | 24 | fun RxMonoFeature( 25 | core: Core, 26 | effects: RxHandler 27 | ): RxMonoFeature = 28 | RxFeature( 29 | core, 30 | Identity(RxDepBag), 31 | effects 32 | ) -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/feature/RxHandler.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.feature 2 | 3 | import dolphins.core.FunDeps 4 | import dolphins.core.Handler 5 | import dolphins.core.postMap 6 | import dolphins.core.preMap 7 | import dolphins.foundation.Kind 8 | import dolphins.rx.types.ForRx 9 | import dolphins.rx.types.unfix 10 | import io.reactivex.Observable 11 | 12 | /** 13 | * Override this to create effect and coeffect handlers. 14 | */ 15 | abstract class RxHandler : Handler, FunDeps by RxDepBag { 16 | 17 | override fun kindfulHandle(e: E): Kind = handle(e).unfix() 18 | 19 | abstract fun handle(e: E): Observable 20 | 21 | } 22 | 23 | /** 24 | * Creates handler out of air 25 | */ 26 | fun rxHandler(handle: (E) -> Observable): RxHandler = 27 | object : RxHandler() { 28 | override fun handle(e: E): Observable = handle(e) 29 | } -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/instances/flowable/Applicative.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.instances.flowable 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.typeclasses.Applicative 5 | import dolphins.foundation.typeclasses.Functor 6 | import dolphins.foundation.typeclasses.ShadyApplicative 7 | import dolphins.rx.types.ForRx 8 | import dolphins.rx.types.Rx 9 | import dolphins.rx.types.fix 10 | import dolphins.rx.types.unfix 11 | import io.reactivex.Observable 12 | import io.reactivex.functions.BiFunction 13 | 14 | private val RxApplicativeInstance: Applicative = 15 | object : ShadyApplicative, Functor by Rx.Functor { 16 | 17 | override fun zip(a: Kind, b: Kind, f: (A, B) -> C): Kind = 18 | Observable.zip(a.fix(), b.fix(), BiFunction(f)).unfix() 19 | 20 | } 21 | 22 | val Rx.Companion.Applicative 23 | get() = RxApplicativeInstance -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/instances/flowable/Monad.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.instances.flowable 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.typeclasses.Applicative 5 | import dolphins.foundation.typeclasses.Functor 6 | import dolphins.foundation.typeclasses.Monad 7 | import dolphins.rx.types.ForRx 8 | import dolphins.rx.types.Rx 9 | import dolphins.rx.types.fix 10 | import dolphins.rx.types.unfix 11 | import io.reactivex.Observable 12 | 13 | private val RxMonadInstance: Monad = 14 | object : Monad, Applicative by Rx.Applicative { 15 | 16 | override fun just(value: A): Kind = 17 | Observable.just(value).unfix() 18 | 19 | override fun Kind.flatMap(f: (A) -> Kind): Kind = 20 | fix().flatMap { f(it).fix() }.unfix() 21 | 22 | } 23 | 24 | val Rx.Companion.Monad 25 | get() = RxMonadInstance -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | `maven-publish` 4 | } 5 | 6 | group = "dolphins" 7 | version = "0.1.0" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | kotlin { 14 | 15 | jvm { 16 | withJava() 17 | } 18 | /* 19 | js() 20 | iosArm32(); iosArm64(); iosX64()*/ 21 | // commented out for now to not interfere with publishing process 22 | 23 | sourceSets { 24 | val commonMain by getting { 25 | dependencies { 26 | implementation(kotlin("stdlib-common")) 27 | } 28 | } 29 | val commonTest by getting { 30 | dependencies { 31 | implementation(kotlin("test-common")) 32 | implementation(kotlin("test-annotations-common")) 33 | } 34 | } 35 | val jvmMain by getting { 36 | dependencies { 37 | implementation(kotlin("stdlib-jdk8")) 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/foundation/typeclasses/BiFunctor.kt: -------------------------------------------------------------------------------- 1 | package dolphins.foundation.typeclasses 2 | 3 | import dolphins.foundation.Kind2 4 | import dolphins.foundation.functions.self 5 | 6 | interface BiFunctor { 7 | 8 | fun Kind2.lmap(f: (A) -> A1): Kind2 9 | 10 | fun Kind2.rmap(f: (B) -> B1): Kind2 11 | 12 | fun Kind2.bimap(af: (A) -> A1, bf: (B) -> B1): Kind2 13 | 14 | } 15 | 16 | interface BiFunctorBimap : BiFunctor { 17 | 18 | override fun Kind2.bimap(af: (A) -> A1, bf: (B) -> B1): Kind2 = 19 | lmap(af).rmap(bf) 20 | 21 | } 22 | 23 | interface BiFunctorRLMap : BiFunctor { 24 | 25 | override fun Kind2.lmap(f: (A) -> A1): Kind2 = 26 | bimap(f, ::self) 27 | 28 | override fun Kind2.rmap(f: (B) -> B1): Kind2 = 29 | bimap(::self, f) 30 | 31 | } -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/instances/flowable/Stream.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.instances.flowable 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.typeclasses.Monad 5 | import dolphins.foundation.typeclasses.Stream 6 | import dolphins.rx.types.* 7 | import io.reactivex.Observable 8 | 9 | private val RxStreamInstance: Stream = 10 | object : Stream, Monad by Rx.Monad { 11 | 12 | override fun Kind.scan(acc: B, f: (B, A) -> B): Kind = 13 | fixed { scan(acc, f) } 14 | 15 | override fun merge(vararg ss: Kind): Kind = 16 | Observable.merge(ss.map { it.fix() }).unfix() 17 | 18 | override fun merge(ss: List>): Kind = 19 | Observable.merge(ss.map { it.fix() }).unfix() 20 | 21 | override fun Kind.take(number: Int): Kind = 22 | fixed { take(number.toLong()) } 23 | 24 | } 25 | 26 | val Rx.Companion.Stream 27 | get() = RxStreamInstance -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/core/Structure.kt: -------------------------------------------------------------------------------- 1 | package dolphins.core 2 | 3 | import dolphins.foundation.types.channel.ChannelVal 4 | 5 | /** 6 | * Represents what will be going on in [Feature] 7 | * [core], [coeffectHandler] and [effectHandler] are exposed for change, 8 | * while [deps] and channels are only available for observation. 9 | */ 10 | data class Structure( 11 | private val deps: FunDeps, 12 | val core: Core, 13 | val coeffectHandler: Handler, 14 | val effectHandler: Handler, 15 | private val eventChannel: ChannelVal, 16 | private val mutationChannel: ChannelVal, 17 | private val stateChannel: ChannelVal, 18 | private val effectChannel: ChannelVal> 19 | ) { 20 | 21 | fun liftF(action: FunDeps.() -> R): R = 22 | deps.run(action) 23 | 24 | fun observeEvents() = eventChannel 25 | fun observeMutations() = mutationChannel 26 | fun observeState() = stateChannel 27 | fun observeEffects() = effectChannel 28 | 29 | } -------------------------------------------------------------------------------- /rxjava/src/main/kotlin/dolphins/rx/instances/flowable/Channel.kt: -------------------------------------------------------------------------------- 1 | package dolphins.rx.instances.flowable 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.typeclasses.Channel 5 | import dolphins.foundation.types.channel.ChannelVal 6 | import dolphins.rx.types.* 7 | import io.reactivex.Observable 8 | import io.reactivex.subjects.BehaviorSubject 9 | import io.reactivex.subjects.PublishSubject 10 | 11 | private val RxChannelInstance = 12 | object : Channel { 13 | 14 | override fun conflated(): ChannelVal = 15 | RxChannel(BehaviorSubject.create()) 16 | 17 | override fun conflated(default: A): ChannelVal = 18 | RxChannel(BehaviorSubject.createDefault(default)) 19 | 20 | override fun through(): ChannelVal = 21 | RxChannel(PublishSubject.create()) 22 | 23 | override fun ChannelVal.write(value: A): Kind = 24 | Observable.fromCallable { 25 | fix().subject.onNext(value) 26 | }.unfix() 27 | 28 | override fun ChannelVal.suspendRead(): Kind = 29 | fix().subject.unfix() 30 | 31 | } 32 | 33 | val Rx.Companion.Channel 34 | get() = RxChannelInstance -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/core/Handler.kt: -------------------------------------------------------------------------------- 1 | package dolphins.core 2 | 3 | import dolphins.foundation.Kind 4 | 5 | interface Handler : FunDeps { 6 | 7 | /** 8 | * DO NOT OVERRIDE. This method is for internal uses only. 9 | */ 10 | fun kindfulHandle(e: E): Kind 11 | 12 | } 13 | 14 | fun handler(deps: FunDeps, handle: (E) -> Kind): Handler = 15 | object : Handler, FunDeps by deps { 16 | 17 | override fun kindfulHandle(e: E): Kind = 18 | handle(e) 19 | 20 | } 21 | 22 | fun Handler.preMap(handler: Handler): Handler = 23 | handler(this) { e1 -> 24 | handler.kindfulHandle(e1) 25 | .flatMap { e2 -> 26 | this.kindfulHandle(e2) 27 | } 28 | } 29 | 30 | fun Handler.postMap(handler: Handler): Handler = 31 | handler(this) { e -> 32 | this.kindfulHandle(e) 33 | .flatMap { m -> 34 | handler.kindfulHandle(m) 35 | } 36 | } 37 | 38 | class Identity(funDeps: FunDeps) : Handler, FunDeps by funDeps { 39 | 40 | override fun kindfulHandle(e: M): Kind = 41 | just(e) 42 | 43 | } -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/patterns/GhostMutation.kt: -------------------------------------------------------------------------------- 1 | package dolphins.patterns 2 | 3 | import dolphins.core.Core 4 | import dolphins.core.Feature 5 | import dolphins.core.FunDeps 6 | import dolphins.core.Handler 7 | 8 | /** 9 | * Special mutation, which eliminates the `M` type from `Feature` and instead is defined by state [S] and effect [E]. 10 | * `Feature` with ghost mutations will look like this: 11 | * ```kotlin 12 | * typealias GhostFeature = Feature, E> 13 | * ``` 14 | */ 15 | data class GhostMutation( 16 | val changeState: (S) -> S, 17 | val emitEffects: (S) -> Set 18 | ) 19 | 20 | fun ghostUpdate(state: S, mutation: GhostMutation): Pair> = 21 | mutation.changeState(state) to mutation.emitEffects(state) 22 | 23 | typealias GhostCore = Core, E> 24 | 25 | fun GhostCore(initialState: S, initialEffects: Set): GhostCore = 26 | Core( 27 | initialState, 28 | initialEffects, 29 | ::ghostUpdate 30 | ) 31 | 32 | typealias GhostFeature = Feature, E> 33 | 34 | fun GhostingFeature( 35 | deps: FunDeps, 36 | core: GhostCore, 37 | coeffectHandler: Handler>, 38 | effectHandler: Handler 39 | ): GhostFeature = 40 | Feature( 41 | deps, 42 | core, 43 | coeffectHandler, 44 | effectHandler 45 | ) -------------------------------------------------------------------------------- /.idea/modules/core/dolphins.dolphins-core.commonMain.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SOURCE_SET_HOLDER 7 | 8 | 9 | 14 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/modules/core/dolphins.dolphins-core.commonTest.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SOURCE_SET_HOLDER 7 | jvmTest|:dolphins-core:jvmTest|jvm 8 | 9 | 10 | 15 | 26 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | 30 | 31 | 33 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 56 | 57 | 58 | 60 | 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,gradle,kotlin,intellij 3 | # Edit at https://www.gitignore.io/?templates=java,gradle,kotlin,intellij 4 | 5 | ### Intellij ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | .idea/artifacts/* 16 | 17 | # Generated files 18 | .idea/**/contentModel.xml 19 | 20 | # Sensitive or high-churn files 21 | .idea/**/dataSources/ 22 | .idea/**/dataSources.ids 23 | .idea/**/dataSources.local.xml 24 | .idea/**/sqlDataSources.xml 25 | .idea/**/dynamic.xml 26 | .idea/**/uiDesigner.xml 27 | .idea/**/dbnavigator.xml 28 | 29 | # Gradle 30 | .idea/**/gradle.xml 31 | .idea/**/libraries 32 | 33 | # Gradle and Maven with auto-import 34 | # When using Gradle or Maven with auto-import, you should exclude module files, 35 | # since they will be recreated, and may cause churn. Uncomment if using 36 | # auto-import. 37 | # .idea/modules.xml 38 | # .idea/*.iml 39 | # .idea/modules 40 | # *.iml 41 | # *.ipr 42 | 43 | # CMake 44 | cmake-build-*/ 45 | 46 | # Mongo Explorer plugin 47 | .idea/**/mongoSettings.xml 48 | 49 | # File-based project format 50 | *.iws 51 | 52 | # IntelliJ 53 | out/ 54 | 55 | # mpeltonen/sbt-idea plugin 56 | .idea_modules/ 57 | 58 | # JIRA plugin 59 | atlassian-ide-plugin.xml 60 | 61 | # Cursive Clojure plugin 62 | .idea/replstate.xml 63 | 64 | # Crashlytics plugin (for Android Studio and IntelliJ) 65 | com_crashlytics_export_strings.xml 66 | crashlytics.properties 67 | crashlytics-build.properties 68 | fabric.properties 69 | 70 | # Editor-based Rest Client 71 | .idea/httpRequests 72 | 73 | # Android studio 3.1+ serialized cache file 74 | .idea/caches/build_file_checksums.ser 75 | 76 | ### Intellij Patch ### 77 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 78 | 79 | # *.iml 80 | # modules.xml 81 | # .idea/misc.xml 82 | # *.ipr 83 | 84 | # Sonarlint plugin 85 | .idea/**/sonarlint/ 86 | 87 | # SonarQube Plugin 88 | .idea/**/sonarIssues.xml 89 | 90 | # Markdown Navigator plugin 91 | .idea/**/markdown-navigator.xml 92 | .idea/**/markdown-navigator/ 93 | 94 | ### Java ### 95 | # Compiled class file 96 | *.class 97 | 98 | # Log file 99 | *.log 100 | 101 | # BlueJ files 102 | *.ctxt 103 | 104 | # Mobile Tools for Java (J2ME) 105 | .mtj.tmp/ 106 | 107 | # Package Files # 108 | *.jar 109 | *.war 110 | *.nar 111 | *.ear 112 | *.zip 113 | *.tar.gz 114 | *.rar 115 | 116 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 117 | hs_err_pid* 118 | 119 | ### Kotlin ### 120 | # Compiled class file 121 | 122 | # Log file 123 | 124 | # BlueJ files 125 | 126 | # Mobile Tools for Java (J2ME) 127 | 128 | # Package Files # 129 | 130 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 131 | 132 | ### Gradle ### 133 | .gradle 134 | build/ 135 | 136 | # Ignore Gradle GUI config 137 | gradle-app.setting 138 | 139 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 140 | !gradle-wrapper.jar 141 | 142 | # Cache of project 143 | .gradletasknamecache 144 | 145 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 146 | # gradle/wrapper/gradle-wrapper.properties 147 | 148 | ### Gradle Patch ### 149 | **/build/ 150 | 151 | # End of https://www.gitignore.io/api/java,gradle,kotlin,intellij -------------------------------------------------------------------------------- /.idea/modules/core/dolphins.dolphins-core.jvmMain.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | dolphins-core_commonMain 7 | 8 | dolphins.dolphins-core.commonMain 9 | 10 | COMPILATION_AND_SOURCE_SET_HOLDER 11 | 12 | 14 | 15 | 25 | 36 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/dolphins/core/Feature.kt: -------------------------------------------------------------------------------- 1 | package dolphins.core 2 | 3 | import dolphins.foundation.Kind 4 | import dolphins.foundation.typeclasses.* 5 | 6 | /** 7 | * Core class, which implements basically everything. 8 | * On creation, it launches controllable loop of receiving events of type [Ev] through [mutate], transforming it into 9 | * mutations of type [Mu] (possibly with side-effects) and then emitting a new state of type [St] to all subscribers of [state]. 10 | * The feature can be disposed of, if required, by invoking the [kill] method. 11 | * @param F an HKT of the underlying async library 12 | * @param St state type 13 | * @param Ev event type, which passes through coeffect handler and is mapped to [Mu] 14 | * @param Mu mutations type, used in update 15 | * @param Ef effect type 16 | * @param deps pack of functional dependencies (typeclasses instances) 17 | * @param core pack of pure stuff: initial values and an update function 18 | * @param coeffectHandler transformer from [Ev] to [Mu]. It may perform side-effects 19 | * @param effectHandler interprets effects of type [Ef], returned from update, mapping them to actual side-effects 20 | * and returning resulting event [Ev] back into the loop 21 | */ 22 | class Feature( 23 | deps: FunDeps, 24 | core: Core, 25 | coeffectHandler: Handler, 26 | effectHandler: Handler, 27 | fins: List> = emptyList() 28 | ) : FunDeps by deps { 29 | 30 | private val eventChannel = through() 31 | private val mutationChannel = through() 32 | private val stateChannel = conflated(core.initialState) 33 | private val effectChannel = through>() 34 | 35 | private val flowHandle: Handle 36 | 37 | init { 38 | val struct = Structure( 39 | deps, core, 40 | coeffectHandler, effectHandler, 41 | eventChannel, mutationChannel, 42 | stateChannel, effectChannel 43 | ) 44 | 45 | val finalStruct = fins.fold(struct) { str, fin -> 46 | fin.examine(str) 47 | } 48 | 49 | finalStruct.run { 50 | 51 | flowHandle = eventChannel 52 | .suspendRead() 53 | .shiftTo(io()) 54 | .flatMap { event -> coeffectHandler.kindfulHandle(event) } 55 | .flatMap { mutationChannel.write(it) } 56 | .consume {} 57 | 58 | mutationChannel 59 | .suspendRead() 60 | .shiftTo(io()) 61 | .flatMap { mutation -> 62 | pair( 63 | stateChannel.suspendRead().take(1), 64 | just(mutation) 65 | ) 66 | }.shiftTo(computation()) 67 | .fmap { (state, mutation) -> 68 | core.update(state, mutation) 69 | }.flatMap { (state, effects) -> 70 | pair( 71 | stateChannel.write(state), 72 | effectChannel.write(effects) 73 | ) 74 | }.consume {} 75 | 76 | effectChannel 77 | .suspendRead() 78 | .shiftTo(io()) 79 | .flatMap { effects -> 80 | merge(effects.map(effectHandler::kindfulHandle)) 81 | }.flatMap { m -> 82 | eventChannel.write(m) 83 | }.consume {} 84 | 85 | } 86 | } 87 | 88 | /** 89 | * Method used to trigger state update 90 | */ 91 | fun mutate(event: Ev): Handle = 92 | eventChannel.write(event) 93 | .consume {} 94 | 95 | /** 96 | * @return stream of states 97 | */ 98 | fun state(): Kind = 99 | stateChannel.suspendRead() 100 | 101 | /** 102 | * Ends life of the feature, stopping any updates and disposing of feature lifecycle scope 103 | */ 104 | fun kill() { 105 | flowHandle.release() 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🐬 Dolphins! 2 | Dolphins is intended to be zero-dependency (hence multiplatform by default), highly customizable library (framework?) 3 | for writing applications using declarative architecture named **The Elm Architecture** (TEA for short). 4 | 5 | ## What is TEA? 6 | Very generally, TEA allows you to encode your UI state management in terms of immutable (or persistent) data structures and pure functions, then supply those to some system, which is able to perform side effects (for Dolphins, it's `dolphins.core.Feature`) and... that's all. 7 | 8 | Let's see an example. This is a simple anonymous chat, where messages are received through, maybe, WebSocket (it is actually not relevant at all right now). The user can type in his own message and send them. 9 | ```kotlin 10 | data class ChatState( 11 | val messages: List, 12 | val input: String 13 | ) 14 | 15 | sealed class ChatMutation { 16 | data class NewMessage(val message: Message) : ChatMutation() 17 | data class NewInput(val input: String) : ChatMutation() 18 | object SendMessage : ChatMutation() 19 | } 20 | 21 | sealed class ChatEffect { 22 | data class SendMessage(val text: String) : ChatEffect() 23 | object SubscribeToChat : ChatEffect() 24 | } 25 | 26 | fun ChatState.update(mutation: ChatMutation): Pair> = 27 | when (mutation) { 28 | is ChatMutation.NewMessage -> copy(messages = messages + mutation.message) to emptySet() 29 | is ChatMutation.NewInput -> copy(input = mutation.input) to emptySet() 30 | is ChatMutation.SendMessage -> copy(input = "") to setOf(ChatEffect.SendMessage(input)) 31 | } 32 | ``` 33 | Here `ChatState` is a core immutable data class, which encodes relevant application state. `ChatMutation` is a subset of types which encode **what** changes your state. Function `update` describes **how** state changes in response to `ChatMutation`. `ChatEffect` then encodes some actions, which are either go-all-round or fire-and-forget. Examples include network requests and database interactions. Notice that those are also pure data classes - the actual activity happens in another entity, called "EffectHandler". 34 | Then you take a `Feature` constructor of chosen flavor, shove it all in and then you have a complete working object, through which you can mutate the state and subscribe to its emission to update your UI. 35 | 36 | It's worth noting that nothing of that is platform-dependent and thus you can transfer your whole interaction logic between platforms. All you need is to choose appropriate implementation of `Feature`. Although, you'd probably want to adapt the state you get to state that's most convenient to display on your UI. 37 | 38 | Complete sample: https://gist.github.com/happy-bracket/8ca92b70d48d2ab4e2680b4ab318061c 39 | 40 | ## Features of 🐬 Dolphins 41 | ### Unopinionated 42 | Usually we employ a certain set of libraries and patterns to write UI code. It might be reactivity along with RxJava, reactivity through MVVM with Lychee or LiveData. It especially varies among other platforms, where there's React or Angular or something else in Web, RxSwift on iOS and JavaFX with its own databinding on Desktop. 43 | 44 | Well, Dolphins can handle most of that. Through a certain feature of type system called **HKT** (a lot of examples can be seen in arrow-kt.io), which abstracts over the type of effect. So, with a little effort (or no effort at all, depending on whether your favorite library is supported out-of-the-box), Your `Feature` can return `Observable`, `Flow` or you can even simply put a listener on it. 45 | ### Extensible 46 | With middlewares (`dolphins.core.Fin`) you can polymorphically add new behaviors to `Feature`-s, such as logging, debugging mocks (or stubs, or fakes), time-travel and practically anything you want. 47 | ### Effectful 48 | Handlers presented in Dolphins allow you to process effects separately from your actual pure business- or UI-logic. They are composable, such that you can create chains of Handlers with different types (proficient FP developers might recognize Kleisli arrows here - yes, exactly). Going further - you get Coeffect Handlers, which allow you to reduce the number of effects by pre-composing `update`. Samples to see how great it is will be out soon. 49 | 50 | ## What is Dolphins in the long run? 51 | It is in very early stage and highly depends on my ability or wish to develop it. 52 | 53 | Planned features: 54 | - Time travel 55 | - Logging 56 | - Debugging utils 57 | - Testing utils 58 | - Predefined effects for networking and database 59 | 60 | Platforms to support: 61 | - Kotlin Coroutines 62 | - RxJS 63 | - RxSwift (not sure, need to consult to iOS developers) 64 | -------------------------------------------------------------------------------- /.idea/modules/core/dolphins.dolphins-core.jvmTest.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | dolphins-core_commonTest 7 | 8 | dolphins.dolphins-core.jvmMain 9 | dolphins.dolphins-core.commonMain 10 | dolphins.dolphins-core.commonTest 11 | 12 | COMPILATION_AND_SOURCE_SET_HOLDER 13 | jvmTest|:dolphins-core:jvmTest|jvm 14 | 15 | 17 | 18 | 29 | 34 | 45 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | --------------------------------------------------------------------------------