├── .github └── workflows │ └── scala.yml ├── .gitignore ├── .mergify.yml ├── .scalafmt.conf ├── AbstractAlgebra.md ├── Adjunction.md ├── BasicAbstractions.md ├── Bifunctors.md ├── CombinatoryBirds.md ├── Comonads.md ├── ComputationalTrinitarianism.md ├── Contravariant.md ├── Free.md ├── FunctionalDataStructures.md ├── HigherKinded.md ├── KanExtensions.md ├── LICENSE ├── Limits.md ├── Optics.md ├── OtherEncodingsOfCT.md ├── Profunctors.md ├── README.md ├── RecursionSchemas.md ├── Topos.md ├── build.sbt ├── img ├── braided_hexagon1.svg ├── braided_hexagon2.svg ├── fun_trinc2.svg ├── mon_cat_triangle.svg ├── monoidal_pentagon.svg ├── pentagon_tuple.svg ├── single_op_abstract_algebra.svg ├── triangle_either.svg └── triangle_tuple.svg ├── notes ├── Computational Quadrinitarianism.MD ├── Hitchhiker's Guide to the Quadrinitarianism.MD ├── Monoidal Categories Notes.MD └── Periodic table of n-categories Notes.MD ├── project ├── build.properties └── plugins.sbt └── src ├── main └── scala │ ├── bifunctor │ └── InstancesForForBuildInTypes.scala │ ├── cayley │ └── CayleyTheorem.scala │ ├── contravariant │ └── HoFStructure.scala │ ├── ct_other_impls │ ├── DataCategory.scala │ └── ModularProfunctorBifunctorHierarchy.scala │ ├── disintegrate │ └── Disintegrate.scala │ ├── educational │ ├── abstract_algebra │ │ ├── Monoid.scala │ │ └── Semigroup.scala │ ├── category_theory │ │ ├── Alternative.scala │ │ ├── Applicative.scala │ │ ├── Apply.scala │ │ ├── Comonad.scala │ │ ├── Cont.scala │ │ ├── ContT.scala │ │ ├── Dijkstra.scala │ │ ├── Foldable.scala │ │ ├── Functor.scala │ │ ├── Invariant.scala │ │ ├── Monad.scala │ │ ├── StateMonad.scala │ │ ├── Traverse.scala │ │ ├── adjunct_rep │ │ │ ├── Adjunction.scala │ │ │ └── RepresentableSimpleImpl.scala │ │ ├── contra │ │ │ ├── Contravariant.scala │ │ │ ├── ContravariantCoyoneda.scala │ │ │ ├── Decidable.scala │ │ │ ├── Decide.scala │ │ │ ├── Divide.scala │ │ │ └── Divisible.scala │ │ ├── higher │ │ │ ├── CategoryK.scala │ │ │ ├── NaturalTransformation.scala │ │ │ └── monoidal │ │ │ │ ├── AlgebraOfADTs.scala │ │ │ │ ├── MonoidalCategory.scala │ │ │ │ └── MonoidalObject.scala │ │ ├── kan │ │ │ ├── Codensity.scala │ │ │ ├── Coyoneda.scala │ │ │ ├── DayConvolution.scala │ │ │ ├── DensitySimpleImpl.scala │ │ │ ├── LeftKanExtension.scala │ │ │ ├── LimitsAndColimits.scala │ │ │ ├── RightKanExtension.scala │ │ │ └── Yoneda.scala │ │ └── two │ │ │ ├── Category.scala │ │ │ ├── bifunctors │ │ │ ├── Biapplicative.scala │ │ │ ├── Bifoldable.scala │ │ │ ├── Bifunctor.scala │ │ │ ├── Bitraverse.scala │ │ │ ├── Clown.scala │ │ │ └── Joker.scala │ │ │ ├── enriched │ │ │ ├── MonoidalVCategory.scala │ │ │ ├── Optic.scala │ │ │ ├── ProfOptic.scala │ │ │ ├── StrongMonoidalVAction.scala │ │ │ ├── Tambara.scala │ │ │ ├── VBifunctor.scala │ │ │ ├── VCategory.scala │ │ │ ├── VFunctor.scala │ │ │ └── VProfunctor.scala │ │ │ └── profunctor │ │ │ ├── Costar.scala │ │ │ ├── Forget.scala │ │ │ ├── Profunctor.scala │ │ │ ├── Star.scala │ │ │ ├── choice │ │ │ └── Choice.scala │ │ │ ├── closed │ │ │ ├── Closed.scala │ │ │ ├── Closure.scala │ │ │ ├── Coclosed.scala │ │ │ └── Environment.scala │ │ │ ├── higher │ │ │ ├── DinaturalTransformation.scala │ │ │ ├── Procompose.scala │ │ │ ├── ProfunctorComonad.scala │ │ │ ├── ProfunctorCoyoneda.scala │ │ │ ├── ProfunctorFunctor.scala │ │ │ ├── ProfunctorMonad.scala │ │ │ ├── ProfunctorRan.scala │ │ │ └── ProfunctorYoneda.scala │ │ │ ├── mapping │ │ │ ├── Settable.scala │ │ │ └── Walk.scala │ │ │ ├── optics │ │ │ ├── Glassed.scala │ │ │ ├── Magnified.scala │ │ │ ├── Polynodal.scala │ │ │ ├── Polyp.scala │ │ │ ├── Telescoped.scala │ │ │ ├── Traversing.scala │ │ │ └── Windowed.scala │ │ │ ├── sieve │ │ │ ├── Cosieve.scala │ │ │ └── Sieve.scala │ │ │ ├── strong │ │ │ ├── CofreeTambara.scala │ │ │ ├── Costrong.scala │ │ │ ├── Cotambara.scala │ │ │ ├── FreeTambara.scala │ │ │ ├── Pastro.scala │ │ │ ├── Strong.scala │ │ │ └── Tambara.scala │ │ │ └── traverse │ │ │ ├── CofreeTraversing.scala │ │ │ ├── FreeTraversing.scala │ │ │ ├── Step.scala │ │ │ └── Traversing.scala │ ├── collections │ │ ├── ConsList.scala │ │ ├── FunCol.scala │ │ ├── HeadNel.scala │ │ ├── RoseTree.scala │ │ ├── Tree.scala │ │ └── ZipList.scala │ ├── data │ │ ├── CoKleisli.scala │ │ ├── CoReader.scala │ │ ├── Cofree.scala │ │ ├── Comparison.scala │ │ ├── Compose.scala │ │ ├── Const.scala │ │ ├── Fix.scala │ │ ├── FreeApp.scala │ │ ├── FreeM.scala │ │ ├── Id.scala │ │ ├── Kleisli.scala │ │ ├── Maybe.scala │ │ ├── Nu.scala │ │ ├── Op.scala │ │ ├── Predicate.scala │ │ ├── RIO.scala │ │ ├── Reader.scala │ │ ├── Show.scala │ │ ├── State.scala │ │ ├── TRIO.scala │ │ ├── These.scala │ │ ├── Thunk.scala │ │ ├── Validated.scala │ │ └── Writer.scala │ ├── optics │ │ ├── Iso.scala │ │ ├── Lens.scala │ │ └── Prism.scala │ └── types │ │ ├── Const.scala │ │ ├── Id.scala │ │ ├── NaturalTransformation.scala │ │ ├── Op.scala │ │ ├── Reader.scala │ │ ├── State.scala │ │ ├── Store.scala │ │ ├── Thunk.scala │ │ └── Void.scala │ ├── examples │ ├── ArrowBasicOps.scala │ ├── ArrowFizBuzz.scala │ ├── BifunctorSimpleImpl.scala │ ├── CoyonedaExample.scala │ ├── F_Algebras.scala │ ├── IntState.scala │ ├── KeyValueStoreFreeMonad.scala │ ├── KleisliExamples.scala │ ├── ReversingYonedaForList.scala │ ├── Tree.scala │ └── YonedaLiftAndLowerAreInverses.scala │ ├── filterable │ └── Filterable.scala │ ├── functorfunctor │ ├── FFunctor.scala │ └── FFunctorExample.scala │ ├── hoare │ └── HoareProbabilityMonad.scala │ ├── profunctor_optics │ ├── ClassicOptics.scala │ ├── EmilyProfOptics.scala │ ├── ProfunctorOpticsSimpleImpl.scala │ └── YonedaExamples.scala │ ├── selective │ └── Selective.scala │ └── typetheory │ ├── LambdaCalc.scala │ ├── Nat.scala │ ├── PropositionalLogic.scala │ ├── RhoCalculus.scala │ ├── SkiCombinators.scala │ └── logic │ ├── B3.scala │ ├── Bool.scala │ └── K3.scala └── test └── scala ├── applicative └── ApplicativeExamplesSpec.scala ├── bifunctor └── BicovariantExamplesSpec.scala ├── comonad └── ComonadCustomImplSpec.scala ├── contravariant ├── ContravariantSpec.scala └── DivideSpec.scala ├── functor ├── CovariantExamplesSpec.scala └── TreeFunctorSpec.scala ├── helper_implementations └── TreeSpec.scala ├── kan └── RanCustomImpl.scala ├── monad ├── CustomMonadImplementationSpec.scala └── ReaderMonadSpec.scala ├── monoid ├── AlternativeMonoidInstancesSpec.scala └── MonoidExamplesSpec.scala ├── mtl └── TraverseEmptyListPermutationsSpec.scala ├── profunctor ├── ClosedProfunctorSpec.scala ├── DivariantExampleSpec.scala ├── ProfunctorSpec.scala └── StrongSpec.scala ├── semigroup └── SemigroupExamplesSpec.scala ├── traverse └── TraverseExamplesSpec.scala └── zivariant └── ZivariantSpec.scala /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: Scala CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up JDK 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: '21' 23 | distribution: 'temurin' 24 | cache: 'sbt' 25 | - name: Run tests 26 | run: sbt test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### Scala template 3 | *.class 4 | *.log 5 | 6 | # sbt specific 7 | .cache 8 | .lib/ 9 | dist/* 10 | target/ 11 | lib_managed/ 12 | src_managed/ 13 | project/boot/ 14 | project/plugins/project/ 15 | 16 | # Scala-IDE specific 17 | .scala_dependencies 18 | .worksheet 19 | 20 | # ENSIME specific 21 | .ensime_cache/ 22 | .ensime 23 | ### JetBrains template 24 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 25 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 26 | 27 | # User-specific stuff: 28 | .idea/ 29 | 30 | ## File-based project format: 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | /out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Crashlytics plugin (for Android Studio and IntelliJ) 45 | com_crashlytics_export_strings.xml 46 | crashlytics.properties 47 | crashlytics-build.properties 48 | fabric.properties 49 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: assign and label scala-steward's PRs 3 | conditions: 4 | - author=scala-steward 5 | actions: 6 | assign: 7 | users: [lemastero] 8 | label: 9 | add: [dependency-update] 10 | - name: merge Scala Steward's PRs 11 | conditions: 12 | - base=main 13 | - author=scala-steward 14 | - status-success=Travis CI - Pull Request 15 | actions: 16 | merge: 17 | method: merge 18 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemastero/scala_typeclassopedia/d609b5b48e17272c83ba2108a9efd2278a82c6d4/.scalafmt.conf -------------------------------------------------------------------------------- /CombinatoryBirds.md: -------------------------------------------------------------------------------- 1 | # Bird combinators 2 | 3 | | bird | syntax | combinator | interpretation | 4 | |-----------------|----------------------------------------------------|----------------|----------------------| 5 | | idiot | a -> a | I combinator | id | 6 | | kestrel | a -> b -> a | K combinator | const | 7 | | bluebird | (b -> c) -> (a -> b) -> a -> c | B combinator | compose | 8 | | cardinal | (a -> b -> c) -> b -> a -> c | C combinator | flip | 9 | | applicator | (a -> b) -> a -> b | A combinator | function application | 10 | | psi | (b -> b -> c) -> (a -> b) -> a -> a -> c | Psi combinator | [on](http://hackage.haskell.org/package/base/docs/Data-Function.html#v:on) | 11 | | becard | (c -> d) -> (b -> c) -> (a -> b) -> a -> d | B3 combinator | | 12 | | blackbird | (c -> d) -> (a -> b -> c) -> a -> b -> d | B1 combinator | | 13 | | bluebird prime | (a -> c -> d) -> a -> (b -> c) -> b -> d | B' combinator | | 14 | | bunting | (d -> e) -> (a -> b -> c -> d) -> a -> b -> c -> e | B2 combinator | | 15 | 16 | * Implementations Haskell [data-aviary](http://hackage.haskell.org/package/data-aviary/docs/Data-Aviary-Birds.html), Purescript [Aviary.Birds](https://pursuit.purescript.org/packages/purescript-birds/docs/Aviary.Birds), JavaScript [fantasy-birds](https://github.com/fantasyland/fantasy-birds), Scala [folone/type-level-birds](https://github.com/folone/type-level-birds) 17 | 18 | * Resources 19 | * [Combinator Birds - Chris Rathman](http://www.angelfire.com/tx4/cus/combinator/birds.html) Reference of Birds expressed as Combinatory Logic (SKI calculus) 20 | * [functions from Functor, Applicative, Monad, Comond as combinators](http://hackage.haskell.org/package/data-aviary/docs/Data-Aviary-Functional.html) 21 | * [Haskell Wiki Combinatory logic](https://wiki.haskell.org/Combinatory_logic) 22 | * The Thrush combinator in Scala - Debasish Ghosh [(blog post)](http://debasishg.blogspot.com/2009/09/thrush-combinator-in-scala.html) 23 | * (Haskell) Combinators in Haskell - geophf [(blog post)](http://logicaltypes.blogspot.com/2008/08/combinators-in-haskell.html) 24 | * (Haskell) Combinatory Birds as Types - geophf [(blog post)](http://logicaltypes.blogspot.com/2008/08/combinatory-birds-as-types.html) -------------------------------------------------------------------------------- /FunctionalDataStructures.md: -------------------------------------------------------------------------------- 1 | # [Functional data structures](./FunctionalDataStructures.md) 2 | * Resources: 3 | * [typelevel/cats-collections](https://github.com/typelevel/cats-collections) 4 | * [What's new in purely functional data structures since Okasaki?](https://cstheory.stackexchange.com/questions/1539/whats-new-in-purely-functional-data-structures-since-okasaki) 5 | * [Funds - Functional Data Structures in Common Lisp](https://common-lisp.net/project/funds/) 6 | 7 | ## Zipper 8 | * Resources: 9 | * learning Scalaz - Zipper [(blog post)](http://eed3si9n.com/learning-scalaz/Zipper.html) 10 | * Zippers by Example - George Wilson: https://www.youtube.com/watch?v=woK7ntZRwXQ 11 | * Zippers, Comonads & Data Structures in Scala - Mark Hibberd & Tony Morris: https://www.youtube.com/watch?v=WpA8VPekcK4 12 | * scalaz [(src)](https://github.com/scalaz/scalaz/blob/series/7.3.x/core/src/main/scala/scalaz/Zipper.scala) 13 | * [Generic Zipper: the context of a traversal - Oleg Kiselyov](http://okmij.org/ftp/continuations/zipper.html) 14 | 15 | ## FingerTree 16 | * Resources: 17 | * Extreme Cleverness: Functional Data Structures in Scala - Daniel Spiewak [(video)](https://www.youtube.com/watch?v=pNhBQJN44YQ) 18 | * scalaz [(src)](https://github.com/scalaz/scalaz/blob/series/7.3.x/core/src/main/scala/scalaz/FingerTree.scala) 19 | 20 | -------------------------------------------------------------------------------- /Topos.md: -------------------------------------------------------------------------------- 1 | ## Topos 2 | 3 | Topos is a category with some properties such that it behaves like sets. 4 | So we can do mathematics in such category. 5 | 6 | Definitions: 7 | * `Elementatry topos` is more general, used in logic 8 | * `Grothendieck topos (sheaf topos)` more specialized for application in geometry - it has natural number object. Adding natural number object to definition of elementary topos gives `W-topos` 9 | 10 | Elementary topos is a category that: 11 | * has finite limits 12 | * is cartesian closed 13 | * has subobject classifier 14 | 15 | Resources: 16 | * vpatryshev/Categories [Topos](https://github.com/vpatryshev/Categories/blob/master/scala2/src/main/scala/math/cat/topos/Topos.scala) [Grothendieck Topos, Subobject Classifier](https://github.com/vpatryshev/Categories/blob/master/scala2/src/main/scala/math/cat/topos/GrothendieckTopos.scala), [Topology](https://github.com/vpatryshev/Categories/blob/master/scala2/src/main/scala/math/cat/topos/Topology.scala), [Lawvere-Tierney topology](https://github.com/vpatryshev/Categories/blob/master/scala2/src/main/scala/math/cat/topos/LawvereTopology.scala) 17 | * (Agda) agda/agda-categories [Topos](https://github.com/agda/agda-categories/blob/master/src/Categories/Category/Topos.agda) [Subobject Classifier](https://github.com/agda/agda-categories/blob/master/src/Categories/Diagram/SubobjectClassifier.agda) 18 | * (Coq) UniMath/UniMath [Grothendieck Topos](https://github.com/UniMath/UniMath/blob/master/UniMath/CategoryTheory/GrothendieckToposes/Toposes.v) [Subobject Classifier](https://github.com/UniMath/UniMath/blob/master/UniMath/CategoryTheory/SubobjectClassifier/SubobjectClassifier.v) 19 | * (Haskell) [brunjlar/protop](https://github.com/brunjlar/protop) 20 | * Category Theory for Computing Science - Michael Barr Charles Wells [(pdf)](http://www.math.mcgill.ca/triples/Barr-Wells-ctcs.pdf) Chapter 15 Toposes 21 | * 7Sketches Chapter 7 Logic of behavior: Sheaves, toposes, and internal languages [(arxiv)](https://arxiv.org/abs/1803.05316), [(video 1)](https://www.youtube.com/watch?v=Cf3tsAeGhBg), [(video 2)](https://www.youtube.com/watch?v=wF-khda2i4c) 22 | * (Category Theory, Haskell) Topoi - Bartosz Milewski [(blog post)](https://bartoszmilewski.com/2017/07/22/topoi/) 23 | * Category Theory: Toposes - MathProofsable [(video playlist)](https://www.youtube.com/watch?v=gKYpvyQPhZo&list=PL4FD0wu2mjWM3ZSxXBj4LRNsNKWZYaT7k) 24 | * Computational Quadrinitarianism (Curious Correspondences go Cubical) - Gershom Bazerman [(blog post)](http://comonad.com/reader/2018/computational-quadrinitarianism-curious-correspondences-go-cubical/) 25 | * [fdilke/bewl](https://github.com/fdilke/bewl) (A DSL for the internal language of a topos) 26 | * [resources about topos theory](./ComputationalTrinitarianism.md#topos-theory) 27 | * nLab [Topos](https://ncatlab.org/nlab/show/topos), [Subobject Classifier](https://ncatlab.org/nlab/show/subobject+classifier), [Lawvere-Tierney topology](https://ncatlab.org/nlab/show/Lawvere-Tierney+topology) -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "scala_typeclassopedia" 2 | 3 | version := "0.0.1" 4 | 5 | lazy val scala212 = "2.12.20" 6 | lazy val scala213 = "2.13.15" 7 | 8 | scalaVersion := scala213 9 | 10 | crossScalaVersions := List(scala212, scala213) 11 | 12 | resolvers += Resolver.sonatypeRepo("snapshots") 13 | 14 | lazy val catsVersion = "2.12.0" 15 | lazy val catsMtlVersion = "1.5.0" 16 | lazy val scalaTestPlusVersion = "3.1.0.0-RC2" 17 | lazy val scalacheckVersion = "1.18.1" 18 | libraryDependencies ++= Seq( 19 | // cats 20 | "org.typelevel" %% "cats-core" % catsVersion, 21 | "org.typelevel" %% "cats-free" % catsVersion, 22 | "org.typelevel" %% "cats-laws" % catsVersion, 23 | "org.typelevel" %% "alleycats-core" % catsVersion, 24 | "org.typelevel" %% "cats-mtl" % catsMtlVersion, 25 | "org.typelevel" %% "cats-mtl-laws" % catsMtlVersion, 26 | "org.typelevel" %% "cats-effect" % "2.5.5", 27 | 28 | "io.monix" %% "monix" % "3.4.1", 29 | 30 | // Scalaz 31 | "org.scalaz" %% "scalaz-core" % "7.3.8", 32 | 33 | // ZIO 34 | "dev.zio" %% "zio" % "2.1.13", 35 | "dev.zio" %% "zio-prelude" % "1.0.0-RC35", 36 | 37 | // algebra 38 | "org.typelevel" %% "algebra" % "2.12.0", 39 | "com.twitter" %% "algebird-core" % "0.13.10", 40 | 41 | // type level 42 | "com.codecommit" %% "skolems" % "0.2.1", 43 | "com.chuusai" %% "shapeless" % "2.3.12", 44 | 45 | // tofu 46 | "tf.tofu" %% "tofu" % "0.13.6", 47 | 48 | // izumi 49 | "io.7mind.izumi" %% "fundamentals-bio" % "1.2.16", 50 | 51 | // HoTT in Scala 52 | "io.github.siddhartha-gadgil" %% "provingground-core-jvm" % "0.1.1", 53 | 54 | // test 55 | "org.scalacheck" %% "scalacheck" % scalacheckVersion % Test, 56 | "org.scalatestplus" %% "scalatestplus-scalacheck" % scalaTestPlusVersion % Test, 57 | "com.github.alexarchambault" %% "scalacheck-shapeless_1.15" % "1.3.0" % Test, 58 | 59 | "org.scalatest" %% "scalatest" % "3.2.19" % Test, 60 | 61 | "org.typelevel" %% "discipline-core" % "1.7.0" % Test, 62 | "org.typelevel" %% "discipline-scalatest" % "2.3.0" % Test 63 | ) 64 | 65 | scalacOptions ++= Seq( 66 | "-encoding", "UTF-8" 67 | ) 68 | 69 | addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.13.3" cross CrossVersion.full) -------------------------------------------------------------------------------- /img/triangle_tuple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
((A,()),B)
((A,()),B)
(A,((),B))
(A,((),B))
(A,B)
(A,B)
-------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.6 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // sbt dependencyBrowseTree 2 | addDependencyTreePlugin 3 | // sbt dependencyUpdates 4 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") 5 | //addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.8") 6 | // sbt scalafmtAll 7 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") 8 | // sbt clean coverage test coverageReport 9 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.2.2") 10 | -------------------------------------------------------------------------------- /src/main/scala/bifunctor/InstancesForForBuildInTypes.scala: -------------------------------------------------------------------------------- 1 | package bifunctor 2 | 3 | import cats.Bifunctor 4 | 5 | /* Translated into Scala examples illustrating BiFunctor from fun, clear explanation by: 6 | George Wilson - The Extended Functor Family: https://www.youtube.com/watch?v=JUVMiRRq6wU 7 | 8 | - BiFunctor allow you to perform two different operations 9 | 10 | Haskell Bifunctor - type class & laws 11 | 12 | class Bifunctor p where 13 | bimap :: (a -> b) -> (x -> y) -> p a x -> p b y 14 | 15 | first :: (a -> b) -> p a x -> p b x 16 | second :: (x -> y) -> p a x -> p a y 17 | 18 | laws: 19 | bimap id id = id 20 | - bimap using two identify function is identify 21 | bimap f h . bimap g i = bimap (f . g)(h . i) 22 | - bimap using two pair of functions is the same as bimap using composition of compositions of those functions 23 | */ 24 | object InstancesForForBuildInTypes { 25 | 26 | val tuple2Bifunctor: Bifunctor[Tuple2] = new Bifunctor[Tuple2] { 27 | override def bimap[A, B, C, D](fab: (A, B))(f: A => C, g: B => D): (C, D) = 28 | (f(fab._1), g(fab._2)) 29 | } 30 | 31 | val eitherBifunctor: Bifunctor[Either] = new Bifunctor[Either] { 32 | override def bimap[A, B, C, D]( 33 | fab: Either[A, B] 34 | )(f: A => C, g: B => D): Either[C, D] = 35 | fab match { 36 | case Left(v) => Left(f(v)) 37 | case Right(v) => Right(g(v)) 38 | } 39 | } 40 | 41 | import cats.data.Validated 42 | val validatedBifunctor: Bifunctor[Validated] = new Bifunctor[Validated] { 43 | override def bimap[A, B, C, D]( 44 | fab: Validated[A, B] 45 | )(f: A => C, g: B => D): Validated[C, D] = fab.map(g).leftMap(f) 46 | } 47 | 48 | // TODO implement property test for laws, if f collapse elements it would change structure of map, combine elements in that case? 49 | /* 50 | val mapBifunctor: Bifunctor[Map] = new Bifunctor[Map] { 51 | override def bimap[A, B, C, D](fab: Map[A, B])(f: A => C, g: B => D): Map[C, D] = { 52 | fab.foldLeft(Map[C, D]()){ (soFar, entry) => soFar + ( f(entry._1) -> g(entry._2)) } 53 | } 54 | } 55 | */ 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/cayley/CayleyTheorem.scala: -------------------------------------------------------------------------------- 1 | package cayley 2 | 3 | import cats.MonoidK 4 | 5 | object CayleyTheorem { 6 | 7 | /* law: abs compose rep == id */ 8 | type DifferenceList[A] = List[A] => List[A] 9 | def rep[A](xs: List[A]): DifferenceList[A] = ys => xs ++ ys 10 | def abs[A](xs: DifferenceList[A]): List[A] = xs(Nil) 11 | 12 | /** 13 | * Laws: 14 | * rep(abs(a)) == a 15 | * abs(rep(a)) == a 16 | */ 17 | trait CayleyTheoremForMonoid[M[_]] extends MonoidK[M] { 18 | type MonoidEndomorphism[A] = M[A] => M[A] 19 | def rep[A](xs: M[A]): MonoidEndomorphism[A] = ys => combineK(xs, ys) 20 | def abs[A](xs: MonoidEndomorphism[A]): M[A] = xs(empty) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/contravariant/HoFStructure.scala: -------------------------------------------------------------------------------- 1 | package contravariant 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.contra.Contravariant 5 | 6 | /** 7 | * Exploration of the structure that forms covariant and contravariant functors. 8 | * --- 9 | * 10 | * Intro 11 | * --- 12 | * Currying produce functions in form: 13 | * (a -> b) 14 | * a -> (b -> c) 15 | * a -> ((b -> c) -> d) 16 | * ... 17 | * 18 | * All types are in negative position (they are input-like) except the last one - on positive position. 19 | * -A +B 20 | * -A -B +C 21 | * -A -B -C +D 22 | * -A -B -C -D +E 23 | * ... 24 | * or as Bartosz Milewski pointed it follows from fact that currying basically change: 25 | * (A B C D) => E 26 | * into 27 | * A => (B => (C => (D => E))) 28 | * etc. 29 | * 30 | * For types on positive position - you could define Functor (if it does not occures on negative position as well) 31 | * 32 | * Exploration 33 | * --- 34 | * In here I explore what would happen if you align braces in different direction: 35 | * (a -> b) 36 | * (a -> b) -> c 37 | * ((a -> b) -> c) -> d 38 | * ... 39 | * 40 | * It looks like variance switch from negative to positive when you add one more bracket. 41 | * -A +B 42 | * +A -B +C 43 | * -A +B -C +D 44 | * +A -B +C -D +E 45 | * ... 46 | * 47 | * If you reverse the types (here LambdaXYZ type aliases): 48 | * +B -A 49 | * +C -B +A 50 | * +D -C +B -A 51 | * +E -D +C -B +A 52 | * ... 53 | * 54 | * Further development 55 | * --- 56 | * 57 | * TODO 58 | * - examples for (consider Reader, thunk, IO, functions) 59 | * - test Functor/Contravariant laws 60 | * - re-use implementation of methods 61 | * - every paif of contravariant and functor form a prounctor/arrow - encode it 62 | * - new ways to compose profunctors? 63 | * - derive automatically Functor, Contravariant instances or methods itself 64 | * 65 | * Theory: 66 | * - relation to SKI combinators 67 | * - already in combinators from: To Mock a Mockingbird Paperback - Raymond Smullyan? 68 | * - formalize in Idris/Agda/Coq 69 | */ 70 | class HoFStructure { 71 | 72 | type LambdaAB[-B, +A] = B => A 73 | implicit def funB[B]: Functor[B => *] = 74 | new Functor[B => *] { 75 | def map[A, AA](fa: B => A)(f: A => AA): B => AA = 76 | fa andThen f 77 | } 78 | implicit def contraA[A]: Contravariant[* => A] = 79 | new Contravariant[* => A] { 80 | def contramap[B, BB](fa: B => A)(f: BB => B): BB => A = 81 | f andThen fa 82 | } 83 | 84 | type LambdaABC[+C, -B, +A] = (C => B) => A 85 | def funBA[B, A]: Functor[LambdaABC[*, B, A]] = 86 | new Functor[LambdaABC[*, B, A]] { 87 | def map[C, CC](fa: (C => B) => A)(f: C => CC): (CC => B) => A = 88 | g => fa(f andThen g) 89 | } 90 | def contraCA[C, A]: Contravariant[LambdaABC[C, *, A]] = 91 | new Contravariant[LambdaABC[C, *, A]] { 92 | def contramap[B, BB]( 93 | fa: LambdaABC[C, B, A] 94 | )(f: BB => B): LambdaABC[C, BB, A] = 95 | g => fa(g andThen f) 96 | } 97 | 98 | type LambdaABCD[-D, +C, -B, +A] = ((D => C) => B) => A 99 | def contraCBA[C, B, A]: Contravariant[LambdaABCD[*, C, B, A]] = 100 | new Contravariant[LambdaABCD[*, C, B, A]] { 101 | def contramap[D, DD]( 102 | fa: LambdaABCD[D, C, B, A] 103 | )(f: DD => D): LambdaABCD[DD, C, B, A] = 104 | h => fa(g => h(f andThen g)) 105 | } 106 | def funDBA[D, B, A]: Functor[LambdaABCD[D, *, B, A]] = 107 | new Functor[LambdaABCD[D, *, B, A]] { 108 | def map[C, CC]( 109 | fa: LambdaABCD[D, C, B, A] 110 | )(f: C => CC): LambdaABCD[D, CC, B, A] = 111 | h => fa(g => h(g andThen f)) 112 | } 113 | // ... 114 | } 115 | -------------------------------------------------------------------------------- /src/main/scala/ct_other_impls/DataCategory.scala: -------------------------------------------------------------------------------- 1 | package ct_other_impls 2 | 3 | object DataCategory { 4 | type Identity[Morphism[_, _], A] = Morphism[A, A] 5 | /* object is represented as identity morphism */ 6 | type TheObject[Morphism[_, _], A] = Identity[Morphism, A] 7 | 8 | trait Category[Morphism[_, _]] { 9 | def source[A, B](f: Morphism[A, B]): TheObject[Morphism, A] 10 | def target[A, B](f: Morphism[A, B]): TheObject[Morphism, B] 11 | def compose[A, B, C](f: Morphism[B, C], g: Morphism[A, B]): Morphism[A, C] 12 | } 13 | 14 | /** Category of types and one argument pure functions between them */ 15 | val TypesAndPureOneArgumentFunctionsCategory: Category[Function1] = 16 | new Category[Function1] { 17 | def source[A, B](f: A => B): TheObject[Function, A] = identity[A] 18 | def target[A, B](f: A => B): TheObject[Function, B] = identity[B] 19 | def compose[A, B, C](f: B => C, g: A => B): A => C = f compose g 20 | } 21 | 22 | /** Opposite category */ 23 | case class Op[K[_, _], A, B](unOp: K[B, A]) 24 | 25 | def oppositeCategory[K[_, _]](implicit 26 | CC: Category[K] 27 | ): Category[Op[K, *, *]] = 28 | new Category[Op[K, *, *]] { 29 | def source[A, B](f: Op[K, A, B]): TheObject[Op[K, *, *], A] = 30 | Op(CC.target(f.unOp)) 31 | def target[A, B](f: Op[K, A, B]): TheObject[Op[K, *, *], B] = 32 | Op(CC.source(f.unOp)) 33 | def compose[A, B, C](f: Op[K, B, C], g: Op[K, A, B]): Op[K, A, C] = 34 | Op(CC.compose(g.unOp, f.unOp)) 35 | } 36 | 37 | /** 1 category */ 38 | object TheOne 39 | type One[A, B] = TheOne.type 40 | 41 | val oneCategory: Category[One] = new Category[One] { 42 | def source[A, B](f: One[A, B]): TheObject[One, A] = TheOne 43 | def target[A, B](f: One[A, B]): TheObject[One, B] = TheOne 44 | def compose[A, B, C](f: One[B, C], g: One[A, B]): One[A, C] = TheOne 45 | } 46 | 47 | /** In Haskell Boolean, Void categories are encoded using GADTs :( */ 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/educational/abstract_algebra/Monoid.scala: -------------------------------------------------------------------------------- 1 | package educational.abstract_algebra 2 | 3 | trait Monoid[M] extends Semigroup[M] { 4 | def empty: M 5 | } 6 | 7 | object MonoidInstances { 8 | def createMonoid[A](mempty: A, mapply: (A, A) => A): Monoid[A] = 9 | new Monoid[A] { 10 | def empty: A = mempty 11 | def combine(x: A, y: A): A = mapply(x, y) 12 | } 13 | 14 | implicit val multiplyIntMonoid: Monoid[Int] = createMonoid(1, _ * _) 15 | 16 | implicit def optionFirstNonEmpty[A]: Monoid[Option[A]] = 17 | createMonoid(None, _ orElse _) 18 | 19 | implicit val maxLongMonoid: Monoid[Long] = 20 | createMonoid(Long.MinValue, Math.max) 21 | 22 | implicit val minIntMonoid: Monoid[Int] = createMonoid(Int.MaxValue, Math.min) 23 | 24 | implicit val andBoolMonoid: Monoid[Boolean] = createMonoid(true, _ && _) 25 | 26 | implicit val orBoolMonoid: Monoid[Boolean] = createMonoid(false, _ || _) 27 | 28 | implicit def endoFunction[A]: Monoid[A => A] = 29 | createMonoid(identity[A], _ andThen _) 30 | 31 | /** 32 | * Create Monoid for tuple from two Monoids 33 | */ 34 | implicit def createMonoidTuple[L, R]( 35 | left: Monoid[L], 36 | right: Monoid[R] 37 | ): Monoid[(L, R)] = 38 | new Monoid[(L, R)] { 39 | def empty: (L, R) = (left.empty, right.empty) 40 | def combine(x: (L, R), y: (L, R)): (L, R) = 41 | (left.combine(x._1, y._1), right.combine(x._2, y._2)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/educational/abstract_algebra/Semigroup.scala: -------------------------------------------------------------------------------- 1 | package educational.abstract_algebra 2 | 3 | trait Semigroup[M] { 4 | def combine(lhs: M, rhs: M): M 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Alternative.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | import cats.data.NonEmptyList 4 | 5 | trait MonoidK[F[_]] { 6 | def empty[A]: F[A] 7 | def combine[A](lhs: F[A], rhs: F[A]): F[A] 8 | } 9 | 10 | object MonoidK { 11 | val listMonoidK: MonoidK[List] = new MonoidK[List] { 12 | override def empty[A]: List[A] = Nil 13 | override def combine[A](lhs: List[A], rhs: List[A]): List[A] = lhs ++ rhs 14 | } 15 | 16 | val vectorMonoidK: MonoidK[Vector] = new MonoidK[Vector] { 17 | override def empty[A]: Vector[A] = Vector.empty 18 | override def combine[A](lhs: Vector[A], rhs: Vector[A]): Vector[A] = lhs ++ rhs 19 | } 20 | 21 | val optionMonoidK: MonoidK[Option] = new MonoidK[Option] { 22 | override def empty[A]: Option[A] = None 23 | 24 | override def combine[A](lhs: Option[A], rhs: Option[A]): Option[A] = (lhs, rhs) match { 25 | case (None, r) => r 26 | case (l, _) => l 27 | } 28 | } 29 | } 30 | 31 | trait Alternative[F[_]] extends MonoidK[F] with Applicative[F] { 32 | def some[A]: F[A] => F[NonEmptyList[A]] = v => map(v)(NonEmptyList.one) 33 | def many[A]: F[A] => F[List[A]] = v => map(v)(List(_)) 34 | } 35 | // 36 | //object Alternative { 37 | // val vectorMonoidK: Alternative[Vector] = new Alternative[Vector] { 38 | // override def empty[A]: Vector[A] = Vector.empty 39 | // override def combine[A](lhs: Vector[A], rhs: Vector[A]): Vector[A] = lhs ++ rhs 40 | // override def pure[A](a: A): Vector[A] = Vector(a) 41 | // override def ap[A, B](ff: Vector[A => B])(fa: Vector[A]): Vector[B] = ??? 42 | // } 43 | // 44 | // val optionMonoidK: Alternative[Option] = new Alternative[Option] { 45 | // override def empty[A]: Option[A] = None 46 | // 47 | // override def combine[A](lhs: Option[A], rhs: Option[A]): Option[A] = (lhs, rhs) match { 48 | // case (None, r) => r 49 | // case (l, _) => l 50 | // } 51 | // } 52 | //} 53 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Applicative.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | trait Applicative[F[_]] extends Apply[F] { 4 | def pure[A](value: A): F[A] 5 | 6 | // derived methods 7 | def liftA2[A, B, Z](abc: (A, B) => Z)(fa: F[A], fb: F[B]): F[Z] = 8 | ap(map(fa)(abc.curried))(fb) 9 | 10 | override def map[A, B](fa: F[A])(f: A => B): F[B] = 11 | ap(pure(f))(fa) 12 | } 13 | 14 | trait ApplicativeLaws[F[_]] extends FunctorLaws[F] with Applicative[F] { 15 | 16 | def apIdentityLaw[A](fa: F[A]): Boolean = { 17 | val l1: F[A => A] = pure(identity[A]) 18 | val l2: F[A] = ap(l1)(fa) 19 | 20 | // ap F[identity] 21 | // F[A] ==================> F[A] 22 | l2 == fa 23 | } 24 | 25 | def homomorphismLaw[A, B](ab: A => B, a: A): Boolean = { 26 | val fab: F[A => B] = pure(ab) 27 | val fa: F[A] = pure(a) 28 | 29 | // F[A => B] 30 | // F[A] =============> F[B] 31 | val l: F[B] = ap(fab)(fa) 32 | 33 | val r: F[B] = pure(ab(a)) 34 | 35 | l == r 36 | } 37 | 38 | def interchangeLaw[A, B](fab: F[A => B], a: A): Boolean = { 39 | // ap F[A => B] 40 | // F[A] ==============> F[B] 41 | val l: F[B] = ap(fab)(pure(a)) 42 | 43 | val fabb: F[(A => B) => B] = pure((f: A => B) => f(a)) 44 | 45 | // ap F[(A => B) => B] 46 | // F[A => B] ========================> F[B] 47 | val r: F[B] = ap(fabb)(fab) 48 | 49 | l == r 50 | } 51 | 52 | def mapLikeDerivedLaw[A, B](f: A => B, fa: F[A]): Boolean = { 53 | val l: F[B] = map(fa)(f) 54 | val r: F[B] = ap(pure(f))(fa) 55 | l == r 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Apply.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | trait Apply[F[_]] extends Functor[F] { 4 | def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] 5 | 6 | def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = { 7 | val abTuple: A => B => (A, B) = 8 | a => b => (a, b) 9 | val fb2ab: F[B => (A, B)] = map(fa)(abTuple) 10 | ap(fb2ab)(fb) 11 | } 12 | 13 | def ap2[A, B, Z](ff: F[(A, B) => Z])(fa: F[A], fb: F[B]): F[Z] = 14 | map(product(fa, product(fb, ff))) { case (a, (b, f)) => f(a, b) } 15 | } 16 | 17 | trait ApplyLaws[F[_]] extends FunctorLaws[F] with Apply[F] { 18 | 19 | def apComposition[A, B, C]( 20 | fa: F[A], 21 | fab: F[A => B], 22 | fbc: F[B => C] 23 | ): Boolean = { 24 | 25 | // ap F[A => B] ap F[B => C] 26 | // F[A] ==================> F[B] =================> F[C] 27 | val fb: F[B] = ap(fab)(fa) 28 | val left: F[C] = ap(fbc)(fb) 29 | 30 | val expand: (B => C) => ((A => B) => (A => C)) = 31 | (bc: B => C) => (ab: A => B) => bc compose ab 32 | 33 | // map( A=>B => B=>C => A=>C ) 34 | // F[B => C] ======================================> F[A=>B => A=>C] 35 | val fabac: F[(A => B) => (A => C)] = map(fbc)(expand) 36 | 37 | // ap F[A=>B => A=>C] 38 | // F[A => B] ==============================> F[A => C] 39 | val fac: F[A => C] = ap(fabac)(fab) 40 | 41 | // ap F[A=>C] 42 | // F[A] =========================> F[C] 43 | val right: F[C] = ap(fac)(fa) 44 | 45 | left == right 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Comonad.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | trait CoflatMap[F[_]] extends Functor[F] { 4 | def duplicate[A](fa: F[A]): F[F[A]] 5 | def extend[A, B](w: F[A])(f: F[A] => B): F[B] = 6 | map(duplicate(w))(f) // coKleisi composition 7 | } 8 | 9 | /* Comonads are dual to Monads 10 | ------------------------------------------ 11 | | Monad | Comonad | 12 | |----------------------------------------| 13 | | put value inside | get out value | 14 | | remove one layer | add one more layer | 15 | ------------------------------------------ 16 | */ 17 | trait Comonad[F[_]] extends CoflatMap[F] { 18 | def extract[A](w: F[A]): A 19 | } 20 | 21 | trait ComonadLaws { 22 | /* Left identity law: fa.duplicate.extract == fa 23 | 24 | duplicate 25 | ------------> 26 | F[A] F[F[A]] 27 | <------------ 28 | extract */ 29 | 30 | /* Right identity law: fa.extend(extract) == fa 31 | 32 | extend(extract) 33 | F[A] ------------------> F[A] */ 34 | 35 | /* Associativity law: fa.duplicate.duplicate == fa.extend(duplicate) 36 | 37 | duplicate 38 | F[A] ------------> F[F[A]] 39 | \ | 40 | \ | duplicate 41 | \ | 42 | extend(duplicate) \ | 43 | \| \/ 44 | F[F[F[A]]] 45 | 46 | */ 47 | } 48 | 49 | object Comonad { 50 | implicit def TupleComonad[T]: Comonad[(T,*)] = new Comonad[(T, *)] { 51 | override def extract[A](fa: (T, A)): A = fa._2 52 | 53 | override def duplicate[A](fa: (T, A)): (T, (T, A)) = (fa._1, fa) 54 | 55 | override def map[A, B](fa: (T, A))(f: A => B): (T, B) = (fa._1, f(fa._2)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Cont.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | /** Continutation monad */ 4 | trait Cont[M[_], A] { 5 | def run[Z](f: A => M[Z]): M[Z] 6 | } 7 | 8 | object ContInstances { 9 | 10 | def contMonad[M[_]]: Monad[Cont[M, *]] = 11 | new Monad[Cont[M, *]] { 12 | def pure[A](a: A): Cont[M, A] = 13 | new Cont[M, A] { 14 | def run[Z](f: A => M[Z]): M[Z] = f(a) 15 | } 16 | 17 | override def map[A, B](x: Cont[M, A])(f: A => B): Cont[M, B] = 18 | new Cont[M, B] { 19 | def run[Z](ff: B => M[Z]): M[Z] = x.run(f andThen ff) 20 | } 21 | 22 | def flatMap[A, B](ma: Cont[M, A])(f: A => Cont[M, B]): Cont[M, B] = 23 | ??? // TODO implement me 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/ContT.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | /** Continutation monad transformer */ 4 | trait ContT[R, M[_], A] { 5 | def runContT(amr: A => M[R]): M[R] 6 | } 7 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Dijkstra.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | import educational.data.State 4 | 5 | trait Dijkstra[S, A] { 6 | def runWp[R](f: (S, A) => R): S => R 7 | } 8 | 9 | object Dijkstra { 10 | 11 | // State[S,A] => Dijkstra[S,A] 12 | def fromState[S, A](h: State[S, A]): Dijkstra[S, A] = 13 | new Dijkstra[S, A] { 14 | def runWp[R](f: (S, A) => R): S => R = 15 | s => { 16 | val (a, s2) = h.runState(s) 17 | f(s2, a) 18 | } 19 | } 20 | 21 | // Dijkstra[S,A] => State[S,A] 22 | def fromDijkstra[S, A](k: Dijkstra[S, A]): State[S, A] = 23 | State(s => k.runWp { case (s2, a) => (a, s2) }(s)) 24 | 25 | // Dijkstra is Monad over second argument 26 | implicit def dijakstraMonad[S]: Monad[Dijkstra[S, *]] = 27 | new Monad[Dijkstra[S, *]] { 28 | def pure[A](a: A): Dijkstra[S, A] = 29 | new Dijkstra[S, A] { 30 | def runWp[R](f: (S, A) => R): S => R = 31 | s => f(s, a) 32 | } 33 | 34 | def flatMap[A, B]( 35 | ma: Dijkstra[S, A] 36 | )(f: A => Dijkstra[S, B]): Dijkstra[S, B] = 37 | new Dijkstra[S, B] { 38 | def runWp[R](g: (S, B) => R): S => R = 39 | ma.runWp { case (s, a) => f(a).runWp(g)(s) } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Foldable.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | trait Foldable[F[_]] { 4 | def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B 5 | } 6 | 7 | object FoldableInstances { 8 | val listFoldable: Foldable[List] = new Foldable[List] { 9 | def foldLeft[A, B](fa: List[A], b: B)(f: (B, A) => B): B = { 10 | fa.foldLeft(b)(f) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Functor.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | import monix.eval.Task 4 | 5 | trait Functor[F[_]] { 6 | def map[A, B](fa: F[A])(f: A => B): F[B] 7 | def lift[A, B](f: A => B): F[A] => F[B] = map(_)(f) 8 | } 9 | 10 | trait FunctorLaws[F[_]] extends Functor[F] { 11 | 12 | def covariantIdentity[A](fa: F[A]): Boolean = { 13 | // identity 14 | // F[A] =================> F[A] 15 | val l: F[A] = map(fa)(identity) 16 | val r: F[A] = fa 17 | l == r 18 | } 19 | 20 | def covariantComposition[A, B, C](fa: F[A], f: A => B, g: B => C): Boolean = { 21 | // f 22 | // F[A] ========> F[B] 23 | val l1: F[B] = map(fa)(f) 24 | // g 25 | // F[B] =========> F[C] 26 | val l2: F[C] = map(l1)(g) 27 | 28 | // f andThen g 29 | // F[A] ==============> F[C] 30 | val r: F[C] = map(fa)(f andThen g) 31 | 32 | l2 == r 33 | } 34 | } 35 | 36 | object FunctorInstances { 37 | val listFunctor1: Functor[List] = 38 | new Functor[List]() { // TODO add custom impl of List and move this guy there 39 | def map[A, B](fa: List[A])(ab: A => B): List[B] = 40 | fa match { 41 | case Nil => Nil 42 | case head :: tail => ab(head) :: map(tail)(ab) 43 | } 44 | } 45 | 46 | val listFunctor: Functor[List] = new Functor[List]() { 47 | def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f) 48 | } 49 | 50 | val monixTaskFunctor: Functor[Task] = new Functor[Task] { 51 | override def map[A, B](fa: Task[A])(f: A => B): Task[B] = fa.map(f) 52 | } 53 | 54 | def tupleRFunctor[X]: Functor[(X, *)] = 55 | new Functor[(X, *)] { 56 | def map[A, B](fa: (X, A))(f: A => B): (X, B) = (fa._1, f(fa._2)) 57 | } 58 | 59 | type TupleLeftInt[X] = (X, Int) 60 | val leftTupleFunctor: Functor[TupleLeftInt] = new Functor[TupleLeftInt] { 61 | def map[A, B](fa: TupleLeftInt[A])(f: A => B): TupleLeftInt[B] = 62 | (f(fa._1), fa._2) 63 | } 64 | 65 | type EitherRightInt[X] = Either[Throwable, X] // map 66 | val eitherRightIntFunctor: Functor[EitherRightInt] = 67 | new Functor[EitherRightInt] { 68 | def map[A, B](fa: EitherRightInt[A])(f: A => B): EitherRightInt[B] = 69 | fa match { 70 | case Left(v) => Left(v) 71 | case Right(v) => Right(f(v)) 72 | } 73 | } 74 | 75 | type EitherLeftInt[X] = Either[X, Int] // leftMap 76 | val eitherLeftIntFunctor: Functor[EitherLeftInt] = 77 | new Functor[EitherLeftInt] { 78 | def map[A, B](fa: EitherLeftInt[A])(f: A => B): EitherLeftInt[B] = 79 | fa match { 80 | case Left(v) => Left(f(v)) 81 | case Right(v) => Right(v) 82 | } 83 | } 84 | 85 | implicit val oneArgFunctionsFromInt: Functor[Int => *] = 86 | new Functor[Int => *] { 87 | def map[A, B](g: Int => A)(h: A => B): Int => B = 88 | g andThen h 89 | } 90 | 91 | implicit def oneArgFunctionsFromX[Input]: Functor[Input => *] = 92 | new Functor[Input => *] { 93 | def map[A, B](fun: Input => A)(g: A => B): Input => B = 94 | fun andThen g 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Invariant.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | trait Invariant[F[_]] { 4 | def imap[A, B](fa: F[A])(f: A => B, g: B => A): F[B] 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Monad.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | /** 4 | * Simple implementation of Monads shown in: 5 | * Rúnar Bjarnason 6 | * A Scala Comonad Tutorial, Part 1 7 | * http://blog.higher-order.com/blog/2015/06/23/a-scala-comonad-tutorial/ 8 | */ 9 | 10 | /* laws: 11 | 12 | 1) flatten associativity -- TODO how 13 | 14 | 2) pure must be identity for flatten 15 | 16 | pure 17 | A -------------> M[A] 18 | | | 19 | pure | | pure 20 | | | 21 | \/ \/ 22 | M[A] <-------- M[M[A]] 23 | flatten 24 | 25 | Monad[M].flatten( Monad[M].pure( Monad[M].pure(a) ) ) == Monad[M].pure(a) 26 | 27 | 1. flatmap associativity: `fa.flatMap(f).flatMap(g) == fa.flatMap(a => f(a).flatMap(b => g(b))` 28 | 2. left identity: `pure(a).flatMap(f) == f(a)` 29 | 3. right identity: `fa.flatMap(a => pure(a)) == fa` 30 | */ 31 | 32 | trait Monad[F[_]] extends Applicative[F] { 33 | def flatMap[A, B](ma: F[A])(f: A => F[B]): F[B] // flatten(map(ma)(f)) 34 | 35 | def flatten[A](mma: F[F[A]]): F[A] = flatMap(mma)(identity) 36 | override def map[A, B](x: F[A])(f: A => B): F[B] = flatMap(x)(a => pure(f(a))) 37 | 38 | def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = 39 | flatMap(ff)(f => map(fa)(f)) 40 | 41 | override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = 42 | flatMap(fa)(a => map(fb)(b => (a, b))) 43 | 44 | override def ap2[A, B, Z](ff: F[(A, B) => Z])(fa: F[A], fb: F[B]): F[Z] = 45 | flatMap(fa)(a => flatMap(fb)(b => map(ff)(_(a, b)))) 46 | } 47 | 48 | object MonadInstance { 49 | val optionMonad: Monad[Option] = new Monad[Option] { 50 | 51 | def pure[A](a: A): Option[A] = Some(a) 52 | 53 | def flatMap[A, B](ma: Option[A])(f: A => Option[B]): Option[B] = { 54 | ma match { 55 | case Some(a) => f(a) 56 | case None => None 57 | } 58 | } 59 | } 60 | } 61 | 62 | trait MonadLaws[M[_]] extends Monad[M] { 63 | 64 | // fa.flatMap(f).flatMap(g) == fa.flatMap(a => f(a).flatMap(b => g(b)) 65 | def flatMapAssociativity[A, B, C]( 66 | fa: M[A], 67 | f: A => M[B], 68 | g: B => M[C] 69 | ): Boolean = { 70 | // flatMap(f) 71 | // M[A] =============> M[B] 72 | val l1: M[B] = flatMap(fa)(f) 73 | // flatMap(g) 74 | // M[B] =============> M[C] 75 | val l2: M[C] = flatMap(l1)(g) 76 | 77 | val r1: A => M[C] = a => flatMap(f(a))(b => g(b)) 78 | // flatMap(f flatMap g) 79 | // M[A] ======================> M[C] 80 | val r2: M[C] = flatMap(fa)(r1) 81 | l2 == r2 82 | } 83 | 84 | // pure(a).flatMap(f) == f(a) 85 | def leftIdentity[A, B, C](a: A, f: A => M[B]): Boolean = { 86 | // pure 87 | // A =======> M[A] 88 | val l1: M[A] = pure(a) 89 | // flatMap 90 | // M[A] ==========> M[B] 91 | val l2: M[B] = flatMap(l1)(f) 92 | 93 | // A =======> M[B] 94 | val r: M[B] = f(a) 95 | l2 == r 96 | } 97 | 98 | // fa.flatMap(pure) == fa 99 | def rightIdentity[A, B, C](fa: M[A]): Boolean = { 100 | // flatMap 101 | // M[A] ==========> M[A] 102 | val l: M[A] = flatMap(fa)(pure) 103 | 104 | val r: M[A] = fa 105 | l == r 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/StateMonad.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | import Function.const 4 | 5 | trait StateMonad[F[_],S] extends Monad[F] { 6 | def update: (S => S) => F[S] 7 | def set: S => F[S] = s => update( const(s) ) 8 | def fetch: F[S] = update( identity[S] ) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/Traverse.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory 2 | 3 | import educational.types.Id.{Id, travId} 4 | 5 | trait Traverse[F[_]] extends Functor[F] with Foldable[F] { 6 | def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] 7 | 8 | // derived 9 | def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] = 10 | traverse(fga)(ga => ga) 11 | 12 | override def map[A, B](fa: F[A])(f: A => B): F[B] = 13 | traverse[Id, A, B](fa)(f)(travId) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/adjunct_rep/Adjunction.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.adjunct_rep 2 | 3 | // http://blog.higher-order.com/blog/2015/10/04/scala-comonad-tutorial-part-2/ 4 | 5 | trait Adjunction[F[_], G[_]] { 6 | def left[A, B](f: F[A] => B): A => G[B] 7 | def right[A, B](f: A => G[B]): F[A] => B 8 | } 9 | 10 | class Examples { 11 | 12 | def tupleFunctionFromAdjunction[R] 13 | : Adjunction[Tuple2[*, R], Function1[R, *]] = 14 | new Adjunction[(*, R), R => *] { 15 | def left[A, B](f: ((A, R)) => B): A => R => B = 16 | Function.untupled(f).curried 17 | def right[A, B](f: A => R => B): ((A, R)) => B = 18 | Function.uncurried(f).tupled 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/contra/Contravariant.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.contra 2 | 3 | /* 4 | class Contravariant f where 5 | contramap :: (b -> a) -> f a -> f b 6 | */ 7 | trait Contravariant[F[_]] { 8 | def contramap[A, B](fa: F[A])(f: B => A): F[B] 9 | 10 | def contraFanIn[A,B,C](fa: F[A])(ba: B => A, ca: C => A): F[Either[B,C]] = 11 | contramap(fa){ 12 | case Left(b) => ba(b) 13 | case Right(c) => ca(c) 14 | } 15 | } 16 | 17 | trait ContravariantLaws[F[_]] extends Contravariant[F] { 18 | 19 | // contramap id = id 20 | def contramapIdentity[A](fa: F[A]): Boolean = { 21 | // contramap(id) 22 | // F[A] ================> F[A] 23 | contramap(fa)(identity[A]) == fa 24 | } 25 | 26 | // contramap f . contramap g = contramap (g . f) 27 | def contravariantComposite[A, B, C]( 28 | fa: F[A], 29 | f: B => A, 30 | g: C => B 31 | ): Boolean = { 32 | 33 | // contramap f 34 | // F[A] ==============> F[B] 35 | val fb: F[B] = contramap(fa)(f) 36 | 37 | // contramap g 38 | // F[B] ===============> F[C] 39 | val l: F[C] = contramap(fb)(g) 40 | 41 | // contramap (g . f) 42 | // F[A] =====================> F[B] 43 | val r: F[C] = contramap(fa)(f compose g) 44 | 45 | l == r 46 | } 47 | } 48 | 49 | object Contravariant { 50 | 51 | def contraProduct[F[_],A,B](fe: F[Either[A,B]])(implicit CF: Contravariant[F]): (F[A],F[B]) = { 52 | val fa: F[A] = CF.contramap(fe)(Left.apply) 53 | val fb: F[B] = CF.contramap(fe)(Right.apply) 54 | (fa,fb) 55 | } 56 | 57 | def contraFanout[F[_],A,B,C](fe: F[(B,C)])(ab: A => B, ac: A => C)(implicit CF: Contravariant[F]): F[A] = { 58 | CF.contramap(fe){ a => 59 | val b: B = ab(a) 60 | val c: C = ac(a) 61 | (b, c) 62 | } 63 | } 64 | } 65 | 66 | object ContravariantInstances { 67 | 68 | def fun1Contravariant[R]: Contravariant[Function1[*, R]] = 69 | new Contravariant[* => R] { 70 | def contramap[A, AA](fa: A => R)(f: AA => A): AA => R = f andThen fa 71 | } 72 | 73 | val EquivContravariant: Contravariant[Equiv] = new Contravariant[Equiv] { 74 | def contramap[A, B](fa: Equiv[A])(f: B => A): Equiv[B] = 75 | (x: B, y: B) => fa.equiv(f(x), f(y)) 76 | } 77 | 78 | val OrderingContravariant: Contravariant[Ordering] = 79 | new Contravariant[Ordering] { 80 | def contramap[A, B](fa: Ordering[A])(f: B => A): Ordering[B] = 81 | (x: B, y: B) => fa.compare(f(x), f(y)) 82 | } 83 | 84 | val PartialOrderingContravariant: Contravariant[PartialOrdering] = 85 | new Contravariant[PartialOrdering] { 86 | override def contramap[A, B]( 87 | fa: PartialOrdering[A] 88 | )(f: B => A): PartialOrdering[B] = 89 | new PartialOrdering[B] { 90 | def tryCompare(x: B, y: B): Option[Int] = fa.tryCompare(f(x), f(y)) 91 | def lteq(x: B, y: B): Boolean = fa.lteq(f(x), f(y)) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/contra/ContravariantCoyoneda.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.contra 2 | 3 | // data Coyoneda f a where 4 | // Coyoneda :: (a -> b) -> f b -> Coyoneda f a 5 | trait ContravariantCoyoneda[F[_], A] { 6 | type B 7 | val fb: F[B] 8 | val m: A => B 9 | 10 | // lowerCoyoneda :: Contravariant f => Coyoneda f a -> f a 11 | // lowerCoyoneda (Coyoneda f m) = contramap f m 12 | def lowerCoyoneda(implicit CF: Contravariant[F]): F[A] = CF.contramap(fb)(m) 13 | } 14 | 15 | object ContravariantCoyoneda { 16 | // liftCoyoneda :: f a -> Coyoneda f a 17 | // liftCoyoneda = Coyoneda id 18 | def liftCoyoneda[F[_], AA](fa: F[AA]): ContravariantCoyoneda[F, AA] = 19 | new ContravariantCoyoneda[F, AA] { 20 | type B = AA 21 | val fb: F[B] = fa 22 | val m: AA => B = identity[AA] 23 | } 24 | 25 | // instance Contravariant (Coyoneda f) where 26 | // contramap f (Coyoneda g m) = Coyoneda (g.f) m 27 | def cotraContraCoyo[F[_]]: Contravariant[ContravariantCoyoneda[F, *]] = 28 | new Contravariant[ContravariantCoyoneda[F, *]] { 29 | def contramap[AA, BB]( 30 | fa: ContravariantCoyoneda[F, AA] 31 | )(f: BB => AA): ContravariantCoyoneda[F, BB] = 32 | new ContravariantCoyoneda[F, BB] { 33 | type B = fa.B 34 | val fb: F[B] = fa.fb 35 | val m: BB => B = fa.m compose f 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/contra/Decidable.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.contra 2 | 3 | import educational.types.Void 4 | 5 | /* 6 | Decidable is contravariant for Alternative: 7 | https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html#t:Decidable 8 | https://www.reddit.com/r/haskell/comments/38o0f7/a_mixture_of_applicative_and_divisible/ 9 | */ 10 | trait Decidable[F[_]] extends Divisible[F] with Decide[F] { 11 | def loose[A](f: A => Void): F[A] 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/contra/Decide.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.contra 2 | 3 | trait Decide[F[_]] extends Divisible[F] { // or Divisible 4 | def choose[A, B, C](f: A => Either[B, C], fb: F[B], fc: F[C]): F[A] 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/contra/Divide.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.contra 2 | 3 | import educational.abstract_algebra.Monoid 4 | 5 | trait Divide[F[_]] extends Contravariant[F] { 6 | def divide[A, B, C](f: A => (B, C), fb: F[B], fc: F[C]): F[A] // contramap2 7 | } 8 | 9 | trait DivideLaws[F[_]] extends ContravariantLaws[F] with Divide[F] { 10 | 11 | def delta[A]: A => (A, A) = a => (a, a) 12 | 13 | // divide delta (divide delta m n) o = divide delta m (divide delta n o) 14 | def divideComposition[A](fa1: F[A], fa2: F[A], fa3: F[A]): Boolean = { 15 | // divide(delta) 16 | // F[A1], F[A2] ===============> F[A12] 17 | val fa12: F[A] = divide(delta[A], fa1, fa2) 18 | 19 | // divide(delta) 20 | // F[A12], F[A3] =================> F[A123] 21 | val l: F[A] = divide(delta[A], fa12, fa3) 22 | 23 | // divide(delta) 24 | // F[A2], F[A3] ===============> F[A23] 25 | val fa23: F[A] = divide(delta[A], fa2, fa3) 26 | 27 | // divide(delta) 28 | // F[A1], F[A23] ===============> F[A123] 29 | val r: F[A] = divide(delta[A], fa1, fa23) 30 | l == r 31 | } 32 | 33 | // TODO more general laws: http://hackage.haskell.org/package/contravariant-1.5/docs/Data-Functor-Contravariant-Divisible.html#g:4 34 | } 35 | 36 | object Divide { 37 | def fun1Divide[R](implicit MR: Monoid[R]): Divide[Function1[*, R]] = 38 | new Divide[Function1[*, R]] { 39 | def divide[A, B, C](f: A => (B, C), fb: B => R, fc: C => R): A => R = 40 | a => { 41 | val (b, c) = f(a) 42 | MR.combine(fb(b), fc(c)) 43 | } 44 | 45 | def contramap[A, B](fa: Function[A, R])(f: B => A): Function[B, R] = 46 | f andThen fa 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/contra/Divisible.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.contra 2 | 3 | import educational.abstract_algebra.Monoid 4 | 5 | /* 6 | George Wilson - The Extended Functor Family: https://www.youtube.com/watch?v=JUVMiRRq6wU 7 | 8 | Divisible is contravariant for Applicative: 9 | https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html 10 | https://stackoverflow.com/questions/32059754/are-there-useful-applications-for-the-divisible-type-class 11 | */ 12 | trait Divisible[F[_]] extends Divide[F] { 13 | def conquer[A]: F[A] // unit 14 | } 15 | 16 | trait DivisibleLaws[F[_]] extends DivideLaws[F] with Divisible[F] { 17 | 18 | // divide delta m conquer = m 19 | def rightIdentity[A](fa: F[A]): Boolean = { 20 | // divide(delta) 21 | // F[A], conquer ===============> F[A] 22 | val l: F[A] = divide(delta, fa, conquer[A]) 23 | 24 | l == fa 25 | } 26 | 27 | // divide delta conquer m = m 28 | def leftIdentity[A](fa: F[A]): Boolean = { 29 | // divide(delta) 30 | // conquer, F[A] ===============> F[A] 31 | val l: F[A] = divide(delta, conquer[A], fa) 32 | 33 | l == fa 34 | } 35 | 36 | // TODO more eneral laws: http://hackage.haskell.org/package/contravariant-1.5/docs/Data-Functor-Contravariant-Divisible.html#g:4 37 | } 38 | 39 | object Divisible { 40 | 41 | def fun1Divisible[R](implicit MR: Monoid[R]): Divisible[Function1[*, R]] = 42 | new Divisible[Function1[*, R]] { 43 | def divide[A, B, C](f: A => (B, C), fb: B => R, fc: C => R): A => R = 44 | a => { 45 | val (b, c) = f(a) 46 | MR.combine(fb(b), fc(c)) 47 | } 48 | def contramap[A, B](fa: A => R)(f: B => A): B => R = f andThen fa 49 | def conquer[A]: A => R = _ => MR.empty 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/higher/CategoryK.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.higher 2 | 3 | import educational.category_theory.Functor 4 | 5 | // objects are type constructors 6 | // morpshisms are natural transformations 7 | trait CategoryK[Morphism[_[_], _[_]]] { 8 | def id[A[_]]: Morphism[A, A] 9 | def compose[A[_], B[_], C[_]](f: Morphism[B, C])( 10 | g: Morphism[A, B] 11 | ): Morphism[A, C] 12 | } 13 | 14 | object CategoryKInstances { 15 | val functorCategory: CategoryK[~>] = new CategoryK[~>] { 16 | def id[F[_]]: F ~> F = new IdNat[F] 17 | def compose[A[_], B[_], C[_]](f: B ~> C)(g: A ~> B): A ~> C = 18 | VerticalComposition(g, f) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/higher/NaturalTransformation.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.higher 2 | 3 | import educational.category_theory.Functor 4 | 5 | trait ~>[F[_], G[_]] { 6 | def apply[A](fa: F[A]): G[A] 7 | } 8 | 9 | trait NaturalTransformationLaws[F[_], G[_]] extends ~>[F, G] { 10 | 11 | def naturalitySquare[A, B](fa: F[A], ff: ~>[F, G], g: A => B)(implicit 12 | FF: Functor[F], 13 | FG: Functor[G] 14 | ): Boolean = { 15 | val v1: G[A] = ff(fa) 16 | val v2: G[B] = FG.map(v1)(g) 17 | 18 | val w1: F[B] = FF.map(fa)(g) 19 | val w2: G[B] = ff(w1) 20 | 21 | v2 == w2 22 | } 23 | } 24 | 25 | case class VerticalComposition[F[_], G[_], H[_]](f: F ~> G, g: G ~> H) 26 | extends ~>[F, H] { 27 | def apply[A](fa: F[A]): H[A] = g(f(fa)) 28 | } 29 | 30 | // https://github.com/unktomi/stuff/blob/master/containers/AbstractNonsense.scala#L85-L125 31 | case class HorizontalComposition[F[_], G[_], H[_], I[_]]( 32 | f: Functor[F], 33 | h: Functor[H] 34 | ) { 35 | type FG[X] = F[G[X]] 36 | type HI[X] = H[I[X]] 37 | 38 | def innerThenOuter(n1: F ~> H, n2: G ~> I): FG ~> HI = { 39 | new ~>[FG, HI] { 40 | override def apply[X](fg: F[G[X]]): H[I[X]] = { 41 | val gi: G[X] => I[X] = gx => n2(gx) 42 | val fi: F[I[X]] = f.map(fg)(gi) 43 | n1(fi) 44 | } 45 | } 46 | } 47 | 48 | def outerThenInner(n1: F ~> H, n2: G ~> I): FG ~> HI = { 49 | new ~>[FG, HI] { 50 | def apply[X](fg: F[G[X]]): H[I[X]] = { 51 | val gi: G[X] => I[X] = gx => n2(gx) 52 | val hg: H[G[X]] = n1(fg) 53 | h.map(hg)(gi) 54 | } 55 | } 56 | } 57 | } 58 | 59 | case class IdNat[F[_]]() extends ~>[F, F] { 60 | def apply[A](fa: F[A]): F[A] = fa 61 | } 62 | 63 | object NaturalTransformationExamples { 64 | val headOption: List ~> Option = new ~>[List, Option] { 65 | def apply[A](fa: List[A]): Option[A] = fa.headOption 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/higher/monoidal/AlgebraOfADTs.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.higher.monoidal 2 | 3 | object AlgebraOfADTs { 4 | 5 | // Algebra of ADT's - Tuple and Unit 6 | 7 | val isAssociative: Boolean = (("a", "b"), "c") != ("a", ("b", "c")) 8 | 9 | def assoc1[A, B, C]: ((A, B), C) => (A, (B, C)) = { 10 | case ((a, b), c) => (a, (b, c)) 11 | } 12 | 13 | def assoc2[A, B, C]: (A, (B, C)) => ((A, B), C) = { 14 | case (a, (b, c)) => ((a, b), c) 15 | } 16 | 17 | val isAssociativeUpToIsomorphism: Boolean = 18 | (("a", "b"), "c") == assoc2("a", ("b", "c")) 19 | 20 | def leftId[A](x: (A, Unit)): A = x._1 21 | def leftId_inv[A](a: A): (A, Unit) = (a, ()) 22 | 23 | def rihtId[A](x: (Unit, A)): A = x._2 24 | def rihtId_inv[A](a: A): (Unit, A) = ((), a) 25 | 26 | val isLeftIdentity = leftId_inv(42) == (42, ()) 27 | val isRightIdentity = rihtId_inv(42) == ((), 42) 28 | 29 | def swap[A, B]: (A, B) => (B, A) = { case (a, b) => (b, a) } 30 | 31 | // Algebra of ADT's - Either and Void 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/higher/monoidal/MonoidalObject.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.higher.monoidal 2 | 3 | trait MonoidObject[F[_, _], M, I] { 4 | def eta: I => M 5 | def mu: F[M, M] => M 6 | 7 | // TODO laws 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/kan/Codensity.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.kan 2 | 3 | import educational.category_theory.Monad 4 | 5 | trait Codensity[F[_], A] { 6 | def run[B](f: A => F[B]): F[B] 7 | } 8 | 9 | object CodensityInstances { 10 | 11 | /* Create monad without requiring anything (even F to be Functor!) */ 12 | implicit def codensityMonad[G[_]]: Monad[Codensity[G, *]] = 13 | new Monad[Codensity[G, *]] { 14 | override def map[A, B](fa: Codensity[G, A])(f: A => B): Codensity[G, B] = 15 | new Codensity[G, B] { 16 | def run[C](f2: B => G[C]): G[C] = fa.run(f andThen f2) 17 | } 18 | 19 | def pure[A](a: A): Codensity[G, A] = 20 | new Codensity[G, A] { 21 | def run[B](f: A => G[B]): G[B] = f(a) 22 | } 23 | 24 | def flatMap[A, B]( 25 | c: Codensity[G, A] 26 | )(f: A => Codensity[G, B]): Codensity[G, B] = 27 | new Codensity[G, B] { 28 | def run[C](f2: B => G[C]): G[C] = c.run(a => f(a).run(f2)) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/kan/Coyoneda.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.kan 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.higher.~> 5 | import educational.category_theory.kan.LeftKanExtension.Lan 6 | import educational.types.Id.Id 7 | 8 | /* 9 | Functor with some a's in it is isomorphic to some other type 10 | that has function b -> a and some functor with b's inside 11 | 12 | f a ~ forall b. ((b -> a), f b) 13 | */ 14 | trait Coyoneda[F[_], A] { 15 | type Z 16 | def fb: F[Z] 17 | def f: Z => A 18 | 19 | /* If we have proof that type constructor F is a Functor then we can create F[A]. 20 | So we lower Coyoneda to Functor F. */ 21 | def lowerCoyoneda(implicit fun: Functor[F]): F[A] = fun.map(fb)(f) 22 | 23 | /* Coyoneda is the Left Kan extension of F along the Identity functor */ 24 | // coyonedaToLan :: Coyoneda f a -> Lan Identity f a 25 | def coyonedaToLan(): Lan[Id, F, A] = Lan[Id, F, A, Z](fb, a => f(a)) 26 | } 27 | 28 | object Coyoneda { 29 | 30 | def apply[F[_], AA, BB](ff: BB => AA, ffb: F[BB]): Coyoneda[F, AA] = 31 | new Coyoneda[F, AA] { 32 | type Z = BB 33 | def fb: F[Z] = ffb 34 | def f: Z => AA = ff 35 | } 36 | 37 | /* lift any type constructor F to Coyoneda F A */ 38 | def liftCoyoneda[F[_], A](fa: F[A]): Coyoneda[F, A] = 39 | Coyoneda[F, A, A](identity[A], fa) 40 | 41 | /*Instance of Functor for Coyoneda F */ 42 | implicit def coyoFunctor[F[_]]: Functor[Coyoneda[F, *]] = 43 | new Functor[Coyoneda[F, *]] { 44 | def map[A, AA](fa: Coyoneda[F, A])(ff: A => AA): Coyoneda[F, AA] = 45 | new Coyoneda[F, AA] { 46 | type Z = fa.Z 47 | def f: Z => AA = fa.f andThen ff 48 | def fb: F[Z] = fa.fb 49 | } 50 | } 51 | 52 | /* lift natural transformation to natural transformation on Coyoneda */ 53 | def hoistCoyoneda[F[_], G[_], A, C]( 54 | fab: F ~> G 55 | )(coyo: Coyoneda[F, A]): Coyoneda[G, A] = 56 | new Coyoneda[G, A] { 57 | type Z = coyo.Z 58 | def f: Z => A = coyo.f 59 | def fb: G[Z] = fab(coyo.fb) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/kan/DensitySimpleImpl.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.kan 2 | 3 | import educational.category_theory.kan.LeftKanExtension.Lan 4 | import educational.category_theory.{Comonad, Functor} 5 | 6 | object DensitySimpleImpl { 7 | 8 | /** 9 | * Density Comonad for a Functor. 10 | * 11 | * It is left Kan extension of a Functor along itself (Lan f f). 12 | */ 13 | trait Density[F[_], A] { self => 14 | type Z 15 | val fb: F[Z] 16 | def f: F[Z] => A 17 | 18 | // Derived methods 19 | def map[B](fab: A => B): Density[F, B] = 20 | Density[F, B, Z](f andThen fab, fb) 21 | 22 | def densityToLan: Lan[F, F, A] = Lan[F, F, A, Z](fb, self.f) 23 | 24 | def densityToCoyoneda: Coyoneda[F, Z] = 25 | Coyoneda[F, Z, Z](identity[Z], fb) // TODO is it lawfull Coyoneda ? 26 | } 27 | 28 | object Density { 29 | def apply[F[_], A, B](kba: F[B] => A, kb: F[B]): Density[F, A] = 30 | new Density[F, A] { 31 | type Z = B 32 | val fb: F[Z] = kb 33 | def f: F[Z] => A = kba 34 | } 35 | 36 | /** Density is a Functor for free */ 37 | def functorInstance[K[_]]: Functor[Density[K, *]] = 38 | new Functor[Density[K, *]] { 39 | def map[A, B](x: Density[K, A])(fab: A => B): Density[K, B] = 40 | Density[K, B, x.Z](x.f andThen fab, x.fb) 41 | } 42 | 43 | /** Density is a Comonad for Free */ 44 | def comonadInstance[K[_]]: Comonad[Density[K, *]] = 45 | new Comonad[Density[K, *]] { 46 | def extract[A](w: Density[K, A]): A = w.f(w.fb) 47 | override def duplicate[A](wa: Density[K, A]): Density[K, Density[K, A]] = 48 | Density[K, Density[K, A], wa.Z]( 49 | kx => Density[K, A, wa.Z](wa.f, kx), 50 | wa.fb 51 | ) 52 | def map[A, B](x: Density[K, A])(f: A => B): Density[K, B] = x.map(f) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/kan/LeftKanExtension.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.kan 2 | 3 | import educational.category_theory.Functor 4 | 5 | object LeftKanExtension { 6 | 7 | trait Lan[G[_], H[_], A] { 8 | type I 9 | val fb: H[I] 10 | def f: G[I] => A 11 | } 12 | 13 | object Lan extends LanInstances { 14 | def apply[G[_], H[_], A, B](hhb: H[B], ff: G[B] => A): Lan[G, H, A] = 15 | new Lan[G, H, A] { 16 | type I = B 17 | val fb: H[I] = hhb 18 | def f: G[I] => A = ff 19 | } 20 | } 21 | 22 | sealed abstract class LanInstances { 23 | implicit def lanFunctor[F[_], H[_]]: Functor[Lan[F, H, *]] = 24 | new Functor[Lan[F, H, *]]() { 25 | def map[A, X](x: Lan[F, H, A])(fax: A => X): Lan[F, H, X] = { 26 | new Lan[F, H, X] { 27 | type I = x.I 28 | val fb: H[I] = x.fb 29 | def f: F[I] => X = x.f andThen fax 30 | } 31 | } 32 | } 33 | } 34 | 35 | trait LanFunctor[G[_], H[_]] extends Functor[Lan[G, H, *]] { 36 | def map[A, X](x: Lan[G, H, A])(fax: A => X): Lan[G, H, X] = { 37 | new Lan[G, H, X] { 38 | type I = x.I 39 | val fb: H[I] = x.fb 40 | def f: G[I] => X = x.f andThen fax 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/kan/RightKanExtension.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.kan 2 | 3 | import educational.category_theory.Functor 4 | 5 | /** 6 | * Right Kan Extension (Ran G H) of h along g 7 | * 8 | * Right Kan Extension in Haskell 9 | * 10 | * newtype Ran g h a = Ran 11 | * { runRan :: forall b. (a -> g b) -> h b } 12 | * 13 | * Right Kan Extension in Category Theory: 14 | * * We have categories: A, B, C and functors: X : A -> C, F : A -> C 15 | * Right Kan Extension of X along F is: 16 | * - a functor R : B -> C 17 | * - natural transformation (eta) n: RF -> X 18 | * 19 | * X 20 | * A ------> C 21 | * | /\ 22 | * F | / 23 | * | / R 24 | * \/ / 25 | * B 26 | * 27 | * such that for every other candidate: 28 | * - functor M : B -> C 29 | * - natural transformation u : MF -> X 30 | * then there is unique natural transformation d: M -> R such that following diagram commute: 31 | * 32 | * RF 33 | * n / /\ 34 | * / \ dF 35 | * \/ \ 36 | * X ----------> MF 37 | * u 38 | * 39 | * where for all a from A: dF(a) = d(Fa): MF(a) -> RF(a) 40 | */ 41 | trait Ran[G[_], H[_], A] { 42 | def runRan[B](f: A => G[B]): H[B] 43 | } 44 | 45 | object RanInstances { 46 | 47 | /* Functor of Ran does not require G, H to be functors, so it is Bifunctor for free ? */ 48 | 49 | def ranFunctor[G[_], H[_]]: Functor[Ran[G, H, *]] = 50 | new Functor[Ran[G, H, *]] { 51 | 52 | def map[A, B](fa: Ran[G, H, A])(f: A => B): Ran[G, H, B] = 53 | new Ran[G, H, B] { 54 | def runRan[C](f2: B => G[C]): H[C] = 55 | fa.runRan(f andThen f2) 56 | } 57 | } 58 | 59 | // Codensity monad - monad generated by Ran 60 | def codensityMonad[F[_], A](ran: Ran[F, F, A]): Codensity[F, A] = 61 | new Codensity[F, A] { 62 | def run[B](f: A => F[B]): F[B] = ran.runRan(f) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/kan/Yoneda.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.kan 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.higher.~> 5 | import educational.types.Id.Id 6 | 7 | object Yoneda { 8 | 9 | /** Yoneda lemma 10 | * 11 | * http://hackage.haskell.org/package/kan-extensions/docs/Data-Functor-Yoneda.html 12 | * newtype Yoneda f a = Yoneda (forall r. (a -> r) -> f r) 13 | */ 14 | trait Yoneda[F[_], A] { self => 15 | def run[R](f: A => R): F[R] 16 | 17 | // derived method 18 | def lowerYoneda(): F[A] = run(identity[A]) 19 | 20 | /* Yoneda[F] is a right Kan extension of F along the Identity Functor */ 21 | def yonedaToRan: Ran[Id, F, A] = 22 | new Ran[Id, F, A] { 23 | def runRan[B](f: A => Id[B]): F[B] = run(a => f(a)) 24 | } 25 | 26 | def hoistYoneda[G[_]](fg: F ~> G): Yoneda[G, A] = 27 | new Yoneda[G, A] { 28 | def run[R](f: A => R): G[R] = fg(self.run(f)) 29 | } 30 | } 31 | 32 | object Yoneda { 33 | 34 | def liftYoneda[F[_], A]( 35 | fa: F[A] 36 | )(implicit FunctorF: Functor[F]): Yoneda[F, A] = 37 | new Yoneda[F, A] { 38 | def run[R2](f: A => R2): F[R2] = FunctorF.map(fa)(f) 39 | } 40 | 41 | def ranToYoneda[F[_], A](r: Ran[Id, F, A]): Yoneda[F, A] = 42 | new Yoneda[F, A] { 43 | def run[R](f: A => R): F[R] = r.runRan(a => f(a)) 44 | } 45 | 46 | /** 47 | * Yoneda is a Funcor for free 48 | * 49 | * It is kind of Free Functor as we need Functor when we create Yoneda. 50 | * But we don't use it to define Functor. 51 | */ 52 | def yonedaFunctor[F[_]]: Functor[Yoneda[F, *]] = 53 | new Functor[Yoneda[F, *]] { 54 | def map[A, B](fa: Yoneda[F, A])(f: A => B): Yoneda[F, B] = 55 | new Yoneda[F, B] { 56 | def run[C](f2: B => C): F[C] = fa.run(f andThen f2) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/Category.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two 2 | 3 | trait Semicategory[Morphism[_, _]] { 4 | def compose[A, B, C](f: Morphism[B, C])(g: Morphism[A, B]): Morphism[A, C] 5 | } 6 | 7 | trait SemicategoryLaws[M[_, _]] extends Semicategory[M] { 8 | 9 | def compositivityLaw[A, B, C, D]( 10 | g: M[B, C], 11 | f: M[A, B], 12 | h: M[C, D] 13 | ): Boolean = { 14 | val gf: M[A, C] = compose(g)(f) 15 | val v2: M[A, D] = compose(h)(gf) 16 | 17 | val hg: M[B, D] = compose(h)(g) 18 | val w2: M[A, D] = compose(hg)(f) 19 | 20 | v2 == w2 21 | } 22 | } 23 | 24 | trait Category[Morphism[_, _]] extends Semicategory[Morphism] { 25 | def id[Obj]: Morphism[Obj, Obj] 26 | } 27 | 28 | trait CategoryLaws[M[_, _]] extends Category[M] { 29 | def leftIdentityLaw[A, B](fa: M[A, B]): Boolean = { 30 | compose(id[B])(fa) == fa 31 | } 32 | 33 | def rightIdentityLaw[A, B](fa: M[A, B]): Boolean = { 34 | compose(fa)(id[A]) == fa 35 | } 36 | } 37 | 38 | object CategoryInstances { 39 | trait Function1Cat extends Category[Function1] { 40 | def id[A]: A => A = identity[A] 41 | def compose[A, B, C](f: B => C)(g: A => B): A => C = g andThen f 42 | } 43 | 44 | val scalaProperTypesAndPureFunction1: Category[Function1] = 45 | new Function1Cat {} 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/bifunctors/Biapplicative.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.bifunctors 2 | 3 | trait Biapply[F[A, B]] { 4 | def biApply[A, B, AA, BB](fa: F[A, B])(ff: F[A => AA, B => BB]): F[AA, BB] 5 | } 6 | 7 | trait Biapplicative[F[A, B]] { 8 | def bipure[A,B](a: A, b: B): F[A,B] 9 | } -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/bifunctors/Bifoldable.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.bifunctors 2 | 3 | class Bifoldable {} 4 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/bifunctors/Bitraverse.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.bifunctors 2 | 3 | class Bitraverse {} 4 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/bifunctors/Clown.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.bifunctors 2 | 3 | trait Clown[F[_, _]] { 4 | def mapLeft[A, AA, B](fa: F[A, B])(g: A => AA): F[AA, B] 5 | } 6 | 7 | trait ClownLaws[P[_, _]] extends Clown[P] { 8 | 9 | def mapLeftDistributeOverCompositionLaw[A, B, C, X]( 10 | fa: P[A, X], 11 | f: B => C, 12 | g: A => B 13 | ): Boolean = { 14 | val v1: P[C, X] = mapLeft(fa)(f compose g) 15 | val w1: P[B, X] = mapLeft(fa)(g) 16 | val w2: P[C, X] = mapLeft(w1)(f) 17 | v1 == w2 18 | } 19 | 20 | def mapLeftIdentityLaw[A, B](fa: P[A, B]): Boolean = { 21 | mapLeft(fa)(identity[A]) == fa 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/bifunctors/Joker.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.bifunctors 2 | 3 | trait Joker[F[_, _]] { 4 | def map[A, B, BB](fa: F[A, B])(g: B => BB): F[A, BB] 5 | } 6 | 7 | trait JokerLaws[P[_, _]] extends Joker[P] { 8 | 9 | def mapDistributeOverCompositionLaw[A, B, C, X]( 10 | fa: P[X, A], 11 | f: B => C, 12 | g: A => B 13 | ): Boolean = { 14 | val v1: P[X, C] = map(fa)(f compose g) 15 | val w1: P[X, B] = map(fa)(g) 16 | val w2: P[X, C] = map(w1)(f) 17 | v1 == w2 18 | } 19 | 20 | def mapIdentityLaw[A, B](fa: P[A, B]): Boolean = { 21 | map(fa)(identity[B]) == fa 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/MonoidalVCategory.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /** 4 | Monoidal V-category (M,⊗,I,α,ρ,λ) consists of: 5 | * V-category M 6 | * V-functor ⊗ 7 | M ⊗ M => M 8 | * object I : M 9 | * V-natural isomorphismss 10 | * α: (A ⊗ B) ⊗ C ~ A ⊗ (B ⊗ C) 11 | * λ: X ⊗ I) :=> X 12 | 13 | Haskell: 14 | ``` 15 | class ( Category obja a 16 | , Bifunctor obja a obja a obja a o 17 | , obja i ) 18 | => MonoidalCategory obja a o i where 19 | alpha :: (obja x, obja y, obja z) 20 | => a (x `o` (y `o` z)) ((x `o` y) `o` z) 21 | alphainv :: (obja x, obja y, obja z) 22 | => a ((x `o` y) `o` z) (x `o` (y `o` z)) 23 | rho :: (obja x) => a (x `o` i) x 24 | rhoinv :: (obja x) => a x (x `o` i) 25 | lambda :: (obja x) => a (i `o` x) x 26 | lambdainv :: (obja x) => a x (i `o` x) 27 | ``` 28 | */ 29 | trait MonoidalVCategory[OBJA[_],:=>[_,_],⊗[_,_],I] { 30 | def c: VCategory[OBJA,:=>] 31 | def tensor: VBifunctor[OBJA,:=>,OBJA,:=>,OBJA,:=>,⊗] 32 | def id: OBJA[I] 33 | def α[X,Y,Z](implicit ox: OBJA[X], oy: OBJA[Y], oz: OBJA[Z]): (X ⊗ (Y ⊗ Z)) :=> ((X ⊗ Y) ⊗ Z) 34 | def α_inv[X,Y,Z](implicit ox: OBJA[X], oy: OBJA[Y], oz: OBJA[Z]): ((X ⊗ Y) ⊗ Z) :=> (X ⊗ (Y ⊗ Z)) 35 | def λ[X](implicit ox: OBJA[X]): (X⊗I) :=> X 36 | def λ_inv[X](implicit ox: OBJA[X]): X :=> (X ⊗ I) 37 | def ρ[X](implicit ox: OBJA[X]): (I⊗X) :=> X 38 | def ρ_inv[X](implicit ox: OBJA[X]): X :=> (I ⊗ X) 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/Optic.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /** 4 | TODO in here type constraints are implemented using type aliases? rest impls uses functions as members of type - rethink 5 | Haskell: 6 | data Optic objc c objd d objm m o i f g a b s t where 7 | Optic :: ( MonoidalAction objm m o i objc c f 8 | , MonoidalAction objm m o i objd d g 9 | , objc a, objc s , objd b, objd t , objm x ) 10 | => c s (f x a) -> d (g x b) t 11 | -> Optic objc c objd d objm m o i f g a b s t 12 | 13 | */ 14 | trait Optic[OBJC[_],C[_,_],OBJD[_],D[_,_],OBJM[_],M[_,_],O[_,_],I,F[_,_],G[_,_],A,B,S,T] { 15 | def maf: StrongMonoidalVAction[OBJM,M,O,I,OBJC,C,F] 16 | def mag: StrongMonoidalVAction[OBJM,M,O,I,OBJD,D,G] 17 | def oa: OBJC[A] 18 | def os: OBJC[S] 19 | def ob: OBJD[B] 20 | def ot: OBJD[T] 21 | type X 22 | def l: C[S,F[X,A]] 23 | def r: D[G[X,B],T] 24 | } 25 | 26 | object Optic { 27 | 28 | def apply[OBJC[_],C[_,_],OBJD[_],D[_,_],OBJM[_],M[_,_],O[_,_],I,F[_,_],G[_,_],A,B,S,T,XX]( 29 | amaf: StrongMonoidalVAction[OBJM,M,O,I,OBJC,C,F], 30 | amag: StrongMonoidalVAction[OBJM,M,O,I,OBJD,D,G], 31 | aoa: OBJC[A], 32 | aos: OBJC[S], 33 | aob: OBJD[B], 34 | aot: OBJD[T], 35 | al: C[S,F[XX,A]], 36 | ar: D[G[XX,B],T], 37 | ox: OBJM[XX] 38 | ): Optic[OBJC,C,OBJD,D,OBJM,M,O,I,F,G,A,B,S,T] = { 39 | new Optic[OBJC,C,OBJD,D,OBJM,M,O,I,F,G,A,B,S,T] { 40 | def maf: StrongMonoidalVAction[OBJM,M,O,I,OBJC,C,F] = amaf 41 | def mag: StrongMonoidalVAction[OBJM,M,O,I,OBJD,D,G] = amag 42 | def oa: OBJC[A] = aoa 43 | def os: OBJC[S] = aos 44 | def ob: OBJD[B] = aob 45 | def ot: OBJD[T] = aot 46 | type X = XX 47 | def l: C[S, F[XX, A]] = al 48 | def r: D[G[XX, B], T] = ar 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/ProfOptic.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /** 4 | 5 | Haskell 6 | type ProfOptic objc c objd d objm m o i f g a b s t = forall p . 7 | ( Tambara objc c objd d objm m o i f g p 8 | , MonoidalAction objm m o i objc c f 9 | , MonoidalAction objm m o i objd d g 10 | , objc a , objd b , objc s , objd t 11 | ) => p a b -> p s t 12 | */ 13 | trait ProfOptic[OBJC[_],C[_,_],OBJD[_],D[_,_],OBJM[_],M[_,_],O[_,_],I,F[_,_],G[_,_],A,B,S,T] { 14 | def MAF: StrongMonoidalVAction[OBJM,M,O,I,OBJC,C,F] 15 | def MAG: StrongMonoidalVAction[OBJM,M,O,I,OBJD,D,G] 16 | def OA: OBJC[A] 17 | def OB: OBJD[B] 18 | def OS: OBJC[S] 19 | def OT: OBJD[T] 20 | 21 | def apply[P[_,_]](implicit 22 | TP: Tambara[OBJC,C,OBJD,D,OBJM,M,O,I,F,G,P] // TODO this is trait constraint, but I can't express it, withotut froall P[_,_] 23 | ): P[A,B] => P[S,T] 24 | } 25 | 26 | // TODO 27 | //object ExistentialAndProfunctorOptics { 28 | // def ex2prof[OBJC[_],C[_,_],OBJD[_],D[_,_],OBJM[_],M[_,_],O[_,_],I,F[_,_],G[_,_],A,B,S,T]: 29 | // Optic[OBJC,C,OBJD,D,OBJM,M,O,I,F,G,A,B,S,T] => 30 | // ProfOptic[OBJC,C,OBJD,D,OBJM,M,O,I,F,G,A,B,S,T] = p => { 31 | // new ProfOptic[OBJC,C,OBJD,D,OBJM,M,O,I,F,G,A,B,S,T] { 32 | // def MAF: StrongMonoidalVAction[OBJM, M, O, I, OBJC, C, F] = p.maf 33 | // def MAG: StrongMonoidalVAction[OBJM, M, O, I, OBJD, D, G] = p.mag 34 | // def OA: OBJC[A] = p.oa 35 | // def OB: OBJD[B] = p.ob 36 | // def OS: OBJC[S] = p.os 37 | // def OT: OBJD[T] = p.ot 38 | // def apply[P[_, _]](implicit TP: Tambara[OBJC, C, OBJD, D, OBJM, M, O, I, F, G, P]): P[A, B] => P[S, T] = { 39 | // TP.pp.dimap compose TP.tambara 40 | // } 41 | // } 42 | // } 43 | //} 44 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/StrongMonoidalVAction.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /** 4 | Strong monoidal V-action F 5 | 6 | Haskell: 7 | 8 | class ( MonoidalCategory objm m o i 9 | , Bifunctor objm m objc c objc c f 10 | , Category objc c ) 11 | => MonoidalAction objm m o i objc c f where 12 | unitor :: (objc x) => c (f i x) x 13 | unitorinv :: (objc x) => c x (f i x) 14 | multiplicator :: (objc x, objm p, objm q) 15 | => c (f p (f q x)) (f (p `o` q) x) 16 | multiplicatorinv :: (objc x, objm p, objm q) 17 | => c (f (p `o` q) x) (f p (f q x)) 18 | */ 19 | trait StrongMonoidalVAction[OBJM[_],M[_,_],O[_,_],I,OBJC[_],C[_,_],F[_,_]] { 20 | def mc: MonoidalVCategory[OBJM,M,O,I] 21 | def bif: VBifunctor[OBJM,M,OBJC,C,OBJC,C,F] 22 | def c: VCategory[OBJC,C] 23 | 24 | def unitor[X](implicit ox: OBJC[X]): C[F[I,X],X] 25 | def unitorinv[X](implicit ox: OBJC[X]): C[X,F[I,X]] 26 | def multiplicator[X,P,Q](implicit ox: OBJC[X], op: OBJC[P], oq: OBJC[Q]): 27 | C[F[P,F[Q,X]],F[O[P,Q],X]] 28 | def multiplicatorinv[X,P,Q](implicit ox: OBJC[X], op: OBJC[P], oq: OBJC[Q]): 29 | C[F[O[P,Q],X],F[P,F[Q,X]]] 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/Tambara.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /* 4 | class ( MonoidalAction objm m o i objc c f 5 | , MonoidalAction objm m o i objd d g 6 | , Profunctor objc c objd d p ) 7 | => Tambara objc c objd d objm m o i f g p where 8 | tambara :: (objc x, objd y, objm w) 9 | => p x y -> p (f w x) (g w y) 10 | */ 11 | trait Tambara[OBJC[_],C[_,_],OBJD[_],D[_,_],OBJM[_],M[_,_],O[_,_],I,F[_,_],G[_,_],P[_,_]] { 12 | def maf: StrongMonoidalVAction[OBJM,M,O,I,OBJC,C,F] 13 | def mag: StrongMonoidalVAction[OBJM,M,O,I,OBJD,D,G] 14 | def pp: VProfunctor[OBJC,C,OBJD,D,P] 15 | 16 | def tambara[X,Y,W](implicit ox: OBJC[X], o: OBJD[Y], ow: OBJM[W]): P[X,Y] => P[F[W,X],G[W,Y]] 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/VBifunctor.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /* 4 | class ( Category objc c, Category objd d, Category obje e 5 | , forall x y . (objc x , objd y) => obje (f x y) ) 6 | => Bifunctor objc c objd d obje e f where 7 | bimap :: ( objc x1, objc x2, objd y1, objd y2 ) 8 | => c x1 x2 -> d y1 y2 -> e (f x1 y1) (f x2 y2) 9 | */ 10 | trait VBifunctor[OBJC[_],C[_,_],OBJD[_],D[_,_],OBJE[_],E[_,_],F[_,_]] { 11 | def cc: VCategory[OBJC,C] 12 | def cd: VCategory[OBJD,D] 13 | def ce: VCategory[OBJE,E] 14 | def xy[X,Y](implicit ox: OBJC[X], oy: OBJD[Y]): OBJE[F[X,Y]] 15 | 16 | def bimap[X1,X2,Y1,Y2](implicit ox1: OBJC[X1], 17 | ox2: OBJC[X2], 18 | oy1: OBJD[Y1], 19 | oy2: OBJD[Y2]): C[X1,X2] => D[Y1,Y2] => E[F[X1,X2],F[X2,Y2]] 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/VCategory.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /* 4 | V-enriched category 5 | 6 | nLab: https://ncatlab.org/nlab/show/enriched+category 7 | 8 | Haskell: 9 | class Category objc c where 10 | unit :: (objc x) => c x x 11 | comp :: (objc x) => c y z -> c x y -> c x z 12 | */ 13 | trait VCategory[OBJC[_],C[_,_]] { 14 | def unit[X](implicit o: OBJC[X]): C[X,X] 15 | def comp[X,Y,Z](implicit o: OBJC[X]): C[Y,Z] => C[X,Y] => C[X,Z] 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/VFunctor.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /* 4 | V-enriched functor 5 | 6 | nLab: https://ncatlab.org/nlab/show/enriched+functor 7 | 8 | Haskell 9 | class ( Category objc c, Category objd d 10 | , forall x . objc x => objd (f x) 11 | ) => VFunctor objc c objd d f where 12 | map :: (objc x, objc y) => c x y -> d (f x) (f y) 13 | */ 14 | trait VFunctor[OBJC[_],C[_,_],OBJD[_],D[_,_],F[_]] { 15 | def c1: VCategory[OBJC,C] 16 | def c2: VCategory[OBJD,D] 17 | def f[X]: OBJC[X] => OBJD[F[X]] 18 | 19 | def map[X,Y](implicit x: OBJC[X], y: OBJD[Y]): C[X,Y] => D[F[X],F[Y]] 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/enriched/VProfunctor.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.enriched 2 | 3 | /* 4 | V-profunctor 5 | 6 | class ( Category objc c, Category objd d ) 7 | => Profunctor objc c objd d p where 8 | dimap :: (objc x1, objc x2, objd y1, objd y2) 9 | => c x2 x1 -> d y1 y2 -> p x1 y1 -> p x2 y2 10 | */ 11 | 12 | trait VProfunctor[OBJC[_],C[_,_],OBJD[_],D[_,_],P[_,_]] { 13 | def cc: VCategory[OBJC,C] 14 | def cd: VCategory[OBJD,D] 15 | 16 | def dimap[X1,X2,Y1,Y2](implicit ox1: OBJC[X1], ox2: OBJC[X2], oy1: OBJD[Y1], oy2: OBJD[Y2]): 17 | C[X2,X1] => D[Y1,Y2] => P[X1,X2] => P[X2,Y2] 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/Costar.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor 2 | 3 | import educational.category_theory.Functor 4 | 5 | /** Lift backward Functor into Profunctor */ 6 | case class Costar[F[_], D, C](runCostar: F[D] => C) 7 | 8 | object CostarInstances { // https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor.html#i:Costar 9 | def profunctor[F[_]](FF: Functor[F]): Profunctor[Costar[F, *, *]] = 10 | new Profunctor[Costar[F, *, *]] { 11 | def dimap[A, D, B, C]( 12 | fbc: Costar[F, B, C] 13 | )(ab: A => B, cd: C => D): Costar[F, A, D] = 14 | Costar { fa => 15 | val v: F[B] = FF.map(fa)(ab) 16 | val c: C = fbc.runCostar(v) 17 | cd(c) 18 | } 19 | } 20 | 21 | // TODO more instances 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/Forget.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor 2 | 3 | case class Forget[R, A, B](runForget: A => R) 4 | 5 | object ForgetInstances { // https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor.html#i:Forget 6 | def profunctor[R]: Profunctor[Forget[R, *, *]] = 7 | new Profunctor[Forget[R, *, *]] { 8 | def dimap[X, W, Y, Z]( 9 | k: Forget[R, Y, Z] 10 | )(f: X => Y, cd: Z => W): Forget[R, X, W] = 11 | Forget(k.runForget compose f) 12 | } 13 | 14 | // TODO more instances 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/Star.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor 2 | 3 | import educational.category_theory.Functor 4 | 5 | /** Lift Functor F: C -> D into Profunctor ProF: C -> D */ 6 | case class Star[F[_], D, C](runStar: D => F[C]) 7 | 8 | object StarInstances { 9 | def profunctor[F[_]](implicit FF: Functor[F]): Profunctor[Star[F, *, *]] = 10 | new Profunctor[Star[F, *, *]] { 11 | def dimap[X, W, Y, Z]( 12 | bfc: Star[F, Y, Z] 13 | )(ab: X => Y, cd: Z => W): Star[F, X, W] = 14 | Star[F, X, W] { x => FF.map((bfc.runStar compose ab)(x))(cd) } 15 | } 16 | 17 | // TODO more instances 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/closed/Closed.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.closed 2 | 3 | import educational.category_theory.two.profunctor.{Profunctor, ProfunctorLaws} 4 | import educational.category_theory.two.profunctor.ProfunctorInstance.Function1Profunctor 5 | 6 | import scala.Function.{const, uncurried, untupled} 7 | 8 | /** 9 | * Closed Profunctor 10 | * 11 | * Laws: 12 | * 13 | * lmap (. f) . closed == rmap (. f) . closed 14 | * closed . closed == dimap uncurry curry . closed 15 | * dimap const ($()) . closed == id 16 | */ 17 | trait Closed[=:>[_,_]] extends Profunctor[=:>] { 18 | def closed[A,B,C](pab: A =:> B): (C => A) =:> (C => B) 19 | } 20 | 21 | object ClosedInstances { 22 | val Function1Closed: Closed[Function1] = new Closed[Function1] with Function1Profunctor { 23 | override def closed[A,B,C](f: A => B): (C => A) => C => B = _ andThen f 24 | } 25 | } 26 | 27 | trait ClosedLaws[=:>[_,_]] extends Closed[=:>] with ProfunctorLaws[=:>] { 28 | // lmap (. f) . closed ≡ rmap (. f) . closed 29 | def lmapClosedEqRmapClosed[A, B, C, D](p: A =:> B, f: (D => A) => (C => A), g: (D => B) => (C => B)): Boolean = { 30 | val l1: (C => A) =:> (C => B) = closed(p) 31 | val l2: (D => A) =:> (C => B) = lmap(l1)(f) 32 | 33 | val r1: (D => A) =:> (D => B) = closed(p) 34 | val r2: (D => A) =:> (C => B) = rmap(r1)(g) 35 | l2 == r2 // TODO wh haskell uses 1 method? C == D and it is polymorphic? wired? 36 | } 37 | 38 | // closed . closed ≡ dimap uncurry curry . closed 39 | def closedClosedEqDimapClosed[A, B](p: A =:> B): Boolean = { 40 | val l1: (B => A) =:> (B => B) = closed(p) 41 | val l2: (A => B => A) =:> (A => B => B) = closed(l1) 42 | val r1: (((A, B)) => A) =:> (((A, B)) => B) = closed[A, B, (A, B)](p) 43 | val r2: (A => B => A) =:> (A => B => B) = dimap(r1)(uncurry[A, B, A], curry[A, B, B]) 44 | l2 == r2 45 | } 46 | 47 | // dimap const ($()) . closed ≡ id 48 | def dimapCloseIsIdentity2[A,B](p: A =:> B): Boolean = { 49 | val l1: (Unit => A) =:> (Unit => B) = closed(p) 50 | def foo[YY]: (Unit => YY) => YY = f => f(()) 51 | val l2: A =:> B = dimap(l1)(const[A,Unit],foo) 52 | l2 == p 53 | } 54 | 55 | def uncurry[X, Y, Z]: (X => Y => Z) => Tuple2[X, Y] => Z = uncurried(_).tupled 56 | def curry[X, Y, Z]: (Tuple2[X, Y] => Z) => X => Y => Z = untupled(_).curried 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/closed/Closure.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.closed 2 | 3 | import educational.category_theory.two.profunctor.higher.DinaturalTransformation 4 | 5 | /* 6 | close . unclose ≡ id 7 | unclose . close ≡ id 8 | */ 9 | trait Closure[P[_, _], A, B] { 10 | def runClosure[X]: P[X => A, B => B] 11 | } 12 | 13 | object Closure { 14 | def close[P[_, _], Q[_, _]](pq: DinaturalTransformation[P, Q])( 15 | CP: Closed[P] 16 | ): DinaturalTransformation[P, Closure[Q, *, *]] = ??? 17 | def unclose[P[_, _], Q[_, _]]( 18 | pcq: DinaturalTransformation[P, Closure[Q, *, *]] 19 | ): DinaturalTransformation[P, Q] = ??? 20 | } 21 | 22 | object ClousreInstances { 23 | 24 | def closedProfunctor[P[_, _]]: Closed[Closure[P, *, *]] = 25 | new Closed[Closure[P, *, *]] { 26 | def closed[A, B, X](pab: Closure[P, A, B]): Closure[P, X => A, X => B] = 27 | ??? 28 | def dimap[X, W, Y, Z]( 29 | pab: Closure[P, Y, Z] 30 | )(ab: X => Y, cd: Z => W): Closure[P, X, W] = ??? 31 | } 32 | 33 | // TODO more instances 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/closed/Coclosed.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.closed 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | import educational.category_theory.two.profunctor.ProfunctorLaws 5 | import educational.category_theory.two.profunctor.ProfunctorInstance.Function1Profunctor 6 | import scala.Function.uncurried 7 | import scala.Function.untupled 8 | 9 | // class Profunctor p => Coclosed p where 10 | // open :: p a b -> p (b -> x) (a -> x) 11 | // 12 | trait Coclosed[=:>[_,_]] extends Profunctor[=:>] { 13 | def open[A,B,C](pab: A =:> B): (B => C) =:> (A => C) 14 | } 15 | 16 | object CoclosedInstances { 17 | val Function1Chocolate: Coclosed[Function1] = new Coclosed[Function1] with Function1Profunctor { 18 | override def open[A,B,C](f: A => B): (B => C) => A => C = _ compose f 19 | } 20 | } 21 | 22 | trait CoclosedLaws[=:>[_,_]] extends Coclosed[=:>] with ProfunctorLaws[=:>] { 23 | 24 | // lmap (. f) . open ≡ rmap (. f) . open 25 | def lmapClosedEqRmapClosed[A, B, C, D](p: A =:> B, f: (B => D) => (B => C), g: (A => D) => (A => C)): Boolean = { 26 | val l1: (B => C) =:> (A => C) = open(p) // (C => A) =:> (C => B) 27 | val l2: (B => D) =:> (A => C) = lmap(l1)(f) 28 | 29 | val r1: (B => D) =:> (A => D) = open(p) 30 | val r2: (B => D) =:> (A => C) = rmap(r1)(g) 31 | l2 == r2 // TODO wh haskell uses 1 method? C == D and it is polymorphic? wired? 32 | } 33 | 34 | // TODO WIP 35 | // // open . open ≡ dimap uncurry curry . open 36 | // def closedClosedEqDimapClosed[A, B](p: A =:> B): Boolean = { 37 | // val l1: (B => B) =:> (A => B) = open(p) 38 | // val l2: (A => B => A) =:> (A => B => B) = open(l1) 39 | // val r1: (((A, B)) => A) =:> (((A, B)) => B) = open[A, B, (A, B)](p) 40 | // val r2: (A => B => A) =:> (A => B => B) = dimap(r1)(uncurry[A, B, A], curry[A, B, B]) 41 | // l2 == r2 42 | // } 43 | 44 | // TODO WIP 45 | // dimap const ($()) . open ≡ id 46 | // def dimapCloseIsIdentity2[A,B](p: A =:> B): Boolean = { 47 | // val l1: (B => Unit) =:> (A => Unit) = open(p) 48 | // def foo[YY]: (Unit => YY) => YY = f => f(()) 49 | // val l2: A =:> B = dimap(l1)(foo, const) 50 | // l2 == p 51 | // } 52 | 53 | def uncurry[X, Y, Z]: (X => Y => Z) => Tuple2[X, Y] => Z = uncurried(_).tupled 54 | def curry[X, Y, Z]: (Tuple2[X, Y] => Z) => X => Y => Z = untupled(_).curried 55 | } 56 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/closed/Environment.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.closed 2 | 3 | trait Environment[P[_, _], A, B] { 4 | type X 5 | type Y 6 | type Z 7 | def f1: (Z => Y) => B 8 | def pxy: P[X, Y] 9 | def f2: A => Z => X 10 | } 11 | 12 | object EnvironmentInstances { // https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor-Closed.html#i:Environment 13 | // TODO 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/higher/DinaturalTransformation.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.higher 2 | 3 | /** 4 | * Dinatural Transformation is a function that change one Profunctor P into another one Q 5 | * without modifying the content. 6 | * 7 | * It is Equivalent to Natural Transformation between two Functors (but for Profunctors). 8 | * 9 | * Laws: 10 | * rmap f . dinat . lmap f = lmap f . dinat . rmap f 11 | * 12 | * Do we get it for free by parametricity? 13 | */ 14 | trait DinaturalTransformation[P[_, _], Q[_, _]] { 15 | def dinat[A, B](p: P[A, B]): Q[A, B] 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/higher/Procompose.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.higher 2 | 3 | import educational.category_theory.two.Category 4 | import educational.category_theory.two.profunctor.Profunctor 5 | 6 | /** Composition of Profunctors */ 7 | trait Procompose[P[_, _], Q[_, _], D, C] { 8 | type X 9 | val p: P[X, C] 10 | val q: Q[D, X] 11 | } 12 | 13 | object Precompose { 14 | def apply[XX, P[_, _], Q[_, _], D, C]( 15 | pxxc: P[XX, C], 16 | qdxx: Q[D, XX] 17 | ): Procompose[P, Q, D, C] = 18 | new Procompose[P, Q, D, C] { 19 | type X = XX 20 | val p: P[X, C] = pxxc 21 | val q: Q[D, X] = qdxx 22 | } 23 | 24 | def precomposed[P[_, _], A, B](p: Procompose[P, P, A, B])(implicit 25 | CP: Category[P] 26 | ): P[A, B] = CP.compose(p.p)(p.q) 27 | } 28 | 29 | object PrecomposeInstances { 30 | // def profunctorMonad[P[_,_],D,C]: ProfunctorMonad[Procompose[P, *[_], D, C]] = ??? TODO is it possible to express this in Scala ? 31 | def profunctor[P[_, _], Q[_, _]]: Profunctor[Procompose[P, Q, *, *]] = 32 | new Profunctor[Procompose[P, Q, *, *]] { 33 | def dimap[XX, W, Y, Z]( 34 | pab: Procompose[P, Q, Y, Z] 35 | )(ab: XX => Y, cd: Z => W): Procompose[P, Q, XX, W] = 36 | ??? // TODO implement rmap and lmap and then use thos defs here 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/higher/ProfunctorComonad.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.higher 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | 5 | /** 6 | * Laws 7 | * 8 | * proextract . promap f ≡ f . proextract 9 | * proextract . produplicate ≡ id 10 | * promap proextract . produplicate ≡ id 11 | * produplicate . produplicate ≡ promap produplicate . produplicate 12 | */ 13 | trait ProfunctorComonad[T[_]] extends ProfunctorFunctor[T] { 14 | def proextract[P[_, _]](implicit 15 | P: Profunctor[P] 16 | ): DinaturalTransformation[Lambda[(A, B) => T[P[A, B]]], P] 17 | def produplicate[P[_, _]](implicit 18 | P: Profunctor[P] 19 | ): DinaturalTransformation[Lambda[(A, B) => T[P[A, B]]], Lambda[ 20 | (A, B) => T[T[P[A, B]]] 21 | ]] 22 | } 23 | 24 | object ProfunctorComonadInstance { 25 | // TODO Cotambara 26 | // TODO Tambara 27 | // TODO Closure 28 | // TODO need CotambaraSum 29 | // TODO TambaraSum 30 | // TODO CofreeTraversing 31 | // TODO CofreeMapping 32 | // TODO Coyoneda 33 | // TODO Yoneda 34 | // TODO Rift Ran Cayley 35 | // TODO Bifunctor Product 36 | // TODO BIfunctor Tannen 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/higher/ProfunctorCoyoneda.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.higher 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | 5 | trait ProfunctorCoyoneda[P[_, _], A, B] { 6 | type X 7 | type Y 8 | def f1: A => X 9 | def f2: Y => B 10 | def pxy: P[X, Y] 11 | 12 | def dimap[C, W](l: C => A, r: B => W): ProfunctorCoyoneda[P, C, W] = 13 | ProfunctorCoyoneda[X, Y, P, C, W](f1 compose l, r compose f2, pxy) 14 | } 15 | 16 | object ProfunctorCoyoneda { 17 | 18 | def apply[XX, YY, P[_, _], A, B]( 19 | ax: A => XX, 20 | yb: YY => B, 21 | p: P[XX, YY] 22 | ): ProfunctorCoyoneda[P, A, B] = 23 | new ProfunctorCoyoneda[P, A, B] { 24 | type X = XX 25 | type Y = YY 26 | def f1: A => X = ax 27 | def f2: Y => B = yb 28 | def pxy: P[X, Y] = p 29 | } 30 | 31 | /** 32 | * returnCoyoneda . proextract ≡ id 33 | * proextract . returnCoyoneda ≡ id 34 | * produplicate ≡ returnCoyoneda 35 | */ 36 | def returnCoyoneda[P[_, _], A, B](pab: P[A, B]): ProfunctorCoyoneda[P, A, B] = 37 | ProfunctorCoyoneda(identity[A], identity[B], pab) 38 | 39 | /** 40 | * joinCoyoneda . produplicate ≡ id 41 | * produplicate . joinCoyoneda ≡ id 42 | * joinCoyoneda ≡ proextract 43 | */ 44 | def joinCoyoneda[P[_, _], A, B]( 45 | p: ProfunctorCoyoneda[ProfunctorCoyoneda[P, *, *], A, B] 46 | ): ProfunctorCoyoneda[P, A, B] = ??? 47 | } 48 | 49 | object ProfunctorCoyonedaInstances { 50 | def profunctor[P[_, _]]: Profunctor[ProfunctorCoyoneda[P, *, *]] = 51 | new Profunctor[ProfunctorCoyoneda[P, *, *]] { 52 | def dimap[X, W, Y, Z]( 53 | p: ProfunctorCoyoneda[P, Y, Z] 54 | )(l: X => Y, r: Z => W): ProfunctorCoyoneda[P, X, W] = 55 | p.dimap(l, r) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/higher/ProfunctorFunctor.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.higher 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | 5 | /** 6 | * Laws: 7 | * 8 | * promap f . promap g == promap (f . g) 9 | * promap id == id 10 | */ 11 | trait ProfunctorFunctor[T[_]] { 12 | def promap[P[_, _], Q[_, _]](dt: DinaturalTransformation[P, Q])(implicit 13 | PP: Profunctor[P] 14 | ): DinaturalTransformation[Lambda[(A, B) => T[P[A, B]]], Lambda[ 15 | (A, B) => T[Q[A, B]] 16 | ]] 17 | } 18 | 19 | object ProfunctorFunctorInstances { 20 | // TODO 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/higher/ProfunctorMonad.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.higher 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | 5 | /** 6 | * Laws 7 | * 8 | * promap f . proreturn ≡ proreturn . f 9 | * projoin . proreturn ≡ id 10 | * projoin . promap proreturn ≡ id 11 | * projoin . projoin ≡ projoin . promap projoin 12 | */ 13 | trait ProfunctorMonad[T[_]] extends ProfunctorFunctor[T] { 14 | def proreturn[P[_, _]](implicit 15 | P: Profunctor[P] 16 | ): DinaturalTransformation[P, Lambda[(A, B) => T[P[A, B]]]] 17 | def projoin[P[_, _]](implicit 18 | P: Profunctor[P] 19 | ): DinaturalTransformation[Lambda[(A, B) => T[T[P[A, B]]]], Lambda[ 20 | (A, B) => T[P[A, B]] 21 | ]] 22 | } 23 | 24 | object ProfunctorMonadInstances { // https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor-Monad.html#i:ProfunctorMonad 25 | // TODO Profunctor Monad instances 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/higher/ProfunctorRan.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.higher 2 | 3 | trait ProfunctorRan[P[_, _], Q[_, _], A, B] { 4 | def runRan[X](pxa: P[X, A]): Q[X, B] 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/higher/ProfunctorYoneda.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.higher 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.two.profunctor.Profunctor 5 | 6 | trait ProfunctorYoneda[P[_, _], A, B] { self => 7 | def runYoneda[X, Y](f: X => A, g: B => Y): P[X, Y] 8 | 9 | def dimap[AA, BB](l: AA => A, r: B => BB): ProfunctorYoneda[P, AA, BB] = 10 | new ProfunctorYoneda[P, AA, BB] { 11 | def runYoneda[X, Y](l2: X => AA, r2: BB => Y): P[X, Y] = { 12 | val f1: X => A = l compose l2 13 | val f2: B => Y = r2 compose r 14 | self.runYoneda(f1, f2) 15 | } 16 | } 17 | 18 | /** 19 | * projoin . extractYoneda ≡ id 20 | * extractYoneda . projoin ≡ id 21 | * projoin ≡ extractYoneda 22 | */ 23 | def extractYoneda: P[A, B] = runYoneda(identity[A], identity[B]) 24 | 25 | /** 26 | * projoin . duplicateYoneda ≡ id 27 | * duplicateYoneda . projoin ≡ id 28 | * duplicateYoneda = proreturn 29 | */ 30 | def duplicateYoneda: ProfunctorYoneda[ProfunctorYoneda[P, *, *], A, B] = 31 | new ProfunctorYoneda[ProfunctorYoneda[P, *, *], A, B] { 32 | def runYoneda[X, Y](l: X => A, r: B => Y): ProfunctorYoneda[P, X, Y] = 33 | self.dimap(l, r) 34 | } 35 | } 36 | 37 | object ProfunctorYonedaInstances { 38 | def profunctor[P[_, _]]: Profunctor[ProfunctorYoneda[P, *, *]] = 39 | new Profunctor[ProfunctorYoneda[P, *, *]] { 40 | def dimap[A, D, B, C]( 41 | pab: ProfunctorYoneda[P, B, C] 42 | )(l: A => B, r: C => D): ProfunctorYoneda[P, A, D] = pab.dimap(l, r) 43 | } 44 | 45 | def functor[P[_, _], A]: Functor[ProfunctorYoneda[P, A, *]] = 46 | new Functor[ProfunctorYoneda[P, A, *]] { 47 | def map[C, D]( 48 | x: ProfunctorYoneda[P, A, C] 49 | )(cd: C => D): ProfunctorYoneda[P, A, D] = 50 | new ProfunctorYoneda[P, A, D] { 51 | def runYoneda[X, Y](xa: X => A, dy: D => Y): P[X, Y] = 52 | x.runYoneda(xa, cd andThen dy) 53 | } 54 | } 55 | 56 | // TODO more instances 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/mapping/Settable.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.mapping 2 | 3 | import educational.category_theory.Functor 4 | 5 | trait Settable[P[_, _]] extends Walk[P] { // http://hackage.haskell.org/package/profunctors/docs/Data-Profunctor-Mapping.html 6 | def mapping[A, B, F[_]](pab: P[A, B])(implicit FT: Functor[F]): P[F[A], F[B]] 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/mapping/Walk.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.mapping 2 | 3 | import educational.category_theory.Traverse 4 | import educational.category_theory.two.profunctor.traverse.Step 5 | 6 | trait Walk[P[_, _]] extends Step[P] { 7 | def walk[A, B, F[_]](pab: P[A, B])(implicit FT: Traverse[F]): P[F[A], F[B]] 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/optics/Glassed.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.optics 2 | 3 | import educational.category_theory.two.profunctor.closed.Closed 4 | import educational.category_theory.two.profunctor.strong.Strong 5 | 6 | trait Glassed[=>:[_, _]] extends Strong[=>:] with Closed[=>:] { 7 | def glassedL[A, B, U, T](pab: A =>: B): (T, U => A) =>: (T, U => B) = 8 | second(closed[A, B, U](pab)) 9 | def glassedR[A, B, U, T](pab: A =>: B): (U => A, T) =>: (U => B, T) = 10 | first(closed[A, B, U](pab)) 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/optics/Magnified.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.optics 2 | 3 | import educational.category_theory.two.profunctor.choice.Choice 4 | import educational.category_theory.two.profunctor.strong.Strong 5 | 6 | trait Magnified[=>:[_, _]] extends Strong[=>:] with Choice[=>:] { 7 | def maginfyLS[A, B, C, D]( 8 | pab: A =>: B 9 | ): (C, Either[A, D]) =>: (C, Either[B, D]) = second(left(pab)) 10 | def maginfyRS[A, B, C, D]( 11 | pab: A =>: B 12 | ): (C, Either[D, A]) =>: (C, Either[D, B]) = second(right(pab)) 13 | def maginfyLF[A, B, C, D]( 14 | pab: A =>: B 15 | ): (Either[A, D], C) =>: (Either[B, D], C) = first(left(pab)) 16 | def maginfyRF[A, B, C, D]( 17 | pab: A =>: B 18 | ): (Either[D, A], C) =>: (Either[D, B], C) = first(right(pab)) 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/optics/Polynodal.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.optics 2 | 3 | import educational.category_theory.two.profunctor.closed.Closed 4 | 5 | trait Polynodal[=>:[_, _]] extends Closed[=>:] with Traversing[=>:] { 6 | def griddedL[A, B, C, D]( 7 | pab: A =>: B 8 | ): (D => (List[A], C)) =>: (D => (List[B], C)) = closed(strechL[A, B, C](pab)) 9 | def griddedR[A, B, C, D]( 10 | pab: A =>: B 11 | ): (D => (C, List[A])) =>: (D => (C, List[B])) = closed(strechR[A, B, C](pab)) 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/optics/Polyp.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.optics 2 | 3 | import educational.category_theory.Applicative 4 | import educational.category_theory.two.profunctor.Profunctor 5 | 6 | trait Polyp[=>:[_, _]] extends Profunctor[=>:] { 7 | def polyper[A, B, F[_]](pab: A =>: B)(implicit 8 | A: Applicative[F] 9 | ): F[A] =>: F[B] 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/optics/Telescoped.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.optics 2 | 3 | import educational.category_theory.two.profunctor.choice.Choice 4 | import educational.category_theory.two.profunctor.closed.Closed 5 | 6 | trait Telescoped[=>:[_, _]] extends Closed[=>:] with Choice[=>:] { 7 | def telescopeL[A, B, C, D]( 8 | pab: A =>: B 9 | ): Either[C => A, D] =>: Either[C => B, D] = left(closed(pab)) 10 | def telescopeR[A, B, C, D]( 11 | pab: A =>: B 12 | ): Either[D, C => A] =>: Either[D, C => B] = right(closed(pab)) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/optics/Traversing.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.optics 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | 5 | trait Traversing[=>:[_, _]] extends Profunctor[=>:] { 6 | def strechL[A, B, C]( 7 | pab: A =>: B 8 | ): (List[A], C) =>: (List[B], C) // TODO replace List by Traverse 9 | def strechR[A, B, C](pab: A =>: B): (C, List[A]) =>: (C, List[B]) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/optics/Windowed.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.optics 2 | 3 | import educational.category_theory.two.profunctor.choice.Choice 4 | 5 | trait Windowed[=>:[_, _]] extends Choice[=>:] with Glassed[=>:] { 6 | def windowedRGL[A, B, U, V, T]( 7 | pab: A =>: B 8 | ): Either[V, (T, U => A)] =>: Either[V, (T, U => B)] = right(glassedL(pab)) 9 | def windowedRGR[A, B, U, V, T]( 10 | pab: A =>: B 11 | ): Either[V, (U => A, T)] =>: Either[V, (U => B, T)] = right(glassedR(pab)) 12 | def windowedLGL[A, B, U, V, T]( 13 | pab: A =>: B 14 | ): Either[(U => A, T), V] =>: Either[(U => B, T), V] = left(glassedR(pab)) 15 | def windowedLGR[A, B, U, V, T]( 16 | pab: A =>: B 17 | ): Either[(U => A, T), V] =>: Either[(U => B, T), V] = left(glassedR(pab)) 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/sieve/Cosieve.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.sieve 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | 5 | // TODO F is Functor 6 | // TODO p f | p -> f 7 | trait Cosieve[P[_, _], F[_]] extends Profunctor[P] { 8 | def cosieve[A, B](pab: P[A, B], fa: F[A]): B 9 | } 10 | 11 | object CosieveInstances { 12 | // TODO need Profunctor Costar 13 | // TODO need Cokleisli 14 | // TODO need Profuncotr Compose 15 | // TODO need Functor Compose 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/sieve/Sieve.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.sieve 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | 5 | trait Sieve[P[_, _], F[_]] extends Profunctor[P] { 6 | def sieve[A, B](pab: P[A, B], a: A): F[B] 7 | } 8 | 9 | object SieveInstances { // https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor-Sieve.html#i:Sieve 10 | // TODO need Kleisli 11 | // TODO need Profunctor Compose 12 | // TODO need Functor Compose 13 | // TODO need Profunctor Start 14 | // TODO need Profunctor Forget 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/strong/CofreeTambara.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.strong 2 | 3 | // translation from @emilypi Haskell snippet 4 | // https://gist.github.com/emilypi/407838d9c321d5b21ebc1828ad2bedcb 5 | 6 | trait CofreeTambara[P, A, B] { 7 | type C 8 | def x1: (List[A], C) 9 | def x2: (List[B], C) 10 | } 11 | 12 | object CofreeTambara { 13 | def apply[P, A, AA, B, BB, C]( 14 | t1: (List[AA], C), 15 | t2: (List[BB], C) 16 | ): CofreeTambara[P, A, B] = ??? 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/strong/Costrong.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.strong 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | 5 | trait Costrong[F[_, _]] extends Profunctor[F] { 6 | def unfirst[A, B, D](fa: F[(A, D), (B, D)]): F[A, B] 7 | def unsecond[A, B, D](fa: F[(D, A), (D, B)]): F[A, B] 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/strong/Cotambara.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.strong 2 | 3 | import educational.category_theory.two.profunctor.higher.DinaturalTransformation 4 | 5 | trait Cotambara[Q[_, _], A, B] { 6 | type R[_, _] 7 | def CR: Costrong[R] 8 | def rq: DinaturalTransformation[R, Q] 9 | def rab: R[A, B] 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/strong/FreeTambara.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.strong 2 | 3 | // translation from @emilypi Haskell snippet 4 | // https://gist.github.com/emilypi/407838d9c321d5b21ebc1828ad2bedcb 5 | 6 | trait FreeTambara[P[_, _], A, B] { 7 | type U 8 | type V 9 | type C 10 | type D 11 | def _x1: A => (List[U], C) 12 | def _x2: P[U, V] 13 | def _x3: B => (List[V], D) 14 | } 15 | 16 | object FreeTambara { 17 | def apply[A, B, CC, DD, UU, VV, P[_, _]]( 18 | x1: A => (List[UU], CC), 19 | x2: P[UU, VV], 20 | x3: B => (List[VV], DD) 21 | ): FreeTambara[P, A, B] = 22 | new FreeTambara[P, A, B] { 23 | type U = UU 24 | type V = VV 25 | type C = CC 26 | type D = DD 27 | def _x1: A => (List[UU], CC) = x1 28 | def _x2: P[UU, VV] = x2 29 | def _x3: B => (List[VV], DD) = x3 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/strong/Pastro.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.strong 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | import educational.category_theory.two.profunctor.higher.DinaturalTransformation 5 | 6 | // TODO adjunction Pastro -| Tambara 7 | // Pastro p ~ exists z. Costar ((,)z) Procompose p Procompose Star ((,)z) 8 | 9 | trait Pastro[P[_, _], A, B] { 10 | type X 11 | type Y 12 | type Z 13 | def f1: (Y, Z) => B 14 | val pxy: P[X, Y] 15 | def f2: A => (X, Z) 16 | } 17 | 18 | /** 19 | * Laws 20 | * 21 | * pastro (unpastro f) ≡ f 22 | * unpastro (pastro f) ≡ f 23 | */ 24 | object Pastro { 25 | def pastro[P[_, _], Q[_, _]](pq: DinaturalTransformation[P, Q])(implicit 26 | SQ: Strong[Q] 27 | ): DinaturalTransformation[Pastro[P, *, *], Q] = ??? 28 | def unpastro[P[_, _], Q[_, _]]( 29 | pq: DinaturalTransformation[Pastro[P, *, *], Q] 30 | ): DinaturalTransformation[P, Q] = ??? 31 | } 32 | 33 | object PastroInstances { 34 | // TODO Profunctor Monad 35 | // TODO Profunctor Adjunction 36 | 37 | def profuntorPastro[P[_, _]]: Profunctor[Pastro[P, *, *]] = 38 | new Profunctor[Pastro[P, *, *]] { 39 | def dimap[X, W, Y, Z]( 40 | pab: Pastro[P, Y, Z] 41 | )(ab: X => Y, cd: Z => W): Pastro[P, X, W] = ??? 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/strong/Strong.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.strong 2 | 3 | import educational.category_theory.two.profunctor.{ 4 | Profunctor, 5 | ProfunctorInstance 6 | } 7 | 8 | /** 9 | * Laws: 10 | * 11 | * in term of first: 12 | * 13 | * first' ≡ dimap swap swap . second' 14 | * lmap fst ≡ rmap fst . first' 15 | * lmap (second f) . first' ≡ rmap (second f) . first 16 | * first' . first' ≡ dimap assoc unassoc . first' where 17 | * assoc ((a,b),c) = (a,(b,c)) 18 | * unassoc (a,(b,c)) = ((a,b),c) 19 | * 20 | * in term of second: 21 | * 22 | * second' ≡ dimap swap swap . first' 23 | * lmap snd ≡ rmap snd . second' 24 | * lmap (first f) . second' ≡ rmap (first f) . second' 25 | * second' . second' ≡ dimap unassoc assoc . second' where 26 | * assoc ((a,b),c) = (a,(b,c)) 27 | * unassoc (a,(b,c)) = ((a,b),c) 28 | */ 29 | trait Strong[=>:[_, _]] extends Profunctor[=>:] { 30 | def first[X, Y, Z](pab: X =>: Y): (X, Z) =>: (Y, Z) 31 | 32 | def second[X, Y, Z](pab: X =>: Y): (Z, X) =>: (Z, Y) = { 33 | val v1: (X, Z) =>: (Y, Z) = first(pab) 34 | dimap(v1)(_.swap, _.swap) 35 | } 36 | } 37 | 38 | object Strong { 39 | def uncurry[P[_, _], A, B, C]( 40 | pa: P[A, B => C] 41 | )(implicit S: Strong[P]): P[(A, B), C] = { 42 | S.rmap(S.first[A, B => C, B](pa)) { case (bc, b) => bc(b) } 43 | } 44 | } 45 | 46 | object StrongInstances { // https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor-Strong.html#i:Strong 47 | val Function1Strong: Strong[Function1] = new Strong[Function1] 48 | with ProfunctorInstance.Function1Profunctor { 49 | def first[X, Y, Z](f: Function1[X, Y]): Function1[(X, Z), (Y, Z)] = { 50 | case (x, z) => (f(x), z) 51 | } 52 | } 53 | 54 | // TODO need Profunctor Yoneda 55 | // TODO need Profunctor Coyoneda 56 | // TODO need Profunctor Star 57 | // TODO need Profunctor Pastro 58 | // TODO need Profunctor Cayley 59 | // TODO need Profunctor Compose 60 | // TODO need FreeMapping 61 | // TODO need CofreeMapping 62 | 63 | // TODO FreeTraversing 64 | 65 | // TODO need Bifunctor Tannen 66 | // TODO need Bifunctor Clown 67 | // TODO need Bifunctor Compose 68 | } 69 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/traverse/CofreeTraversing.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.traverse 2 | 3 | import educational.category_theory.Traverse 4 | 5 | // free ProfunctorComonad 6 | trait CofreeTraversing[P[_, _], A, B] { 7 | def runCofreeTraversing[F[_]](implicit FT: Traverse[F]): P[F[A], F[B]] 8 | } 9 | 10 | object CofreeTraversingInstances { 11 | // instances CofreeTraversing https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor-Traversing.html#i:CofreeTraversing 12 | // TODO need ProfunctorFunctor 13 | // TODO need ProfunctorComonad 14 | // TODO when P is Profunctor then 15 | // - Profunctor 16 | // - Strong 17 | // - Choice 18 | // - Traversing 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/traverse/FreeTraversing.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.traverse 2 | 3 | import educational.category_theory.Traverse 4 | 5 | trait FreeTraversing[P[_, _], A, B] { 6 | type F[_] 7 | type X 8 | type Y 9 | val FT: Traverse[F] 10 | def fyb: F[Y] => B 11 | def pxy: P[X, Y] 12 | def afx: A => F[X] 13 | } 14 | 15 | object FreeTraversingInstances { // https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor-Traversing.html#i:FreeTraversing 16 | // TODO need FunctorProfunctor and MonadProfunctor 17 | // TODO Profunctor/Strong/Choice/Traversing for free 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/traverse/Step.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.traverse 2 | 3 | import educational.category_theory.two.profunctor.choice.Choice 4 | import educational.category_theory.two.profunctor.strong.Strong 5 | 6 | trait Step[P[_, _]] extends Choice[P] with Strong[P] { 7 | def step[A, B, C, D](pab: P[A, B]): P[Either[D, (A, C)], Either[D, (B, C)]] = 8 | right(first(pab)) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/educational/category_theory/two/profunctor/traverse/Traversing.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor.traverse 2 | 3 | import educational.category_theory.{Applicative, Traverse} 4 | import educational.category_theory.two.profunctor.choice.Choice 5 | import educational.category_theory.two.profunctor.strong.Strong 6 | 7 | /** Traversing Profunctor 8 | * Hasekll impl: // http://hackage.haskell.org/package/profunctors/docs/Data-Profunctor-Traversing.html 9 | * * 10 | * TODO is this the same as Walk ? (what about step from superclass ?) 11 | * * 12 | * TODO Laws: 13 | * traverse' ≡ wander traverse 14 | * traverse' . rmap f ≡ rmap (fmap f) . traverse' 15 | * traverse' . traverse' ≡ dimap Compose getCompose . traverse' 16 | * dimap Identity runIdentity . traverse' ≡ id 17 | */ 18 | trait Traversing[P[_, _]] extends Choice[P] with Strong[P] { 19 | 20 | def traverse[A, B, F[_]]( 21 | pab: P[A, B] 22 | )(implicit FT: Traverse[F]): P[F[A], F[B]] = 23 | ??? // TODO implement using wander 24 | 25 | def wander[A, B, S, T, F[_]](f: A => F[B], s: S, ft: F[T], pab: P[A, B])( 26 | AF: Applicative[F] 27 | ): P[S, T] = 28 | ??? // TODO implement using walk 29 | } 30 | 31 | /* 32 | trait Traversing[=>:[_,_]] extends Profunctor[=>:] { 33 | def strechL[A,B,C](pab: A =>: B): (List[A],C) =>: (List[B],C) // TODO replace List by Traverse 34 | def strechR[A,B,C](pab: A =>: B): (C, List[A]) =>: (C,List[B]) 35 | } 36 | */ 37 | 38 | object TraversingInstances { 39 | // TODO Traversing Profunctor instances https://hackage.haskell.org/package/profunctors-5.3/docs/Data-Profunctor-Traversing.html#i:Traversing 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/educational/collections/ConsList.scala: -------------------------------------------------------------------------------- 1 | package educational.collections 2 | 3 | sealed trait ConsList[+T] 4 | case object ConsNil extends ConsList[Nothing] 5 | case class Cons[T](h: T, t: ConsList[T]) extends ConsList[T] 6 | -------------------------------------------------------------------------------- /src/main/scala/educational/collections/FunCol.scala: -------------------------------------------------------------------------------- 1 | package educational.collections 2 | 3 | case class MultiSetFun[-A](exists: A => Int) 4 | case class SetFu[-A](exists: A => Boolean) 5 | case class ListFun[+A](get: Int => Option[A], exists: Int => Boolean) 6 | case class MapFun[-I, +A](get: I => Option[A], exists: I => Boolean) 7 | -------------------------------------------------------------------------------- /src/main/scala/educational/collections/HeadNel.scala: -------------------------------------------------------------------------------- 1 | package educational.collections 2 | 3 | import educational.category_theory.Comonad 4 | 5 | sealed trait AbstractNel[+A] { 6 | def head: A 7 | 8 | def tailOpt: Option[AbstractNel[A]] = 9 | this match { 10 | case HeadNel(_, tail) => Some(tail) 11 | case _ => None 12 | } 13 | } 14 | 15 | case class TailNel[A](head: A) extends AbstractNel[A] { 16 | def +(other: AbstractNel[A]): AbstractNel[A] = HeadNel(head, other) 17 | } 18 | 19 | case class HeadNel[+A](head: A, tail: AbstractNel[A]) extends AbstractNel[A] 20 | 21 | case class Nel[+A](head: A, tail: Nel[A]) 22 | 23 | object NelInstances { 24 | val nelComonad: Comonad[Nel] = new Comonad[Nel] { 25 | def extract[A](w: Nel[A]): A = ??? 26 | override def duplicate[A](wa: Nel[A]): Nel[Nel[A]] = ??? 27 | def map[A, B](fa: Nel[A])(f: A => B): Nel[B] = ??? 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/educational/collections/RoseTree.scala: -------------------------------------------------------------------------------- 1 | package educational.collections 2 | 3 | import educational.category_theory.Comonad 4 | 5 | case class RoseTree[A](tip: A, subTrees: List[RoseTree[A]] = Nil) 6 | 7 | object RoseTreeInstances { 8 | 9 | val roseTreeComonad: Comonad[RoseTree] = new Comonad[RoseTree] { 10 | def extract[A](na: RoseTree[A]): A = na.tip 11 | override def duplicate[A](na: RoseTree[A]): RoseTree[RoseTree[A]] = 12 | RoseTree(na, na.subTrees.map(duplicate)) 13 | def map[A, B](na: RoseTree[A])(f: A => B): RoseTree[B] = 14 | RoseTree(f(na.tip), na.subTrees.map(s => map(s)(f))) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/educational/collections/Tree.scala: -------------------------------------------------------------------------------- 1 | package educational.collections 2 | 3 | import cats.{Functor, Monad, StackSafeMonad} 4 | 5 | /** data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving (Eq, Show) */ 6 | sealed trait Tree[+T] 7 | final case class Leaf[T](value: T) extends Tree[T] 8 | final case class Branch[T](left: Tree[T], right: Tree[T]) extends Tree[T] 9 | 10 | object TreeInstances { 11 | 12 | /** 13 | * Haskell: 14 | * 15 | * instance Functor Tree where 16 | * fmap :: (a -> b) -> f a -> f b 17 | * fmap f (Leaf a) = Leaf (f a) 18 | * fmap f (Branch l r) = Branch (fmap f l) (fmap f r) 19 | */ 20 | implicit val treeFunctor: Functor[Tree] = new Functor[Tree] { 21 | override def map[A, B](fa: Tree[A])(f: A => B): Tree[B] = 22 | fa match { 23 | case Leaf(v) => Leaf(f(v)) 24 | case Branch(l, r) => Branch(map(l)(f), map(r)(f)) 25 | } 26 | } 27 | 28 | implicit val instance: Monad[Tree] = new StackSafeMonad[Tree] { 29 | def flatMap[A, B](fa: Tree[A])(f: A => Tree[B]): Tree[B] = 30 | fa match { 31 | case Leaf(v) => f(v) 32 | case Branch(l: Tree[A], r: Tree[B]) => 33 | val l2: Tree[B] = flatMap(l)(f) 34 | val r2: Tree[B] = flatMap(r)(f) 35 | Branch(l2, r2) 36 | } 37 | 38 | def pure[A](x: A): Tree[A] = Leaf(x) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/educational/collections/ZipList.scala: -------------------------------------------------------------------------------- 1 | package educational.collections 2 | 3 | import educational.category_theory.Applicative 4 | 5 | /** 6 | * ZipList is a List that when mapped over do not explode result like regular list 7 | * but pick the shortest list and combine them. 8 | * 9 | * It is example of Applicative that is not a Monad 10 | * 11 | * https://en.wikibooks.org/wiki/Haskell/Applicative_functors 12 | */ 13 | case class ZipList[A](getZipList: List[A]) { // TODO should this be FiniteStream ? 14 | def zipWith[B, C](f: (A, B) => C, other: ZipList[B]): ZipList[C] = 15 | ZipList( 16 | getZipList 17 | .zip(other.getZipList) 18 | .map(f.tupled) 19 | ) 20 | } 21 | 22 | object ZipListInstances { 23 | val applicativeZipList: Applicative[ZipList] = new Applicative[ZipList] { 24 | def pure[A](value: A): ZipList[A] = 25 | ??? // TODO how to add invinite ZipList ? 26 | def ap[A, B](ff: ZipList[A => B])(fa: ZipList[A]): ZipList[B] = { 27 | def aply: (A => B, A) => B = { case (f, a) => f(a) } 28 | ff.zipWith(aply, fa) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/CoKleisli.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.two.profunctor.Profunctor 5 | 6 | case class CoKleisli[F[_], A, B](run: F[A] => B) 7 | 8 | object CoKleisliInstances { 9 | def coKleisliProfunctor[F[_]](implicit 10 | FF: Functor[F] 11 | ): Profunctor[CoKleisli[F, *, *]] = 12 | new Profunctor[CoKleisli[F, *, *]] { 13 | def dimap[S, T, A, B]( 14 | pab: CoKleisli[F, A, B] 15 | )(ab: S => A, cd: B => T): CoKleisli[F, S, T] = 16 | CoKleisli { FF.lift(ab) andThen (pab.run andThen cd) } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/CoReader.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.Comonad 4 | 5 | case class CoReader[R, A]( 6 | extract: A, 7 | ask: R 8 | ) // wrap value A with some context R 9 | 10 | object CoReaderInstances { 11 | def coReaderComonad[R]: Comonad[CoReader[R, *]] = 12 | new Comonad[CoReader[R, *]] { 13 | def map[A, B](x: CoReader[R, A])(f: A => B): CoReader[R, B] = 14 | CoReader(f(x.extract), x.ask) 15 | def extract[A](w: CoReader[R, A]): A = w.extract 16 | override def duplicate[A](wa: CoReader[R, A]): CoReader[R, CoReader[R, A]] = 17 | CoReader(wa, wa.ask) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Cofree.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.{Comonad, Functor} 4 | 5 | case class Cofree[A, F[_]](extract: A, sub: F[Cofree[A, F]])(implicit 6 | functor: Functor[F] 7 | ) { 8 | def map[B](f: A => B): Cofree[B, F] = 9 | Cofree(f(extract), functor.map(sub)(_.map(f))) 10 | def duplicate: Cofree[Cofree[A, F], F] = 11 | Cofree(this, functor.map(sub)(_.duplicate)) 12 | def extend[B](f: Cofree[A, F] => B): Cofree[B, F] = 13 | duplicate.map(f) // coKleisi composition 14 | } 15 | 16 | object Cofree { 17 | 18 | implicit def CofreeComonad[F[_]]: Comonad[Cofree[*, F]] = new Comonad[Cofree[*, F]] { 19 | override def extract[A](w: Cofree[A, F]): A = w.extract 20 | override def duplicate[A](wa: Cofree[A, F]): Cofree[Cofree[A, F], F] = wa.duplicate 21 | override def map[A, B](fa: Cofree[A, F])(f: A => B): Cofree[B, F] = fa.map(f) 22 | } 23 | } 24 | 25 | case class CofreeList[A](extract: A, sub: List[CofreeList[A]]) 26 | 27 | object CofreeList { 28 | 29 | def cofreeListComonad: Comonad[CofreeList] = 30 | new Comonad[CofreeList] { 31 | def map[A, B](ca: CofreeList[A])(f: A => B): CofreeList[B] = 32 | CofreeList(f(ca.extract), ca.sub.map(i => map(i)(f))) 33 | def extract[A](ca: CofreeList[A]): A = ca.extract 34 | override def duplicate[A](ca: CofreeList[A]): CofreeList[CofreeList[A]] = 35 | CofreeList(ca, ca.sub.map(duplicate)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Comparison.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.contra.Contravariant 4 | 5 | sealed trait Ordering 6 | case object LT extends Ordering 7 | case object EQ extends Ordering 8 | case object GT extends Ordering 9 | 10 | final case class Comparison[A]( 11 | runComparison: (A, A) => Ordering 12 | ) 13 | 14 | object Comparison { 15 | 16 | val comparisonContravariant: Contravariant[Comparison] = 17 | new Contravariant[Comparison] { 18 | def contramap[A, B](fa: Comparison[A])(f: B => A): Comparison[B] = { 19 | Comparison[B]((lhs, rhs) => fa.runComparison(f(lhs), f(rhs))) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Compose.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | case class Compose[F[_], G[_], A](v: F[G[A]]) 4 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Const.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.two.bifunctors.Bifunctor 5 | 6 | final case class Const[A, B](a: A) 7 | 8 | object ConstInstances { 9 | def constFunctorR[X]: Functor[Const[X, *]] = 10 | new Functor[Const[X, *]] { 11 | override def map[A, B](fa: Const[X, A])(f: A => B): Const[X, B] = 12 | Const[X, B](fa.a) 13 | } 14 | 15 | def constFunctorL[X]: Functor[Const[*, X]] = 16 | new Functor[Const[*, X]] { 17 | override def map[A, B](fa: Const[A, X])(f: A => B): Const[B, X] = 18 | Const(f(fa.a)) 19 | } 20 | 21 | val constBifunctor: Bifunctor[Const] = new Bifunctor[Const] { 22 | override def bimap[A, B, C, D]( 23 | fa: Const[A, C] 24 | )(f: A => B, g: C => D): Const[B, D] = 25 | Const[B, D](f(fa.a)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Fix.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.Functor 4 | 5 | // Matryoshka: https://github.com/precog/matryoshka/blob/master/core/shared/src/main/scala/matryoshka/data/Fix.scala 6 | // Y combinator on type level 7 | case class Fix[F[_]](unFix: F[Fix[F]]) 8 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/FreeApp.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.Applicative 4 | 5 | /** 6 | * Free Applicative Functor defined based on Haskell implementation 7 | * http://hackage.haskell.org/package/free/docs/Control-Applicative-Free.html 8 | */ 9 | sealed trait FreeAp[F[_], A] 10 | case class Pure[F[_], A](a: A) extends FreeAp[F, A] 11 | case class SuspendAp[F[_], B](f: Value[F, B]) extends FreeAp[F, B] 12 | 13 | trait Value[F[_], B] { 14 | type A 15 | val fa: F[A] 16 | val fab: FreeAp[F, A => B] 17 | } 18 | 19 | object Value { 20 | def apply[F[_], B, AA](faa: F[AA], faab: FreeAp[F, AA => B]): Value[F, B] = 21 | new Value[F, B] { 22 | type A = AA 23 | val fa: F[AA] = faa 24 | val fab: FreeAp[F, A => B] = faab 25 | } 26 | } 27 | 28 | object FreeApInstanes { 29 | def freeAppApplicative[F[_]]: Applicative[FreeAp[F, *]] = 30 | new Applicative[FreeAp[F, *]] { 31 | override def map[X, Y](fa: FreeAp[F, X])(f: X => Y): FreeAp[F, Y] = 32 | fa match { 33 | case Pure(a) => Pure(f(a)) 34 | case SuspendAp(v) => 35 | SuspendAp( 36 | Value(v.fa, map(v.fab)(_ andThen f)) 37 | ) 38 | } 39 | override def pure[A](a: A): FreeAp[F, A] = Pure(a) 40 | override def ap[A, B]( 41 | ff: FreeAp[F, A => B] 42 | )(fa: FreeAp[F, A]): FreeAp[F, B] = 43 | fa match { 44 | case Pure(a) => ??? // TODO 45 | case SuspendAp(v) => ??? // TODO 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/FreeM.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.{Functor, Monad} 4 | 5 | /* from: http://blog.higher-order.com/blog/2015/10/04/scala-comonad-tutorial-part-2/ */ 6 | sealed trait FreeM[F[_], A] 7 | final case class Return[F[_], A](a: A) extends FreeM[F, A] 8 | final case class Suspend[F[_], A](s: F[FreeM[F, A]]) extends FreeM[F, A] 9 | 10 | object FreeM { 11 | def freeMonad[F[_]](implicit FF: Functor[F]): Monad[FreeM[F, *]] = 12 | new Monad[FreeM[F, *]] { 13 | def flatMap[A, B](ma: FreeM[F, A])(f: A => FreeM[F, B]): FreeM[F, B] = 14 | ma match { 15 | case Return(a) => f(a) 16 | case Suspend(m) => 17 | Suspend { 18 | def ff: FreeM[F, A] => FreeM[F, B] = x => flatMap(x)(f) 19 | FF.map(m)(ff) 20 | } 21 | } 22 | def pure[A](a: A): FreeM[F, A] = Return(a) 23 | } 24 | } 25 | 26 | // from https://www.youtube.com/watch?v=M258zVn4m2M 27 | object free2 { 28 | sealed trait Free[F[_], A] 29 | case class Return[F[_], A](a: A) extends Free[F, A] 30 | case class Bind[F[_], I, A](i: F[I], k: I => Free[F, A]) extends Free[F, A] 31 | } 32 | 33 | object free3 { 34 | // https://www.youtube.com/watch?v=7xSfLPD6tiQ 35 | // Pure Functional Database Programming with Fixpoint Types — Rob Norris 36 | case class Free[F[_], A](resume: Either[A, F[Free[F, A]]]) 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Id.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.Comonad 4 | 5 | case class Id[A](value: A) 6 | 7 | object IdInstances { 8 | val idComonad: Comonad[Id] = new Comonad[Id] { 9 | def map[A, B](x: Id[A])(f: A => B): Id[B] = Id(f(x.value)) 10 | def extract[A](w: Id[A]): A = w.value 11 | override def duplicate[A](wa: Id[A]): Id[Id[A]] = Id(wa) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Kleisli.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.two.profunctor.Profunctor 5 | 6 | case class Kleisli[F[_], A, B](run: A => F[B]) 7 | 8 | object KleisliInstances { 9 | 10 | // TODO Haskell wants Monad https://hackage.haskell.org/package/profunctors-5.5.1/docs/src/Data.Profunctor.Unsafe.html#line-192 11 | def kleisliProfunctor[M[_]](MM: Functor[M]): Profunctor[Kleisli[M, *, *]] = 12 | new Profunctor[Kleisli[M, *, *]] { 13 | def dimap[S, T, A, B]( 14 | pab: Kleisli[M, A, B] 15 | )(f: S => A, g: B => T): Kleisli[M, S, T] = 16 | Kleisli { s => 17 | MM.map((pab.run compose f)(s))(g) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Maybe.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.{Applicative, Functor} 4 | 5 | sealed trait Maybe[+A] 6 | case class MSome[A](a: A) extends Maybe[A] 7 | case object MNone extends Maybe[Nothing] 8 | 9 | object MaybeInstances { 10 | 11 | val optionFunctor: Functor[Maybe] = new Functor[Maybe] { 12 | def map[A, B](opt: Maybe[A])(f: A => B): Maybe[B] = 13 | opt match { 14 | case MNone => MNone 15 | case MSome(v) => MSome(f(v)) 16 | } 17 | } 18 | 19 | val optionApplicative: Applicative[Maybe] = new Applicative[Maybe] { 20 | def pure[A](a: A): Maybe[A] = MSome(a) 21 | def ap[A, B](ff: Maybe[A => B])(fa: Maybe[A]): Maybe[B] = 22 | (ff, fa) match { 23 | case (MSome(fab), MSome(faa)) => MSome(fab(faa)) 24 | case (_, _) => MNone 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Nu.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | trait Nu[F[_]] { 4 | type A 5 | val a: A 6 | val unNu: A => F[A] 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Op.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | case class Op[K[_, _], A, B](unOp: K[B, A]) 4 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Predicate.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.contra.Contravariant 4 | 5 | final case class Predicate[-A](fun: A => Boolean) 6 | 7 | object PredicateInstances { 8 | 9 | val predicateContravariant: Contravariant[Predicate] = 10 | new Contravariant[Predicate] { 11 | def contramap[A, B](pred: Predicate[A])(fba: B => A): Predicate[B] = 12 | Predicate[B](fba andThen pred.fun) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/RIO.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.abstract_algebra.Monoid 4 | import educational.category_theory.contra.{Contravariant, Divide} 5 | import educational.category_theory.{Functor, Monad} 6 | import educational.category_theory.two.profunctor.Profunctor 7 | 8 | /** 9 | * Profunctor IO (Reader + IO) models input and output 10 | */ 11 | case class RIO[R, A](run: R => A) 12 | 13 | object RIOInstances { 14 | def rioFunctor[R, E]: Functor[RIO[R, *]] = 15 | new Functor[RIO[R, *]] { 16 | def map[A, B](fa: RIO[R, A])(f: A => B): RIO[R, B] = RIO(fa.run andThen f) 17 | } 18 | 19 | trait RIOMonad[R, E] extends Monad[RIO[R, *]] { 20 | def pure[A](a: A): RIO[R, A] = RIO(_ => a) 21 | def flatMap[A, B](fa: RIO[R, A])(f: A => RIO[R, B]): RIO[R, B] = 22 | RIO(r => { 23 | val rrb: RIO[R, RIO[R, B]] = map(fa)(f) 24 | rrb.run(r).run(r) 25 | }) 26 | } 27 | 28 | def rioMonad[R, E]: Monad[RIO[R, *]] = new RIOMonad[R, E] {} 29 | 30 | def rioContravariant[A]: Contravariant[RIO[*, A]] = 31 | new Contravariant[RIO[*, A]] { 32 | def contramap[R, RR](fa: RIO[R, A])(f: RR => R): RIO[RR, A] = 33 | RIO { rr => fa.run(f(rr)) } 34 | } 35 | 36 | def rioDivide[F[_], A](implicit MA: Monoid[A]): Divide[RIO[*, A]] = 37 | new Divide[RIO[*, A]] { 38 | def divide[RR, B, C]( 39 | f: RR => (B, C), 40 | fb: RIO[B, A], 41 | fc: RIO[C, A] 42 | ): RIO[RR, A] = { 43 | RIO[RR, A]((rr: RR) => { 44 | val (b, c) = f(rr) 45 | MA.combine(fb.run(b), fc.run(c)) 46 | }) 47 | } 48 | 49 | override def contramap[R, RR](fa: RIO[R, A])(f: RR => R): RIO[RR, A] = 50 | RIO { rr => fa.run(f(rr)) } 51 | } 52 | 53 | def rioProfunctor[E]: Profunctor[RIO[*, *]] = 54 | new Profunctor[RIO[*, *]] { 55 | override def dimap[S, T, A, B]( 56 | pab: RIO[A, B] 57 | )(ab: S => A, cd: B => T): RIO[S, T] = 58 | RIO((ab andThen pab.run) andThen cd) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Reader.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.contra.Contravariant 4 | import educational.category_theory.{Functor, Monad} 5 | 6 | case class Reader[-R, +V](run: R => V) 7 | 8 | object Reader { 9 | 10 | def readerFunctor[C]: Functor[Reader[C, *]] = 11 | new Functor[Reader[C, *]] { 12 | def map[A, B](x: Reader[C, A])(f: A => B): Reader[C, B] = 13 | Reader(x.run andThen f) 14 | } 15 | 16 | def readerMonad[C]: Monad[Reader[C, *]] = 17 | new Monad[Reader[C, *]] { 18 | override def map[A, B](x: Reader[C, A])(f: A => B): Reader[C, B] = 19 | Reader(x.run andThen f) 20 | def pure[A](a: A): Reader[C, A] = new Reader(_ => a) 21 | def flatMap[A, B](ma: Reader[C, A])(f: A => Reader[C, B]): Reader[C, B] = 22 | ??? // TODO 23 | } 24 | 25 | def readerContra[V]: Contravariant[Reader[*, V]] = 26 | new Contravariant[Reader[*, V]] { 27 | def contramap[A, B](fa: Reader[A, V])(f: B => A): Reader[B, V] = 28 | Reader(f andThen fa.run) 29 | } 30 | } 31 | 32 | /* 33 | case class Reader[-In, +R](run: In => R) { 34 | 35 | def map[R2](f: R => R2): Reader[In, R2] = 36 | Reader(run andThen f) 37 | 38 | def flatMap[R2, In2 <: In](f: R => Reader[In2, R2]): Reader[In2, R2] = 39 | Reader(x => f(run(x)).run(x)) 40 | } 41 | */ 42 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Show.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | trait Show[A] { 4 | def asString(a: A): String 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/State.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.category_theory.Monad 4 | 5 | case class State[S, A](runState: S => (A, S)) 6 | 7 | object State { 8 | 9 | implicit def stateMonad[S]: Monad[State[S, *]] = 10 | new Monad[State[S, *]] { 11 | 12 | def pure[A](a: A): State[S, A] = State(s => (a, s)) 13 | 14 | def flatMap[A, B](ma: State[S, A])(f: A => State[S, B]): State[S, B] = 15 | State[S, B](s => { 16 | val (a: A, s2: S) = ma.runState(s) 17 | f(a).runState(s2) 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/These.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | /** 4 | * Non exclusive two values 5 | * 6 | * (A + B + AB) 7 | */ 8 | 9 | sealed trait These[A, B] 10 | case class This[A, B](a: A) extends These[A, B] 11 | case class That[A, B](b: B) extends These[A, B] 12 | case class Those[A, B](a: A, b: B) extends These[A, B] 13 | 14 | object These { 15 | // Bitraverse 16 | // Bifoldable 17 | // Bifunctor 18 | // Bicrosswalk 19 | // Functor 20 | // Applicative (with Semigroup) 21 | // Foldable 22 | // Traversable 23 | // Apply (with Semigroup) 24 | // Crosswalk 25 | // Bind (with Semigroup) 26 | // Semigroup (with Semigroup a, Semigroup b) ? 27 | // Rep 28 | 29 | // TODO helpers: https://hackage.haskell.org/package/these/docs/Data-These.html 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Thunk.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | case class Thunk[A](run: () => A) 4 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Validated.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | import educational.collections.HeadNel 4 | 5 | object Validated { 6 | sealed trait Validated[+A, +E] 7 | case class SuccessV[A](result: A) extends Validated[A, Nothing] 8 | case class ErrorV[E](error: E) extends Validated[Nothing, E] 9 | } 10 | 11 | object ValidatedNel { 12 | sealed trait ValidatedNel[+A, +E] 13 | case class SuccessVN[A](result: A) extends ValidatedNel[A, Nothing] 14 | case class ErrorVN[E](error: E) extends ValidatedNel[Nothing, E] 15 | case class ErrorsVN[E](errors: HeadNel[E]) extends ValidatedNel[Nothing, E] 16 | } 17 | 18 | object ValidatedUnified { 19 | sealed trait Validated[+A, +E] 20 | sealed trait ValidatedNel[+A, +E] 21 | case class SuccessV[A](result: A) 22 | extends ValidatedNel[A, Nothing] 23 | with Validated[A, Nothing] 24 | case class ErrorV[E](error: E) 25 | extends ValidatedNel[Nothing, E] 26 | with Validated[Nothing, E] 27 | case class ErrorsV[E](error: E, errors: HeadNel[E]) 28 | extends ValidatedNel[Nothing, E] 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/educational/data/Writer.scala: -------------------------------------------------------------------------------- 1 | package educational.data 2 | 3 | final case class Writer[R, A](runWriter: A => (R, A)) 4 | -------------------------------------------------------------------------------- /src/main/scala/educational/optics/Iso.scala: -------------------------------------------------------------------------------- 1 | package educational.optics 2 | 3 | trait ~[A,B] { 4 | def to: B => A 5 | def from: A => B 6 | } 7 | 8 | object Iso { 9 | type Equivalence[A, B] = ~[A, B] 10 | type Bijection[A, B] = ~[A, B] 11 | } 12 | 13 | case class Iso[S,A](from: S => A, to: A => S) { self => // Adapter 14 | def modifyB(f: A => A): S => S = 15 | (from andThen f) andThen to 16 | // def andThen[B](other: Iso[A,B]): Iso[S,B] = Iso[S,B]( 17 | // self.from andThen other.from, 18 | // 19 | // ) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/main/scala/educational/optics/Lens.scala: -------------------------------------------------------------------------------- 1 | package educational.optics 2 | 3 | case class Lens[+A,-B,-S,+T]( 4 | view: S => A, 5 | update: (B,S) => T 6 | ) 7 | 8 | object LensExamples { 9 | 10 | def tupleFoucsFirst[A,B,C]: Lens[A, B, (A,C), (B,C)] = { 11 | def getFst: ((A,C)) => A = { case (a, _) => a } 12 | def overrideFst: (B,(A,C)) => (B,C) = { case (b, (_,c)) => (b,c) } 13 | Lens[A, B, (A,C), (B,C)](getFst, overrideFst) 14 | } 15 | 16 | def tupleFoucsSecond[A,B,C]: Lens[A, B, (A,C), (B,C)] = { 17 | def getFst: ((A,C)) => A = { case (a, _) => a } 18 | def overrideFst: (B, (A,C)) => (B,C) = { case (b, (_, c)) => (b, c) } 19 | Lens[A, B, (A,C), (B,C)](getFst, overrideFst) 20 | } 21 | 22 | // monomorphic - types have to be the same for A,B and S,T 23 | val signNonNegative: Lens[Boolean, Boolean, Int, Int] = { 24 | def isNonNegative: Int => Boolean = _ >= 0 25 | def asNonNegative: (Boolean,Int) => Int = { case(b,n) => 26 | if(b) Math.abs(n) 27 | else -Math.abs(n) 28 | } 29 | 30 | Lens[Boolean, Boolean, Int, Int](isNonNegative, asNonNegative) 31 | } 32 | 33 | // Nat and isOdd would work 34 | // Nat and isPrime would be tricky (enforcing property could be hard) 35 | 36 | // compsing lens 37 | // def tupleInnerFirst[A,B,C,D]: Lens[A, B, ((A,C),D), ((B,C),D)] = { 38 | // def overrideFst: (B,(A,C)) => (B,C) = { case (b, (_,c)) => (b,c) } 39 | // Lens[A, B, ((A,C),D), ((B,C),D)](tupleFoucsFirst.view andThen tupleFoucsFirst.view, overrideFst) 40 | // } 41 | 42 | // TODO finish + check if we use Option instead of Either if that helps? 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/educational/optics/Prism.scala: -------------------------------------------------------------------------------- 1 | package educational.optics 2 | 3 | case class Prism[+A,-B,-S,+T]( 4 | matsh: S => Either[T,A], 5 | build: B => T 6 | ) 7 | 8 | object PrismExamples { 9 | 10 | def optionPrism[A,B]: Prism[A,B,Option[A],Option[B]] = { 11 | val upcast: B => Option[B] = Some.apply 12 | val downCast: Option[A] => Either[Option[B],A] = { 13 | case Some(a) => Right(a) 14 | case None => Left(None) 15 | } 16 | Prism[A,B,Option[A],Option[B]]( 17 | matsh = downCast, 18 | build = upcast 19 | ) 20 | } 21 | 22 | def intToDoublePrism: Prism[Int,Int,Double,Double] = { 23 | val asDouble: Int => Double = _.toDouble 24 | val asInt: Double => Either[Double,Int] = d => { 25 | if(d.isValidInt) Right(d.toInt) 26 | else Left(d) 27 | } 28 | Prism[Int,Int,Double,Double]( 29 | matsh = asInt, 30 | build = asDouble 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/Const.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.two.bifunctors.Bifunctor 5 | 6 | object Const { 7 | type Const[A, B] = A 8 | 9 | def constFunctorR[A]: Functor[Const[A, *]] = 10 | new Functor[Const[A, *]] { 11 | def map[B, BB](fa: Const[A, B])(f: B => BB): Const[A, BB] = fa 12 | } 13 | 14 | def constFunctorL[B]: Functor[Const[*, B]] = 15 | new Functor[Const[*, B]] { 16 | override def map[A, AA](fa: Const[A, B])(f: A => AA): Const[AA, B] = f(fa) 17 | } 18 | 19 | val constBifunctor: Bifunctor[Const] = new Bifunctor[Const] { 20 | def bimap[A, AA, B, BB]( 21 | fa: Const[A, B] 22 | )(f: A => AA, g: B => BB): Const[AA, BB] = f(fa) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/Id.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | import educational.category_theory.{Comonad, Monad} 4 | 5 | object Id { 6 | type Id[+A] = A 7 | 8 | trait ComonadAux[W[_]] extends Comonad[W] { 9 | override def duplicate[A](wa: W[A]): W[W[A]] = extend(wa)(identity) 10 | } 11 | 12 | implicit val travId = new Monad[Id] with ComonadAux[Id] { 13 | override def pure[A](value: A): Id[A] = value 14 | override def ap[A, B](ff: Id[A => B])(fa: Id[A]): Id[B] = ff(fa) 15 | override def map[A, B](x: Id[A])(f: A => B): Id[B] = f(x) 16 | override def flatMap[A, B](ma: Id[A])(f: A => Id[B]): Id[B] = f(ma) 17 | override def extract[A](w: Id[A]): A = w 18 | override def extend[A, B](w: Id[A])(f: Id[A] => B): Id[B] = f(w) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/NaturalTransformation.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | object NaturalTransformation { 4 | type ~>[F[_], G[_]] = F[_] => G[_] 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/Op.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | object Op { 4 | type Op[A, B] = B => A 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/Reader.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | import educational.category_theory.Functor 4 | import educational.category_theory.contra.Contravariant 5 | 6 | object Reader { 7 | type Reader[-R, +A] = R => A 8 | 9 | def readerFunctor[C]: Functor[Reader[C, *]] = 10 | new Functor[Reader[C, *]] { 11 | def map[A, B](ca: Reader[C, A])(ab: A => B): Reader[C, B] = ca andThen ab 12 | } 13 | 14 | def readerContra[C]: Contravariant[Reader[*, C]] = 15 | new Contravariant[Reader[*, C]] { 16 | def contramap[A, B](fa: Reader[A, C])(ba: B => A): Reader[B, C] = 17 | ba andThen fa 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/State.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | import educational.category_theory.{Monad, MonoidK, StateMonad} 4 | 5 | object State { 6 | type State[S,A] = S => (A,S) 7 | 8 | trait StateAsMonad[S] extends Monad[State[S,*]] { 9 | def flatMap[A, B](st: S => (A,S))(f: A => S => (B,S)): S => (B,S) = 10 | s => { 11 | val (v, s2) = st(s) 12 | f(v)(s2) 13 | } 14 | def pure[A](v: A): State[S,A] = s => (v, s) 15 | } 16 | 17 | implicit def stateMonad[S]: Monad[State[S,*]] = new StateAsMonad[S] {} 18 | 19 | implicit def statStateMonad[S]: StateMonad[State[S,*],S] = new StateMonad[State[S,*],S] with StateAsMonad[S] { 20 | def update: (S => S) => State[S, S] = f => s => (s, f(s)) 21 | } 22 | 23 | // parametrised state-transformer monad 24 | type StateM[F[_],S,A] = S => F[(A,S)] 25 | 26 | def statemStateMonad[F[_],S](implicit FM: Monad[F]): StateMonad[StateM[F,S,*],S] = new StateMonad[StateM[F,S,*],S] { 27 | override def update: (S => S) => StateM[F, S, S] = f => s => FM.pure((s, f(s))) 28 | 29 | def flatMap[A, B](stm: StateM[F, S, A])(f: A => StateM[F, S, B]): StateM[F, S, B] = s => { 30 | val f1: F[(A, S)] = stm(s) 31 | FM.flatMap(f1){ case (v,s2) => 32 | f(v)(s2) 33 | } 34 | } 35 | def pure[A](a: A): StateM[F, S, A] = s => FM.pure((a,s)) 36 | } 37 | 38 | implicit def statemMonoidK[F[_],S](implicit FM: MonoidK[F]): MonoidK[StateM[F,S,*]] = new MonoidK[StateM[F,S,*]] { 39 | override def empty[A]: StateM[F, S, A] = _ => FM.empty[(A,S)] 40 | override def combine[A](lhs: StateM[F, S, A], rhs: StateM[F, S, A]): StateM[F, S, A] = 41 | s => FM.combine(lhs(s),rhs(s)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/Store.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | import educational.category_theory.Comonad 4 | 5 | // store comonad == costate comonad 6 | object Store { 7 | type Store[S, A] = (S => A, S) 8 | 9 | def storeComonad[S]: Comonad[Store[S,*]] = new Comonad[Store[S,*]] { 10 | override def extract[A](w: (S => A, S)): A = ??? // { case (f, s) => f(s) } 11 | override def duplicate[A](wa: (S => A, S)): (S => (S => A, S), S) = ??? 12 | override def map[A, B](fa: (S => A, S))(f: A => B): (S => B, S) = ??? 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/Thunk.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | import cats.Functor 4 | 5 | class Thunk { 6 | type Thunk[+R] = () => R 7 | 8 | val thunkFunctor: Functor[Thunk] = new Functor[Thunk] { 9 | def map[A, B](fa: Thunk[A])(f: A => B): Thunk[B] = () => f(fa()) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/educational/types/Void.scala: -------------------------------------------------------------------------------- 1 | package educational.types 2 | 3 | sealed trait Void { 4 | def absurd[A]: A 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/examples/ArrowBasicOps.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import cats.arrow.Arrow 4 | import cats.syntax.all._ 5 | 6 | /** 7 | * Examples inspired by Eugene Yokota learning Scalaz 8 | * http://eed3si9n.com/learning-scalaz/Arrow.html 9 | */ 10 | object ArrowBasicOps extends App { // TODO change into unit tests 11 | val inc: Int => Int = _ + 1 12 | val inc2: Int => Int = _ * 2 13 | val len: String => Int = _.length 14 | val times10: Int => Int = _ * 10 15 | val comb: (Int, Int) => String = _.toString + _.toString 16 | 17 | val a1 = Arrow[Function1].lift(times10) 18 | val a2 = Arrow[Function1].lift(inc) 19 | 20 | val r1 = Arrow[Function1].andThen(len, inc)("foo") // <<< 21 | val r2 = Arrow[Function1].compose(inc, len)("foo") // >>> 22 | val r3 = a1.dimap(inc)(inc2)(10) 23 | val r4 = a1.split(inc)((50, 60)) // *** 24 | val r5 = Arrow[Function1].split(times10, inc)((50, 60)) 25 | 26 | println(List(r1, r2, r3, r4, r5).mkString(", ")) 27 | println((a1 >>> a2)(5)) 28 | println((a1 <<< a2)(5)) 29 | println(a1.second((1, 2))) 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/examples/ArrowFizBuzz.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import cats.arrow.Arrow 4 | 5 | /** 6 | * Translated from post by geophf: 7 | * http://logicaltypes.blogspot.com/2014/02/arrow-is-spelt-fizz-buzz.html 8 | * import Control.Arrow 9 | * 10 | * predfb :: String -> Int -> Int -> Either String Int 11 | * predfb str modulo x 12 | * | x `mod` modulo == 0 = Left str 13 | * | otherwise = Right x 14 | * 15 | * fizz = predfb "fizz" 3 16 | * buzz = predfb "buzz" 5 17 | * 18 | * fbprinter :: (Either String Int, Either String Int) -> String 19 | * fbprinter (Left x, Left y) = x ++ y 20 | * fbprinter (Left x, _) = x 21 | * fbprinter (_, Left y) = y 22 | * fbprinter (Right num, _) = show num 23 | * 24 | * fizzbuzz = [1..100] >>= return . (fizz &&& buzz >>> fbprinter) 25 | */ 26 | object ArrowFizBuzz extends App { // TODO change into unit tests 27 | 28 | def predfb(str: String, modulo: Int)(x: Int): Either[String, Int] = 29 | if (x % modulo == 0) Left(str) else Right(x) 30 | 31 | val fizz: Int => Either[String, Int] = predfb("fizz", 3) 32 | val buzz: Int => Either[String, Int] = predfb("buzz", 5) 33 | 34 | def fbprinter(pair: (Either[String, Int], Either[String, Int])): String = 35 | pair match { 36 | case (Left(x), Left(y)) => x + y 37 | case (Left(x), _) => x 38 | case (_, Left(y)) => y 39 | case (Right(n), _) => s"$n" 40 | } 41 | 42 | // there seem to be no &&& alternative in cats 43 | // there is in Haskell and 44 | // Scalaz: https://github.com/scalaz/scalaz/blob/series/7.3.x/core/src/main/scala/scalaz/Arrow.scala 45 | // 46 | (1 until 100) 47 | .map(v => (v, v)) 48 | .map(Arrow[Function1].split(fizz, buzz)) 49 | .map(fbprinter) 50 | .foreach(println) 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/examples/BifunctorSimpleImpl.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | object BifunctorSimpleImpl extends App { // TODO change into unit tests 4 | import educational.category_theory.two.bifunctors.BifunctorInstances._ 5 | 6 | val toUpper: Char => Char = _.toUpper 7 | val add1: Int => Int = _ + 1 8 | println(tupleBifunctor.bimap(('j', 3))(toUpper, add1)) 9 | println(eitherBifunctor.bimap(Left('j'))(toUpper, add1)) 10 | println(eitherBifunctor.bimap(Right(3))(toUpper, add1)) 11 | 12 | println(tupleBifunctor.mapLeft(('j', 3))(toUpper)) 13 | println(eitherBifunctor.mapLeft(Left('j'))(toUpper)) 14 | 15 | println(tupleBifunctor.map(('j', 41))(add1)) 16 | println(eitherBifunctor.map(Left('j'))(add1)) 17 | println(eitherBifunctor.map(Right(41))(add1)) 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/examples/CoyonedaExample.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import educational.category_theory.kan.Coyoneda 4 | 5 | object CoyonedaExample extends App { // TODO change into unit tests 6 | val s = Set(1, 2, 3, 4, 5) 7 | val s2 = Coyoneda.liftCoyoneda(s) 8 | val s3 = Coyoneda.coyoFunctor.map(s2)(_ % 2) // mapping over set 9 | println(s3) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/examples/F_Algebras.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | object F_Algebras { 4 | 5 | type Algebra[F[_], A] = F[A] => A 6 | 7 | // F-Algebra for Monoid 8 | sealed trait MonF[+A] 9 | case object MEmpty extends MonF[Nothing] 10 | case class MAppend[A](lhs: A, rhs: A) extends MonF[A] 11 | 12 | // Int is a carrier object 13 | def monoidAlgebraMulti: MonF[Int] => Int = { 14 | case MEmpty => 1 15 | case MAppend(lhs, rhs) => lhs * rhs 16 | } 17 | 18 | def monoidAlgebraAdd: MonF[Int] => Int = { 19 | case MEmpty => 0 20 | case MAppend(lhs, rhs) => lhs + rhs 21 | } 22 | 23 | // F-Algebra for Ring 24 | sealed trait RingF[A] 25 | case object RZero extends RingF[Nothing] 26 | case object ROne extends RingF[Nothing] 27 | case class RNeg[A](a: A) extends RingF[A] 28 | case class RAdd[A](lhs: A, rhs: A) extends RingF[A] 29 | case class RMul[A](lhs: A, rhs: A) extends RingF[A] 30 | 31 | // recursion 32 | sealed trait Expr 33 | case object EZero extends Expr 34 | case object EOne extends Expr 35 | case class ENeg(a: Expr) extends Expr 36 | case class EAdd(lhs: Expr, rhs: Expr) extends Expr 37 | case class EMul(lhs: Expr, rhs: Expr) extends Expr 38 | 39 | // catamorphism 40 | 41 | // natural numbers + fibonacci as algebra 42 | sealed trait NatF[+A] 43 | case object NZero extends NatF[Nothing] 44 | case class NSucc[A](a: A) extends NatF[A] 45 | 46 | def fibAlgebra: NatF[(Int, Int)] => (Int, Int) = { 47 | case NZero => (1, 1) 48 | case NSucc((n, m)) => (n, n + m) 49 | } 50 | 51 | // TODO cata fib ? 52 | 53 | // List is initial algebra for following Functor on A, E is element type 54 | 55 | trait ListF[+E, +A] 56 | case object LNil extends ListF[Nothing, Nothing] 57 | case class LCons[E, A](e: E, a: A) extends ListF[E, A] 58 | 59 | def sumAlg: ListF[Int, Int] => Int = { 60 | case LNil => 0 61 | case LCons(e, a) => e + a 62 | } 63 | 64 | // cata sumAlg is a sum of list 65 | // sum = foldr (l e a -> e + a) 0 66 | // ss fold is a catamorphism for List 67 | // fold is a catamorphism for a fix point of a Functor 68 | 69 | case class StreamF[E, A]( 70 | e: E, 71 | a: A 72 | ) // but this is a trivial functor a product type 73 | // [2..] 74 | def era: StreamF[Int, List[Int]] => List[Int] = { 75 | case StreamF(e, a) => List(e) ++ a.filter(_ % e != 0) 76 | } 77 | 78 | // primes = ana era [2..] 79 | } 80 | -------------------------------------------------------------------------------- /src/main/scala/examples/IntState.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import cats.{Monad, StackSafeMonad} 4 | 5 | object IntState { 6 | 7 | /** Computation that need state and change it */ 8 | type IntState[T] = Int => (T, Int) 9 | 10 | implicit val instance: Monad[IntState] = new StackSafeMonad[IntState] { 11 | def flatMap[A, B](fa: IntState[A])(f: A => IntState[B]): IntState[B] = { 12 | (i: Int) => 13 | val firstResult: (A, Int) = fa(i) 14 | val sndRes: IntState[B] = f(firstResult._1) 15 | sndRes(firstResult._2) 16 | } 17 | 18 | def pure[A](x: A): IntState[A] = i => (x, i) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/examples/KeyValueStoreFreeMonad.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | // example from documentation: http://typelevel.org/cats/datatypes/freemonad.html 4 | 5 | import cats.free.Free 6 | import cats.free.Free.liftF 7 | 8 | // TODO change into unit tests 9 | 10 | sealed trait KVStoreA[A] 11 | final case class Put[T](key: String, value: T) extends KVStoreA[Unit] 12 | final case class Get[T](key: String) extends KVStoreA[Option[T]] 13 | final case class Delete(key: String) extends KVStoreA[Unit] 14 | 15 | object KeyValueStoreFreeMonad extends App { 16 | 17 | // 1 free type 18 | type KVStore[A] = Free[KVStoreA, A] 19 | 20 | // 2 DSL 21 | def put[T](key: String, value: T): KVStore[Unit] = 22 | liftF[KVStoreA, Unit](Put[T](key, value)) 23 | 24 | def get[T](key: String): KVStore[Option[T]] = 25 | liftF[KVStoreA, Option[T]](Get[T](key)) 26 | 27 | def delete(key: String): KVStore[Unit] = liftF(Delete(key)) 28 | 29 | def update[T](key: String, f: T => T): KVStore[Unit] = 30 | for { 31 | vMaybe <- get[T](key) 32 | _ <- vMaybe.map(v => put[T](key, f(v))).getOrElse(Free.pure(())) 33 | } yield () 34 | 35 | // 3 program 36 | def program: KVStore[Option[Int]] = 37 | for { 38 | _ <- put("wild-cats", 2) 39 | _ <- update[Int]("wild-cats", (_ + 12)) 40 | _ <- put("tame-cats", 5) 41 | n <- get[Int]("wild-cats") 42 | _ <- delete("tame-cats") 43 | } yield n 44 | 45 | // 4 compiler, using natural transformation (~> or FunctionK) 46 | // compiler could use Future, Option, Either etc instead of Id 47 | // import cats.arrow.FunctionK 48 | // import cats.{Id, ~>} 49 | // import scala.collection.mutable 50 | 51 | /* TODO it does not compile for me :( 52 | def impureCompiler: KVStoreA ~> Id = 53 | new (KVStoreA ~> Id) { 54 | val kvs = mutable.Map.empty[String, Any] 55 | 56 | def apply[A](fa: KVStoreA[A]): Id[A] = // Id[A] will not compile 57 | fa match { 58 | case Put(key, value) => 59 | println(s"put($key, $value)") 60 | kvs(key) = value 61 | case Get(key) => 62 | println(s"get($key)") 63 | kvs.get(key).map(_.asInstanceOf[A]) 64 | case Delete(key) => 65 | println(s"delete($key)") 66 | kvs.remove(key) 67 | () 68 | } 69 | } 70 | 71 | // 5 run 72 | val result: Option[Int] = program.foldMap(impureCompiler) 73 | println(result) 74 | */ 75 | } 76 | -------------------------------------------------------------------------------- /src/main/scala/examples/KleisliExamples.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import cats.data.Kleisli 4 | 5 | /** 6 | * First example from cats docs: http://typelevel.org/cats/datatypes/kleisli.html 7 | */ 8 | object KleisliExamples extends App { 9 | val twice: Int => Int = _ * 2 10 | val countCats: Int => String = x => if (x == 1) "1 cat" else s"$x cats" 11 | val twiceAsManyCats = twice andThen countCats 12 | println(twiceAsManyCats(1)) 13 | 14 | val parse: String => Option[Int] = s => 15 | try { Some(s.toInt) } 16 | catch { case _: NumberFormatException => None } 17 | val reciprocal: Int => Option[Double] = i => 18 | if (i != 0) Some(1.0 / i) else None 19 | 20 | val parseKleisli = Kleisli(parse) 21 | val reciprocalKleisli = Kleisli(reciprocal) 22 | val parseAndReciprocal = reciprocalKleisli.compose(parseKleisli) 23 | val parseAndReciprocal2 = parseKleisli.andThen(reciprocalKleisli) 24 | 25 | println(parseAndReciprocal("10")) 26 | println(parseAndReciprocal2("10")) 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/examples/ReversingYonedaForList.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import cats.free.Yoneda 4 | 5 | /** 6 | * Example 2 show that you could add some invertable transformation to Yoneda 7 | * 8 | * from excellent talk by: Alissa Pajer 9 | * How Haskell is Changing my Brain - Yay Yoneda 10 | * https://vimeo.com/96639840 11 | */ 12 | 13 | case class ReverseIntListYoneda(list: List[Int]) extends Yoneda[List, Int] { 14 | override def apply[B](f: Int => B): List[B] = list.reverse.map(f) 15 | } 16 | 17 | object ReversingYonedaFOrList extends App { 18 | 19 | val reverseYo = ReverseIntListYoneda(List(1, 2, 3)) 20 | val yoneda2 = reverseYo.map(_ + 3) 21 | println(yoneda2.run) 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/examples/Tree.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import cats.Monad 4 | import cats.syntax.option._ 5 | import educational.collections.{Branch, Leaf, Tree} 6 | import IntState.instance._ 7 | 8 | object Tree { 9 | 10 | /** Number each node (replace current value) 11 | * 12 | * Haskell version: 13 | * 14 | * number :: Num n => Tree t -> n -> (Tree n, n) 15 | * number (Leaf a) s = (Leaf s, s+1) 16 | * number (Branch l r) s = 17 | * let (l1, s1) = number l s 18 | * (r2, s2) = number r s1 19 | * in (Branch l1 r2, s2) 20 | * 21 | * Haskell version using monad: 22 | * 23 | * tick s = (s, s+1) 24 | * 25 | * replaceByIndex2 (Leaf a) = tick >>>= \s -> return2 (Leaf s) 26 | * replaceByIndex2 (Branch l r) = 27 | * replaceByIndex2 l >>>= \l1 -> 28 | * replaceByIndex2 r >>>= \r1 -> 29 | * return2 (Branch l1 r1) 30 | */ 31 | def number1[A](tree: Tree[A], n: Int): (Tree[Int], Int) = { 32 | tree match { 33 | case Leaf(_) => (Leaf(n), n + 1) 34 | case Branch(left, right) => 35 | val (numberedLeft, newN) = number1(left, n) 36 | val (numberedRight, newerN) = number1(right, newN) 37 | (Branch(numberedLeft, numberedRight), newerN) 38 | } 39 | } 40 | 41 | def number[A]: Tree[A] => Int => (Tree[Int], Int) = { 42 | case Branch(l, r) => map2(number(l), number(r))(Branch.apply) 43 | case Leaf(_) => map(tick)(Leaf.apply) 44 | } 45 | 46 | val tick: Int => (Int, Int) = s => (s, s + 1) 47 | 48 | /** Merge two Tree's if they have the same structure 49 | * 50 | * Haskell version: 51 | * 52 | * zipTree :: Tree a -> Tree b -> Maybe (Tree (a, b)) 53 | * zipTree (Leaf a) (Leaf b) = 54 | * Just (Leaf (a,b)) 55 | * zipTree (Branch fstLeft fstRight) (Branch sndLeft sndRight) = 56 | * case zipTree fstLeft sndLeft of 57 | * Nothing -> Nothing 58 | * Just leftJoined -> 59 | * case zipTree fstRight sndRight of 60 | * Nothing -> Nothing 61 | * Just rightJoined -> Just (Branch leftJoined rightJoined) 62 | * zipTree _ _ = Nothing 63 | * 64 | * Haskell version using monads: 65 | * 66 | * zipTree :: Tree a -> Tree b -> Maybe (Tree (a, b)) 67 | * zipTree (Leaf a) (Leaf b) = 68 | * return (Leaf (a,b)) 69 | * zipTree (Branch fstLeft fstRight) (Branch sndLeft sndRight) = 70 | * zipTree fstLeft sndLeft >>= \leftJoined -> 71 | * zipTree fstRight sndRight >>= \rightJoined -> 72 | * return (Branch leftJoined rightJoined) 73 | * zipTree _ _ = Nothing 74 | */ 75 | def zip1[A, B]: (Tree[A], Tree[B]) => Option[Tree[(A, B)]] = { 76 | case (Leaf(a), Leaf(b)) => Leaf((a, b)).some 77 | case (Branch(fstLeft, fstRight), Branch(sndLeft, sndRight)) => 78 | zip1(fstLeft, sndLeft) match { 79 | case None => None 80 | case Some(leftResult) => 81 | zip1(fstRight, sndRight) match { 82 | case None => None 83 | case Some(rightResult) => Branch(leftResult, rightResult).some 84 | } 85 | } 86 | case (_, _) => None 87 | } 88 | 89 | def zip[A, B]: (Tree[A], Tree[B]) => Option[Tree[(A, B)]] = { 90 | case (Leaf(a), Leaf(b)) => Leaf((a, b)).some 91 | case (Branch(l1, r1), Branch(l2, r2)) => 92 | Monad[Option].map2(zip(l1, l2), zip(r1, r2))(Branch.apply) // is it lazy? 93 | case (_, _) => None 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/scala/examples/YonedaLiftAndLowerAreInverses.scala: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import cats.free.Yoneda 4 | 5 | /** 6 | * Example show that Yoneda.run (in Haskell lowerYoneda) is 7 | * an inverse to Yoneda.apply (in Haskell liftYoneda) 8 | * 9 | * from excellent talk by: Alissa Pajer 10 | * How Haskell is Changing my Brain - Yay Yoneda 11 | * https://vimeo.com/96639840 12 | */ 13 | object YonedaLiftAndLowerAreInverses extends App { 14 | 15 | val liftYonedaOpt = Yoneda(Option(true)) 16 | val lowerYonedaOpt = liftYonedaOpt.run // Some(true) 17 | println(lowerYonedaOpt) 18 | 19 | val liftYonedaList = Yoneda(List(1, 2, 3)) 20 | val lowerYonedaList = liftYonedaList.run // List(1, 2, 3) 21 | println(lowerYonedaList) 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/filterable/Filterable.scala: -------------------------------------------------------------------------------- 1 | package filterable 2 | 3 | import educational.category_theory.Functor 4 | 5 | // https://github.com/masaeedu/filterable/blob/master/src/Data/Filterable.hs 6 | class Filterable { 7 | 8 | type \/[A, B] = Either[A, B] 9 | 10 | // Monoidal structure 11 | def assocE[A, B, C]: Either[A, Either[B, C]] => Either[Either[A, B], C] = { 12 | case Left(a) => ??? 13 | } 14 | 15 | trait Filterable[F[_]] extends Functor[F] { 16 | def partition[A, B]: F[Either[A, B]] => (F[A], F[B]) 17 | } 18 | 19 | // TODO laws 20 | 21 | object Filterable { 22 | def trivial[F[_]](implicit FF: Filterable[F]): F[Void] => Unit = Function.const(()) 23 | 24 | val filterableOption: Filterable[Option] = new Filterable[Option] { 25 | def map[A, B](x: Option[A])(f: A => B): Option[B] = x.map(f) 26 | def partition[A, B]: Option[Either[A, B]] => (Option[A], Option[B]) = { 27 | case None => (None, None) 28 | case Some(Left(a)) => (Some(a), None) 29 | case Some(Right(b)) => (None, Some(b)) 30 | } 31 | } 32 | 33 | val filterableList: Filterable[List] = new Filterable[List] { 34 | def map[A, B](x: List[A])(f: A => B): List[B] = x.map(f) 35 | def partition[A, B]: List[Either[A, B]] => (List[A], List[B]) = 36 | a => (a.collect { case Left(a) => a }, a.collect { case Right(b) => b }) 37 | } 38 | 39 | // TODO Logic 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/functorfunctor/FFunctor.scala: -------------------------------------------------------------------------------- 1 | package functorfunctor 2 | 3 | import educational.category_theory.higher.~> 4 | 5 | /** 6 | * Functor Functor 7 | * 8 | * @see https://www.benjamin.pizza/posts/2017-12-15-functor-functors.html 9 | * 10 | * Laws: 11 | * -- identity 12 | * ffmap id == id 13 | * 14 | * -- composition 15 | * ffmap (eta . phi) = ffmap eta . ffmap phi 16 | */ 17 | trait FFunctor[FF[_[_]]] { 18 | def ffmap[F[_], G[_]](nat: F ~> G): FF[F] => FF[G] 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/functorfunctor/FFunctorExample.scala: -------------------------------------------------------------------------------- 1 | package functorfunctor 2 | 3 | import java.time.LocalDate 4 | 5 | import educational.types.Id.Id 6 | 7 | /* 8 | * Example based on: 9 | * Functor Functors - benjamin.pizza: https://www.benjamin.pizza/posts/2017-12-15-functor-functors.html 10 | */ 11 | object FFunctorExample { 12 | 13 | sealed trait CardType 14 | case object Visa extends CardType 15 | case object AmEx extends CardType 16 | case object Mastercard extends CardType 17 | 18 | object V1 { 19 | case class Form( 20 | email: String, 21 | cardType: CardType, 22 | cardNumber: String, 23 | cardExpiry: LocalDate 24 | ) 25 | case class DraftForm( 26 | email: Option[String], 27 | cardType: Option[CardType], 28 | cardNumber: Option[String], 29 | cardExpiry: Option[LocalDate] 30 | ) 31 | 32 | def toForm: DraftForm => Option[Form] = { 33 | case DraftForm( 34 | Some(email), 35 | Some(cardType), 36 | Some(cardNumber), 37 | Some(cardExpiry) 38 | ) => 39 | Some(Form(email, cardType, cardNumber, cardExpiry)) 40 | case _ => None 41 | } 42 | } 43 | 44 | object V2 { 45 | case class FormTemplate[F[_]]( 46 | email: F[String], 47 | cardType: F[CardType], 48 | cardNumber: F[String], 49 | cardExpiry: F[LocalDate] 50 | ) 51 | type Form = FormTemplate[Id] 52 | type DraftForm = FormTemplate[Option] 53 | 54 | def toForm: FormTemplate[Option] => Option[Form] = ??? 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/hoare/HoareProbabilityMonad.scala: -------------------------------------------------------------------------------- 1 | package hoare 2 | 3 | import educational.category_theory.Monad 4 | 5 | /** 6 | * Hoare Probablility Monad 7 | * 8 | * Laws: 9 | * 10 | * identity laws: 11 | * choice(0, mx, my) == my 12 | * choice(1, mx, my) == mx 13 | * 14 | * skewed commutativity law: 15 | * choice(p, mx, my) == choice(1-p, my, mx) 16 | * 17 | * idempotence: 18 | * choice(p, mx, mx) == mx 19 | * 20 | * quasi-associativity: 21 | * if p == rs and (1-s)=(1-p)(1-q) then 22 | * choice(p, mx, choice(q, my, mz) == choice(s, choice(r, mx, my), mz) 23 | * mx - p 24 | * my - (1-p)q 25 | * mz - (1-p)(1-q) 26 | * 27 | * mx - sr 28 | * my - s(1-r) 29 | * mz - 1-s 30 | * 31 | * @tparam M effect 32 | * @tparam Prob type of progability e.g. double from [0,1] 33 | */ 34 | trait HoareProbabilityMonad[M[_], Prob] extends Monad[M] { 35 | 36 | /** bahaves like mx with probability of p and like my with probability of my * */ 37 | def choice[A](p: Prob, mx: M[A], my: M[A]): M[A] 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/profunctor_optics/ProfunctorOpticsSimpleImpl.scala: -------------------------------------------------------------------------------- 1 | package profunctor_optics 2 | 3 | import educational.category_theory.{Functor, Traverse} 4 | 5 | /* 6 | Mainline Profunctor Heirarchy for Optics 7 | https://r6research.livejournal.com/27476.html 8 | */ 9 | object ProfunctorOpticsSimpleImpl { 10 | 11 | trait Strong[P[_, _]] extends Profunctor[P] { 12 | def first[A, B, C](pab: P[A, B]): P[(A, C), (B, C)] 13 | 14 | def second[A, B, C](pab: P[A, B]): P[(C, A), (C, B)] = { 15 | val v1: P[(A, C), (B, C)] = first(pab) 16 | val v2: P[(A, C), (B, C)] => P[(C, A), (C, B)] = dimap(_.swap, _.swap) 17 | v2(v1) 18 | } 19 | } 20 | 21 | trait Choice[P[_, _]] extends Profunctor[P] { 22 | def left[A, B, C](pab: P[A, B]): P[Either[A, C], Either[B, C]] 23 | def right[A, B, C](pab: P[A, B]): P[Either[C, A], Either[C, B]] = { 24 | val v1: P[Either[A, C], Either[B, C]] = left(pab) 25 | val v2: P[Either[A, C], Either[B, C]] => P[Either[C, A], Either[C, B]] = 26 | dimap(_.swap, _.swap) 27 | v2(v1) 28 | } 29 | } 30 | 31 | trait Step[P[_, _]] extends Choice[P] with Strong[P] { 32 | def step[A, B, C, D]( 33 | pab: P[A, B] 34 | ): P[Either[D, (A, C)], Either[D, (B, C)]] = right(first(pab)) 35 | } 36 | 37 | trait Walk[P[_, _]] extends Step[P] { 38 | def walk[A, B, F[_]](pab: P[A, B])(implicit FT: Traverse[F]): P[F[A], F[B]] 39 | } 40 | 41 | trait Settable[P[_, _]] extends Walk[P] { 42 | def mapping[A, B, F[_]](pab: P[A, B])(implicit 43 | FT: Functor[F] 44 | ): P[F[A], F[B]] 45 | } 46 | 47 | trait Closed[P[_, _]] extends Profunctor[P] { 48 | def closed[A, B, X](pab: P[A, B]): P[X => A, X => B] 49 | } 50 | 51 | // Profunctor Optics 52 | 53 | trait Iso[S, T, A, B] { 54 | def run[P[_, _]](pab: P[A, B])(implicit P: Profunctor[P]): P[S, T] 55 | } 56 | 57 | trait Lens[S, T, A, B] { 58 | def run[P[_, _]](pab: P[A, B])(implicit S: Strong[P]): P[S, T] 59 | } 60 | 61 | trait Prism[S, T, A, B] { 62 | def run[P[_, _]](pab: P[A, B])(implicit C: Choice[P]): P[S, T] 63 | } 64 | 65 | trait AffineTraversal[S, T, A, B] { 66 | def run[P[_, _]](pab: P[A, B])(implicit S: Step[P]): P[S, T] 67 | } 68 | 69 | trait Traversal[S, T, A, B] { 70 | def run[P[_, _]](pab: P[A, B])(implicit W: Walk[P]): P[S, T] 71 | } 72 | 73 | trait SEC[S, T, A, B] { 74 | def run[P[_, _]](pab: P[A, B])(implicit S: Settable[P]): P[S, T] 75 | } 76 | 77 | trait Grates[S, T, A, B] { // https://r6research.livejournal.com/28050.html 78 | def run[P[_, _]](pab: P[A, B])(implicit C: Closed[P]): P[S, T] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/scala/profunctor_optics/YonedaExamples.scala: -------------------------------------------------------------------------------- 1 | package profunctor_optics 2 | 3 | import educational.category_theory.{Functor, Invariant} 4 | import educational.category_theory.higher.~> 5 | import educational.types.Reader.Reader 6 | 7 | object YonedaExamples extends App { 8 | 9 | trait ~[A, B] { 10 | def to: B => A 11 | def from: A => B 12 | } 13 | 14 | def isoInvariant[C]: Invariant[~[*,C]] = new Invariant[~[*,C]] { 15 | override def imap[A, B](fa: A ~ C)(ab: A => B, ba: B => A): B ~ C = new ~[B,C]{ 16 | def to: C => B = fa.to andThen ab 17 | def from: B => C = ba andThen fa.from 18 | } 19 | } 20 | 21 | abstract class Yo[F[+_], A] { 22 | def runYo(): Reader[A, *] ~> F[*] 23 | } 24 | 25 | type YonedaLemma[F[+_], A] = Yo[F, A] ~ F[A] 26 | def yonedaLemma[F[+_], A](implicit FF: Functor[F]): YonedaLemma[F, A] = 27 | new YonedaLemma[F, A] { 28 | def to: F[A] => Yo[F, A] = fa => 29 | new Yo[F, A] { 30 | def runYo(): (A => *) ~> F = 31 | λ[(A => *) ~> F](atox => FF.map(fa)(atox)) 32 | } 33 | 34 | def from: Yo[F, A] => F[A] = 35 | (fa: Yo[F, A]) => { 36 | val raf: Reader[A, *] ~> F = fa.runYo() 37 | raf.apply(identity[A]) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/selective/Selective.scala: -------------------------------------------------------------------------------- 1 | package selective 2 | 3 | import educational.category_theory.{Applicative, Monad} 4 | 5 | // TODO WIP 6 | /** Selective Applicatie Functors 7 | * 8 | * It is monoid with following tensor 9 | * 10 | * data (:|:) f g a where 11 | * (:|:) :: f (Either x a) -> g (x -> a) -> (:|:) f g a 12 | * 13 | * functor products which correspond to Applicative and Monad: 14 | * 15 | * data (:+:) f g a where 16 | * (:+:) :: f x -> g (x -> a) -> (f :+: g) a 17 | * 18 | * data (:*:) f g a where 19 | * (:*:) :: f x -> (x -> g a) -> (:*:) f g a 20 | * 21 | * Applicative/Selective/Monad could be written as follows: 22 | * 23 | * class Applicative f where 24 | * pure :: Identity ~> f 25 | * (<*>) :: f :+: f ~> f 26 | * 27 | * class Selective f where 28 | * pure :: Identity ~> f 29 | * (<*?) :: f :|: f ~> f 30 | * 31 | * class Monad f where 32 | * return :: Identity ~> f 33 | * join :: f :*: f ~> f 34 | * 35 | * @see [[https://blogs.ncl.ac.uk/andreymokhov/selective/ Andrey Mokhov Selective applicative functors]] 36 | */ 37 | trait Selective[F[_]] extends Applicative[F] { 38 | 39 | /** 40 | * Handle errors. 41 | * 42 | * Apply handler A => B if you provide Left[A] but skip the handler (and Effect) if you provide Right[B] 43 | */ 44 | def handle[A, B](fab: F[Either[A, B]], ff: F[A => B]): F[B] 45 | 46 | /** 47 | * Handle both cases of Either 48 | * 49 | * TODO is this correct implementation ??? 50 | */ 51 | def select[A, B, C]( 52 | fab: F[Either[A, B]], 53 | fac: F[A => C], 54 | fbc: F[B => C] 55 | ): F[C] = { 56 | val v0: (B => C) => Either[A, B] => Either[A, C] = bc => _.map(bc) 57 | val v1: F[Either[A, B] => Either[A, C]] = map(fbc)(v0) 58 | val v2: F[Either[A, C]] = ap(v1)(fab) 59 | handle(v2, fac) 60 | } 61 | 62 | /* Selective handle method can express Applicative apply */ 63 | override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = 64 | handle[A, B](map(fa)(Left.apply), ff) 65 | } 66 | 67 | object SelectiveInstance { 68 | def monadSelective[F[_]](implicit MF: Monad[F]): Selective[F] = 69 | new Selective[F] { 70 | def handle[A, B](fab: F[Either[A, B]], ff: F[A => B]): F[B] = ??? 71 | def pure[A](value: A): F[A] = MF.pure(value) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/scala/typetheory/LambdaCalc.scala: -------------------------------------------------------------------------------- 1 | package typetheory 2 | 3 | /** 4 | * Lambda calculus 5 | * 6 | * BNF: 7 | * M,N ::= x | λx.M | (M N) 8 | * 9 | * mathematical model: 10 | * M[X] = X + (X * M) + (M * M) 11 | */ 12 | sealed trait LambdaCalc[X] 13 | case class Mention[X](x: X) extends LambdaCalc[X] 14 | case class Abstraction[X](x: X, m: LambdaCalc[X]) extends LambdaCalc[X] 15 | case class Application[X](m: LambdaCalc[X], n: LambdaCalc[X]) 16 | extends LambdaCalc[X] 17 | 18 | object LambdaCalc { 19 | def freeNames[X](x: LambdaCalc[X]): Set[X] = 20 | x match { 21 | case Mention(lx) => Set(lx) 22 | case Abstraction(lx, lm) => freeNames(lm) - lx 23 | case Application(m, n) => freeNames(m) ++ freeNames(n) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/typetheory/Nat.scala: -------------------------------------------------------------------------------- 1 | package typetheory 2 | 3 | import scala.annotation.tailrec 4 | import typetheory.logic.Bool._ 5 | 6 | object Nat { 7 | 8 | sealed trait Nat 9 | case object Zero extends Nat 10 | case class Succ(n: Nat) extends Nat 11 | 12 | // unary operations 13 | 14 | def inc(n: Nat): Nat = Succ(n) 15 | 16 | def dec(n: Nat): Nat = 17 | n match { 18 | case Zero => Zero 19 | case Succ(m) => m 20 | } 21 | 22 | // binary operators 23 | 24 | @tailrec 25 | def plus(lhs: Nat, rhs: Nat): Nat = 26 | lhs match { 27 | case Zero => rhs 28 | case Succ(m) => plus(m, Succ(rhs)) 29 | } 30 | 31 | def times(lhs: Nat, rhs: Nat): Nat = 32 | (lhs, rhs) match { 33 | case (Zero, _) => Zero 34 | case (Succ(m), n) => plus(m, times(m, n)) 35 | } 36 | 37 | @tailrec 38 | def monus(lhs: Nat, rhs: Nat): Nat = 39 | (lhs, rhs) match { 40 | case (Zero, _) => Zero 41 | case (Succ(m), Succ(n)) => monus(m, n) 42 | } 43 | 44 | // relations 45 | 46 | @tailrec 47 | def eq(lhs: Nat, rhs: Nat): Bool = 48 | (lhs, rhs) match { 49 | case (Zero, Succ(_)) => False 50 | case (Succ(m), Succ(n)) => eq(m, n) 51 | } 52 | 53 | @tailrec 54 | def lq(lhs: Nat, rhs: Nat): Bool = 55 | (lhs, rhs) match { 56 | case (Zero, Succ(_)) => True 57 | case (Succ(m), Succ(n)) => lq(m, n) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/typetheory/PropositionalLogic.scala: -------------------------------------------------------------------------------- 1 | package typetheory 2 | 3 | class PropositionalLogic { 4 | object PropositionLogic { 5 | type /\[P, Q] = (P, Q) 6 | type \/[P, Q] = Either[P, Q] 7 | // => implication is function 8 | type <=>[P, Q] = (P => Q, P => Q) // Definition of biconditional 9 | } 10 | 11 | object Tautology { 12 | 13 | /** P ^ (Q V R) => (P ^ Q) ∨ (P ^ R) */ 14 | def andDistributeOr[P, Q, R](b: (P, Either[Q, R])): Either[(P, Q), (P, R)] = 15 | b._2 match { 16 | case Right(r) => Right((b._1, r)) 17 | case Left(q) => Left((b._1, q)) 18 | } 19 | 20 | /** (P ^ Q) ∨ (P ^ R) => P ^ (Q V R) */ 21 | def andDistributeOrInv[P, Q, R]( 22 | b: Either[(P, Q), (P, R)] 23 | ): (P, Either[Q, R]) = 24 | b match { 25 | case Right(pr) => (pr._1, Right(pr._2)) 26 | case Left(pr) => (pr._1, Left(pr._2)) 27 | } 28 | 29 | /** ((P => Q) ^ (Q => R)) => P => R is function composition */ 30 | def `Law of syllogism`[P, Q, R](f: P => Q, g: Q => R): P => R = 31 | f andThen g 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/typetheory/RhoCalculus.scala: -------------------------------------------------------------------------------- 1 | package typetheory 2 | 3 | /** 4 | * Rho calculus 5 | * 6 | * BNF: 7 | * P,Q ::= 0 | for (y <- x)P | x!(Q) | P|Q | *x 8 | * 9 | * mathematical model: 10 | * P[X] = 11 | */ 12 | sealed trait RhoCalc[X] 13 | case object Zero extends RhoCalc[Nothing] 14 | case class ForComprehension[X](y: X, x: X, p: RhoCalc[X]) extends RhoCalc[X] 15 | case class Bang[X](x: X, q: RhoCalc[X]) extends RhoCalc[X] 16 | case class Par[X](p: RhoCalc[X], q: RhoCalc[X]) extends RhoCalc[X] 17 | case class Star[X](p: X) extends RhoCalc[X] 18 | -------------------------------------------------------------------------------- /src/main/scala/typetheory/SkiCombinators.scala: -------------------------------------------------------------------------------- 1 | package typetheory 2 | 3 | class SkiCombinators { 4 | 5 | def S[X, Y, Z]: (Z => Y => X) => (Z => Y) => Z => X = 6 | a => 7 | b => 8 | c => { 9 | val bc: Y = b(c) 10 | val ac: Y => X = a(c) 11 | ac(bc) 12 | } 13 | 14 | def K[X, Y, Z]: X => Y => X = a => _ => a 15 | 16 | def I[X]: X => X = identity[X] 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/typetheory/logic/B3.scala: -------------------------------------------------------------------------------- 1 | package typetheory.logic 2 | 3 | /** Bochvar's internal three-valued logic */ 4 | object B3 { 5 | 6 | sealed trait B3 7 | case object T extends B3 8 | case object F extends B3 9 | case object I extends B3 10 | 11 | def not(a: B3): B3 = 12 | a match { 13 | case T => F 14 | case I => I 15 | case F => T 16 | } 17 | 18 | def and(a: B3, b: B3): B3 = 19 | (a, b) match { 20 | case (T, b) => b 21 | case (I, _) => I 22 | case (F, I) => I 23 | case (F, _) => F 24 | } 25 | 26 | def or(a: B3, b: B3): B3 = 27 | (a, b) match { 28 | case (T, I) => I 29 | case (T, _) => T 30 | case (I, _) => I 31 | case (F, b) => b 32 | } 33 | 34 | def impl(a: B3, b: B3): B3 = 35 | (a, b) match { 36 | case (T, b) => b 37 | case (I, _) => I 38 | case (F, I) => I 39 | case (F, _) => T 40 | } 41 | 42 | def bicond(a: B3, b: B3): B3 = 43 | (a, b) match { 44 | case (T, b) => b 45 | case (I, _) => I 46 | case (F, T) => F 47 | case (F, I) => I 48 | case (F, F) => T 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/scala/typetheory/logic/Bool.scala: -------------------------------------------------------------------------------- 1 | package typetheory.logic 2 | 3 | object Bool { 4 | 5 | sealed trait Bool 6 | case object True extends Bool 7 | case object False extends Bool 8 | 9 | // unary operations 10 | 11 | def not(n: Bool): Bool = 12 | n match { // increment with rounding 13 | case True => False 14 | case False => True 15 | } 16 | 17 | def inc(n: Bool): Bool = True 18 | 19 | def dec(n: Bool): Bool = False 20 | 21 | def buffer(n: Bool): Bool = n // identity 22 | 23 | // binary operators 24 | 25 | /* 26 | | A | B | AND | OR | XOR | IMPLY | NAND | NOR | XNOR | NIMPLY | converse | nconverse | fst | snd | false | true | nsnd | nfst | 27 | |---|---|-----|----|-----|-------|------|-----|------|--------|----------|-----------|-----|-----|-------|------|------|------| 28 | | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 29 | | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 30 | | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 31 | | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 32 | 33 | xnor := biconditional <=> 34 | false == void 35 | plus == max == or 36 | monus == nimply 37 | power 38 | min == and 39 | 40 | */ 41 | 42 | def or(lhs: Bool, rhs: Bool): Bool = 43 | (lhs, rhs) match { 44 | case (False, _) => rhs 45 | case (True, _) => True 46 | } 47 | 48 | def nimply(lhs: Bool, rhs: Bool): Bool = 49 | (lhs, rhs) match { 50 | case (True, False) => True 51 | case (_, _) => False 52 | } 53 | 54 | def and(lhs: Bool, rhs: Bool): Bool = 55 | (lhs, rhs) match { 56 | case (False, _) => False 57 | case (True, _) => rhs 58 | } 59 | 60 | def monus(lhs: Bool, rhs: Bool): Bool = 61 | (lhs, rhs) match { 62 | case (False, _) => False 63 | case (True, False) => True 64 | case (True, True) => False 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/scala/typetheory/logic/K3.scala: -------------------------------------------------------------------------------- 1 | package typetheory.logic 2 | 3 | import typetheory.logic.Bool.{Bool, False, True} 4 | 5 | /** 6 | * Kleene strong K3 logic 7 | * Priest P3 logic 8 | */ 9 | object K3 { 10 | sealed trait K3 11 | case object T extends K3 12 | case object F extends K3 13 | case object I extends K3 14 | 15 | type P3 = K3 16 | 17 | def not(a: K3): K3 = 18 | a match { 19 | case T => F 20 | case I => I 21 | case F => T 22 | } 23 | 24 | def and(a: K3, b: K3): K3 = 25 | (a, b) match { 26 | case (T, b) => b 27 | case (I, F) => F 28 | case (I, _) => I 29 | case (F, _) => F 30 | } 31 | 32 | def or(a: K3, b: K3): K3 = 33 | (a, b) match { 34 | case (T, _) => T 35 | case (I, T) => T 36 | case (I, _) => I 37 | case (F, b) => b 38 | } 39 | 40 | def impl(a: K3, b: K3): K3 = 41 | (a, b) match { 42 | case (T, b) => b 43 | case (I, T) => T 44 | case (I, _) => I 45 | case (F, _) => T 46 | } 47 | 48 | def bicond(a: K3, b: K3): K3 = 49 | (a, b) match { 50 | case (T, b) => b 51 | case (I, _) => I 52 | case (F, T) => F 53 | case (F, I) => I 54 | case (F, F) => T 55 | } 56 | 57 | // no tautologies 58 | def isKleeneTruth(a: K3): Bool = 59 | a match { 60 | case T => True 61 | case _ => False 62 | } 63 | 64 | // same tautologies as classic logic 65 | def isPriestTruth(a: K3): Bool = 66 | a match { 67 | case T => True 68 | case I => True 69 | case F => False 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/scala/applicative/ApplicativeExamplesSpec.scala: -------------------------------------------------------------------------------- 1 | package applicative 2 | 3 | import educational.category_theory.Applicative 4 | import educational.collections.HeadNel 5 | import org.scalatest.funspec.AnyFunSpec 6 | import org.scalatest.matchers.must.Matchers 7 | 8 | class ApplicativeExamplesSpec extends AnyFunSpec with Matchers { 9 | 10 | describe("derived methods") { 11 | it("option instance") { 12 | val optionApplicative: Applicative[Option] = new Applicative[Option] { 13 | def pure[A](a: A): Option[A] = ??? 14 | def ap[A, B](ff: Option[A => B])(fa: Option[A]): Option[B] = ??? 15 | } 16 | 17 | // TODO 18 | } 19 | 20 | it("ValidatedNel instance") { 21 | sealed trait ValidatedNel[A] 22 | case class SuccessVN[A](value: A) extends ValidatedNel[A] 23 | case class Errors[A](errors: HeadNel[Throwable]) extends ValidatedNel[A] 24 | 25 | val optionApplicative: Applicative[ValidatedNel] = 26 | new Applicative[ValidatedNel] { 27 | override def pure[A](value: A): ValidatedNel[A] = ??? 28 | override def ap[A, B](ff: ValidatedNel[A => B])( 29 | fa: ValidatedNel[A] 30 | ): ValidatedNel[B] = ??? 31 | } 32 | 33 | // TODO 34 | } 35 | 36 | it("examples for Option") { 37 | import cats.Applicative 38 | 39 | val add3: Int => Int = a => a + 3 40 | 41 | Applicative[Option].pure(42) mustBe Some(42) 42 | Applicative[Option].ap(Some(add3))(Some(39)) mustBe Some(42) 43 | } 44 | 45 | it("examples for List") { 46 | import cats.Applicative 47 | 48 | val list1 = List(1, 2) 49 | val listFns: List[Int => Int] = List(a => a + 3, a => a * a) 50 | 51 | Applicative[List].ap(listFns)(list1) mustBe List(4, 5, 1, 4) 52 | Applicative[List].pure("foo") mustBe List("foo") 53 | } 54 | 55 | it("map2 on composed Applicative") { 56 | import cats.Applicative 57 | 58 | val listOpt = Applicative[List] compose Applicative[Option] 59 | 60 | val list1 = List(Some(2), None) 61 | val list2 = List(Some(10), Some(2)) 62 | listOpt.map2(list1, list2)(_ + _) mustBe List( 63 | Some(12), 64 | Some(4), 65 | None, 66 | None 67 | ) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/scala/bifunctor/BicovariantExamplesSpec.scala: -------------------------------------------------------------------------------- 1 | package bifunctor 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | import zio.prelude._ 6 | 7 | class BicovariantExamplesSpec extends AnyFunSpec with Matchers { 8 | 9 | describe("Bicovariant") { 10 | describe("bimap") { 11 | it("bimap can transform both sides of Either or Tuple") { 12 | val product: (Int, List[String]) = (42, List("foo", "bar")) 13 | product.bimap(_ - 1, _.headOption) mustBe(41, Some("foo")) 14 | 15 | val either: Either[Int, List[String]] = Right(List("foo", "bar")) 16 | either.bimap(_ - 1, _.headOption) mustBe Right(Some("foo")) 17 | } 18 | } 19 | 20 | describe("leftmap") { 21 | it("apply given functions for first and second element of tuple") { 22 | val product: (Int, List[String]) = (42, List("foo", "bar")) 23 | product.bimap(_ - 1, _.headOption) mustBe (41, Some("foo")) 24 | 25 | val either: Either[Int, List[String]] = Right(List("foo", "bar")) 26 | either.bimap(_ - 1, _.headOption) mustBe Right(Some("foo")) 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/scala/contravariant/DivideSpec.scala: -------------------------------------------------------------------------------- 1 | package contravariant 2 | 3 | import scalaz.Divide 4 | import org.scalatest.funspec.AnyFunSpec 5 | import org.scalatest.matchers.must.Matchers 6 | 7 | class DivideSpec extends AnyFunSpec with Matchers { 8 | 9 | describe("Divide") { 10 | 11 | case class Serializer[A](run: A => Array[Byte]) 12 | val strSerial = Serializer[String](_.getBytes) 13 | val intSerial = Serializer[Int](_.toString.getBytes) 14 | 15 | case class Fragment(name: String, size: Int) 16 | 17 | it("custom implementation of Fragment serializer") { 18 | 19 | val fragmentSerial = Serializer[Fragment] { frag => 20 | val a1 = strSerial.run(frag.name) 21 | val a2 = intSerial.run(frag.size) 22 | a1 ++ a2 23 | } 24 | 25 | val serialized = fragmentSerial.run(Fragment("Area", 52)) 26 | new String(serialized) mustBe "Area52" 27 | } 28 | 29 | it("Fragment serializer using Divide") { 30 | 31 | implicit val fragmentDivide: Divide[Serializer] = new Divide[Serializer] { 32 | def divide2[A1, A2, Z](s1: => Serializer[A1], s2: => Serializer[A2])( 33 | f: Z => (A1, A2) 34 | ): Serializer[Z] = 35 | Serializer { frag => 36 | val (a1, a2) = f(frag) 37 | val serialized1 = s1.run(a1) 38 | val serializedB = s2.run(a2) 39 | serialized1 ++ serializedB 40 | } 41 | 42 | def contramap[A, B](r: Serializer[A])(f: B => A): Serializer[B] = 43 | Serializer(f andThen r.run) 44 | } 45 | 46 | val fragAsTuple: Fragment => (String, Int) = 47 | frag => (frag.name, frag.size) 48 | val fragmentSerial: Serializer[Fragment] = 49 | Divide[Serializer].divide(strSerial, intSerial)(fragAsTuple) 50 | 51 | val serialized = fragmentSerial.run(Fragment("Area", 52)) 52 | new String(serialized) mustBe "Area52" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/scala/functor/CovariantExamplesSpec.scala: -------------------------------------------------------------------------------- 1 | package functor 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | 6 | class CovariantExamplesSpec extends AnyFunSpec with Matchers { 7 | 8 | private def isOdd(i: Int): Boolean = i % 2 == 1 9 | 10 | describe("Functor") { 11 | 12 | describe("map called directly from Covariant") { 13 | it("apply given function for each element of List") { 14 | import zio.prelude.Covariant 15 | import zio.prelude.Id 16 | 17 | // force using zio version of map 18 | Covariant[List].map(isOdd)(List(2, 3, 4)) mustBe List(false, true, false) 19 | Covariant[Option].map(isOdd)(Option(42)) mustBe Option(false) 20 | 21 | val myId: Id[Int] = Id(42) 22 | Covariant[Id].map(isOdd)(myId) mustBe false 23 | } 24 | } 25 | 26 | describe("derived methods") { 27 | it("can be called directly when Functor syntax is imported") { 28 | import zio.prelude._ 29 | 30 | List(2, 3, 4).unit mustBe List((), (), ()) 31 | List(2, 3, 4).as("foo") mustBe List("foo", "foo", "foo") 32 | // List(2, 3, 4).fproduct(isOdd) mustBe List((2, false), (3, true), (4, false)) 33 | // no fproduct in zio-prelude def fproduct[A, B](fa: F[A])(f: A => B): F[(A, B)] = map(fa)(a => a -> f(a)) 34 | } 35 | 36 | it("for Vector") { 37 | import zio.prelude._ 38 | 39 | Vector(2, 3, 4).unit mustBe Vector((), (), ()) 40 | Vector(2, 3, 4).as("foo") mustBe Vector("foo", "foo", "foo") 41 | //Vector(2, 3, 4).fproduct(isOdd) mustBe Vector((2, false), (3, true), (4, false)) 42 | } 43 | 44 | it("for Option") { 45 | import zio.prelude._ 46 | 47 | Option(42).unit mustBe Option(()) 48 | Option(42).as("foo") mustBe Option("foo") 49 | //Option(42).fproduct(isOdd) mustBe Option((42, false)) 50 | } 51 | } 52 | 53 | describe("compose") { 54 | it("can chain multiple map") { 55 | import cats.Functor 56 | 57 | val listOptionCovariant = Functor[List] compose Functor[Option] 58 | listOptionCovariant.map(List(Some(42), Some(15), None))(isOdd) mustBe List( 59 | Some(false), 60 | Some(true), 61 | None 62 | ) 63 | } 64 | 65 | it("can chain multiple map 2") { 66 | import cats.Functor 67 | 68 | val listOption = Functor[List] compose Functor[Option] 69 | import listOption.map 70 | map(List(Some(42), Some(15), None))(isOdd) mustBe List( 71 | Some(false), 72 | Some(true), 73 | None 74 | ) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/scala/functor/TreeFunctorSpec.scala: -------------------------------------------------------------------------------- 1 | package functor 2 | 3 | import educational.collections.{Branch, Leaf} 4 | import educational.collections.TreeInstances.treeFunctor.map 5 | import org.scalatest.funspec.AnyFunSpec 6 | import org.scalatest.matchers.must.Matchers 7 | 8 | class TreeFunctorSpec extends AnyFunSpec with Matchers { 9 | 10 | describe("map over Tree") { 11 | it("execute function for value stored inside Left") { 12 | map(Leaf("foo"))(stringLength) mustBe Leaf(3) 13 | map(Leaf("foo"))(toUpperCase) mustBe Leaf("FOO") 14 | } 15 | 16 | it("called with Branch execute function for every subtree") { 17 | map(Branch(Leaf("foo"), Leaf("bar")))(stringLength) mustBe Branch( 18 | Leaf(3), 19 | Leaf(3) 20 | ) 21 | 22 | map(Branch(Leaf("foo"), Branch(Leaf("quux"), Leaf("foobar"))))( 23 | toUpperCase 24 | ) mustBe 25 | Branch(Leaf("FOO"), Branch(Leaf("QUUX"), Leaf("FOOBAR"))) 26 | } 27 | 28 | it("called with complicated Tree execute function for every subtree") { 29 | val tree = Branch(Leaf("a"), Branch(Leaf("b"), Leaf("c"))) 30 | 31 | val expected = Branch(Leaf("A"), Branch(Leaf("B"), Leaf("C"))) 32 | 33 | map(tree)(toUpperCase) mustBe expected 34 | } 35 | } 36 | 37 | private def stringLength(s: String) = s.length 38 | private def toUpperCase(s: String) = s.toUpperCase 39 | } 40 | -------------------------------------------------------------------------------- /src/test/scala/monad/CustomMonadImplementationSpec.scala: -------------------------------------------------------------------------------- 1 | package monad 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | import zio.prelude._ 6 | 7 | class CustomMonadImplementationSpec extends AnyFunSpec with Matchers { 8 | 9 | trait Maybe[+A] 10 | case object Empty extends Maybe[Nothing] 11 | case class Value[+A](a: A) extends Maybe[A] 12 | 13 | implicit val maybeCovariant: Covariant[Maybe] = new Covariant[Maybe] { 14 | override def map[A, B](f: A => B): Maybe[A] => Maybe[B] = { 15 | case Empty => Empty 16 | case Value(a) => Value(f(a)) 17 | } 18 | } 19 | 20 | implicit val maybeMonad: IdentityFlatten[Maybe] = new IdentityFlatten[Maybe] { 21 | override def any: Maybe[Any] = Value(()) 22 | 23 | override def flatten[A](ffa: Maybe[Maybe[A]]): Maybe[A] = ffa match { 24 | case Empty => Empty 25 | case Value(a) => a 26 | } 27 | } 28 | 29 | val radiusList: Maybe[Int] = Value(42) 30 | 31 | def pure[A](a: A): Maybe[A] = IdentityFlatten[Maybe].any.map(Function.const(a)) 32 | 33 | describe("Monad derived methods") { 34 | 35 | it("flatMap can be implemented using flatten and pure") { 36 | /* pure 37 | A ----------> M[A] 38 | | | 39 | pure | | pure 40 | | | 41 | \/ \/ 42 | M[A] <---------- M[M[A]] 43 | flatten */ 44 | 45 | radiusList.flatMap(a => Value(a * Math.PI)) mustBe radiusList.map(_ * Math.PI) 46 | 47 | pure(pure(42)).flatten mustBe pure(42) 48 | } 49 | } 50 | 51 | describe("Monad laws examples") { 52 | 53 | it("tests Monad laws") { 54 | def circleArea: Int => Maybe[Double] = 55 | x => Value(Math.PI * x * x) 56 | def show: Double => Maybe[String] = x => Value(x.toString) 57 | 58 | /* flatMap associativity: 59 | 60 | flatMap(f) flatMap(g) 61 | M[A] --------------> M[B] --------------> M[C] 62 | | | 63 | | | 64 | | | 65 | ----------------------------------------- 66 | flatMap( f andThen flatMap(g) ) */ 67 | 68 | val result1 = for { 69 | radius <- radiusList 70 | result <- circleArea(radius) 71 | resultStr <- show(result) 72 | } yield resultStr 73 | 74 | val result2 = radiusList.flatMap( e => 75 | for { 76 | a <- circleArea(e) 77 | r <- show(a) 78 | } yield r 79 | ) 80 | 81 | result1 mustBe result2 82 | 83 | /* left identity: 84 | 85 | pure 86 | A ----------> M[A] 87 | | / 88 | | / 89 | f | / 90 | | / flatMap(f) 91 | | / 92 | | / 93 | M[B] */ 94 | 95 | 96 | pure(42).flatMap(circleArea) mustBe circleArea(42) 97 | 98 | /* right identity: 99 | 100 | flatMap( pure ) 101 | F[A] --------------------> F[A] */ 102 | 103 | radiusList.flatMap(pure) mustBe radiusList 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/scala/monad/ReaderMonadSpec.scala: -------------------------------------------------------------------------------- 1 | package monad 2 | 3 | import scala.concurrent.duration.Duration 4 | import scala.concurrent.duration._ 5 | import org.scalatest.funspec.AnyFunSpec 6 | import org.scalatest.matchers.must.Matchers 7 | 8 | case class Reader[A, B](run: A => B) { // B is changing and A is constant 9 | 10 | def map[C](f: B => C): Reader[A, C] = Reader(run andThen f) 11 | 12 | def flatMap[C](f: B => Reader[A, C]): Reader[A, C] = { 13 | // def attempt1: A => Reader[A, C] = a => f(run(a)) 14 | // def attempt2: A => (A => C) = (a:A) => f(run(a)).run 15 | def attempt3: A => C = 16 | (a: A) => { 17 | val b: B = run(a) 18 | val reader: Reader[A, C] = f(b) 19 | reader.run(a) 20 | } 21 | Reader(attempt3) 22 | } 23 | 24 | def flatten(r: Reader[A, Reader[A, B]]): Reader[A, B] = { // pass the context A to outer computation and inner computation 25 | def newRun: A => B = 26 | a => { 27 | val firstRun: Reader[A, B] = r.run(a) 28 | firstRun.run(a) 29 | } 30 | Reader(newRun) 31 | } 32 | } 33 | 34 | object Reader { 35 | 36 | def ask[A]: Reader[A, A] = Reader(identity) 37 | } 38 | 39 | class ReaderMonadSpec extends AnyFunSpec with Matchers { 40 | 41 | describe("Reader") { 42 | case class DbConfig( 43 | maxTimeout: Duration, 44 | jdbc: String, 45 | user: String, 46 | pass: String 47 | ) 48 | 49 | val baseConfig = 50 | DbConfig(60.seconds, "jdbc://localhost:5432/db", "admin", "1234") 51 | type FindById = Int => String 52 | 53 | def getAllCustomerIds: DbConfig => List[Int] = 54 | _ => List(1, 42) // it pass the tests so who cares 55 | def getDefaultCustomer: DbConfig => FindById = 56 | conf => id => s"Johnny Bravo $id" 57 | 58 | it("produces something from configuration") { 59 | val reader: Reader[DbConfig, (Int => String)] = Reader(getDefaultCustomer) 60 | reader.run(baseConfig)(1) mustBe "Johnny Bravo 1" 61 | } 62 | 63 | it("is like Functor") { 64 | val reader: Reader[DbConfig, List[Int]] = Reader(getAllCustomerIds) 65 | reader.map(_.mkString(", ")).run(baseConfig) mustBe "1, 42" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/scala/monoid/AlternativeMonoidInstancesSpec.scala: -------------------------------------------------------------------------------- 1 | package monoid 2 | 3 | import cats.Monoid 4 | import org.scalatest.funspec.AnyFunSpec 5 | import org.scalatest.matchers.must.Matchers 6 | 7 | class AlternativeMonoidInstancesSpec extends AnyFunSpec with Matchers { 8 | 9 | /** 10 | * Create Monoid for tuple from two Monoids 11 | */ 12 | implicit def createMonoidTuple[L, R]( 13 | left: Monoid[L], 14 | right: Monoid[R] 15 | ): Monoid[(L, R)] = 16 | new Monoid[(L, R)] { 17 | def empty: (L, R) = (left.empty, right.empty) 18 | def combine(x: (L, R), y: (L, R)): (L, R) = 19 | (left.combine(x._1, y._1), right.combine(x._2, y._2)) 20 | } 21 | 22 | describe("custom Monoid for multiply Int") { 23 | it("can use |+| syntax and combineAll method") { 24 | import cats.Monoid 25 | import cats.syntax.semigroup._ 26 | import monoid.AlternativeMonoidInstances.multiplyIntMonoid 27 | 28 | Monoid[Int].combineAll(Nil) mustBe 1 29 | Monoid[Int].combineAll(List(1, 2, 3, 4)) mustBe 24 30 | 31 | 2 |+| 3 mustBe 6 32 | } 33 | } 34 | 35 | describe("custom Monoid instance for first non empty Option") { 36 | it("get first non empty option") { 37 | import cats.Monoid 38 | import cats.syntax.semigroup._ 39 | import cats.syntax.option._ 40 | import monoid.AlternativeMonoidInstances.optionFirstNonEmpty 41 | 42 | case class Foo(v: Int) 43 | 44 | Monoid[Option[Foo]].combineAll( 45 | List(None, Foo(2).some, Foo(3).some) 46 | ) mustBe Some(Foo(2)) 47 | Foo(2).some |+| Foo(3).some mustBe Foo(2).some 48 | } 49 | } 50 | } 51 | 52 | object AlternativeMonoidInstances { 53 | def createMonoid[A](mempty: A, mapply: (A, A) => A): Monoid[A] = 54 | new Monoid[A] { 55 | def empty: A = mempty 56 | def combine(x: A, y: A) = mapply(x, y) 57 | } 58 | 59 | implicit val multiplyIntMonoid: Monoid[Int] = createMonoid(1, _ * _) 60 | 61 | implicit def optionFirstNonEmpty[A]: Monoid[Option[A]] = 62 | createMonoid(None, _ orElse _) 63 | } 64 | -------------------------------------------------------------------------------- /src/test/scala/monoid/MonoidExamplesSpec.scala: -------------------------------------------------------------------------------- 1 | package monoid 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | 6 | class MonoidExamplesSpec extends AnyFunSpec with Matchers { 7 | 8 | describe("combineAll") { 9 | it("invoke operation for all elements") { 10 | import cats.syntax.all._ 11 | 12 | List(1, 2, 3).combineAll mustBe 6 13 | List[Int]().combineAll mustBe 0 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/scala/mtl/TraverseEmptyListPermutationsSpec.scala: -------------------------------------------------------------------------------- 1 | package mtl 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | 6 | /** 7 | * Generate all permutations of the list using filterM 8 | * 9 | * MonadFilter filterM moved to TraverseFilter filterA: https://github.com/typelevel/cats/blob/master/CHANGES.md 10 | * 11 | * Explanations: 12 | * http://stackoverflow.com/questions/28872396/haskells-filterm-with-filterm-x-true-false-1-2-3 13 | * (Brent Yorgey in Haskell) https://byorgey.wordpress.com/2007/06/26/deducing-code-from-types-filterm/ 14 | */ 15 | class TraverseEmptyListPermutationsSpec extends AnyFunSpec with Matchers { 16 | 17 | describe("filterA") { 18 | it("compute all permutations of the lit if given List(true, false)") { 19 | import cats._ 20 | import cats.syntax.all._ 21 | 22 | val allPermutations = List( 23 | List(1, 2, 3), 24 | List(1, 2), 25 | List(1, 3), 26 | List(2, 3), 27 | List(1), 28 | List(2), 29 | List(3), 30 | Nil 31 | ) 32 | val result: List[List[Int]] = 33 | List(1, 2, 3).filterA(_ => List(true, false)) 34 | result contains theSameElementsAs(allPermutations) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/scala/profunctor/ClosedProfunctorSpec.scala: -------------------------------------------------------------------------------- 1 | package profunctor 2 | 3 | import cats.arrow.Profunctor 4 | import org.scalatest.funspec.AnyFunSpec 5 | import org.scalatest.matchers.must.Matchers 6 | 7 | class ClosedProfunctorSpec 8 | extends AnyFunSpec 9 | with Matchers { 10 | 11 | describe("Closed Profunctor") { 12 | trait Closed[=>:[_,_]] extends Profunctor[=>:] { 13 | def closed[A,B,C]: A=>:B => (C=>A) =>: (C => B) 14 | } 15 | 16 | it("for Function1 closed is just composition of arguments") { 17 | val function1Closed: Closed[Function1] = new Closed[Function] { 18 | def closed[A,B,C]: (A => B) => ((C => A) => (C => B)) = ab => _ andThen ab 19 | def dimap[A,B,C,D](ab: A => B)(ca: C => A)(bd: B => D): C => D = (ca andThen ab) andThen bd 20 | } 21 | 22 | def len: String => Int = _.length 23 | def res[A]: (A => String) => A => Int = function1Closed.closed[String,Int,A](len) 24 | 25 | case class Person(name: String) 26 | def getName: Person => String = _.name 27 | val getNameLength: Person => Int = res(getName) 28 | getNameLength(Person("Fred")) mustBe 4 29 | } 30 | 31 | it("closed profunctor is a TambaraModule with tensor inverted Funcion1") { 32 | trait TambaraModule[=>:[_,_], ⊗[_,_]] { 33 | def runTambara[A,B,C]: A=>:B => (A⊗C) =>: (B⊗C) 34 | } 35 | 36 | type Function1Op[A,B] = B => A 37 | def closedTambara[P[_,_]](implicit CP: Closed[P]) = new TambaraModule[P, Function1Op] { 38 | def runTambara[A, B, C]: P[A, B] => P[C => A, C => B] = CP.closed 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/scala/profunctor/DivariantExampleSpec.scala: -------------------------------------------------------------------------------- 1 | package educational.category_theory.two.profunctor 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | import zio.prelude._ 6 | 7 | class DivariantExampleSpec extends AnyFunSpec with Matchers { 8 | 9 | describe("Divariant") { 10 | it("Divariant for Function1 is Covariant + Contravariant") { 11 | case class Person(name: String, age: Int) 12 | 13 | val len: String => Int = _.length 14 | val getPersonName: Person => String = _.name 15 | val above5: Int => Boolean = _ > 5 16 | 17 | val personNameAbove5 = len.dimap(getPersonName, above5) 18 | personNameAbove5(Person("Foo", 100)) mustBe false 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/scala/profunctor/ProfunctorSpec.scala: -------------------------------------------------------------------------------- 1 | package profunctor 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | import scalaz.Scalaz._ 6 | import scalaz._ 7 | 8 | class ProfunctorSpec 9 | extends AnyFunSpec 10 | with Matchers { 11 | 12 | describe("Profunctor") { 13 | it("Profunctor for Function1 is Functor + Contravariant") { 14 | case class Person(name: String, age: Int) 15 | 16 | val len: String => Int = _.length // String => Int 17 | val getPersonName: Person => String = _.name // Person => String 18 | val above5: Int => Boolean = _ > 5 // Int => Boolean 19 | 20 | val personNameAbove5 = Profunctor[Function1].dimap(len)(getPersonName)(above5) // AA => BB 21 | personNameAbove5(Person("Foo", 100)) mustBe false 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/profunctor/StrongSpec.scala: -------------------------------------------------------------------------------- 1 | package profunctor 2 | 3 | import educational.category_theory.two.profunctor.Profunctor 4 | import educational.category_theory.two.profunctor.strong.Strong 5 | import org.scalatest.funspec.AnyFunSpec 6 | import org.scalatest.matchers.must.Matchers 7 | 8 | case class DoubleFun[X, Y](fun: (X, X) => (Y, Y)) 9 | 10 | trait DoubleFunPro extends Profunctor[DoubleFun] { 11 | override def dimap[S, T, A, B]( 12 | pab: DoubleFun[A, B] 13 | )(f: S => A, g: B => T): DoubleFun[S, T] = 14 | DoubleFun[S, T] { (c1, c2) => 15 | pab.fun(f(c1), f(c2)) match { case (z1, z2) => (g(z1), g(z2)) } 16 | } 17 | } 18 | 19 | class StrongSpec extends AnyFunSpec with Matchers { 20 | 21 | it("Functions from A to B are Strong Profunctors") { 22 | trait Function1Profunctor extends Profunctor[Function1] { 23 | override def dimap[S, T, A, B]( 24 | pab: A => B 25 | )(ab: S => A, cd: B => T): S => T = ab andThen pab andThen cd 26 | } 27 | 28 | val functionPro: Profunctor[Function1] = new Function1Profunctor {} 29 | 30 | val functionStrong: Strong[Function1] = new Strong[Function1] 31 | with Function1Profunctor { 32 | def first[X, Y, Z](pab: X => Y): Function1[(X, Z), (Y, Z)] = 33 | (xz: (X, Z)) => (pab(xz._1), xz._2) 34 | } 35 | 36 | def len: String => Int = _.length 37 | 38 | functionStrong.first(len)(("foo", 42)) mustBe (3, 42) 39 | } 40 | 41 | it("You can define multiple Strong Profunctors for P[_,_]") { 42 | val doubleFunProStrength: Strong[DoubleFun] with DoubleFunPro = 43 | new Strong[DoubleFun] with DoubleFunPro { 44 | override def first[A, B, C]( 45 | fa: DoubleFun[A, B] 46 | ): DoubleFun[(A, C), (B, C)] = 47 | DoubleFun { (a1: (A, C), a2: (A, C)) => 48 | val bb = fa.fun(a1._1, a2._1) 49 | ((bb._1, a1._2), (bb._2, a2._2)) 50 | } 51 | } 52 | 53 | val doubleFunProStrength2: Strong[DoubleFun] with DoubleFunPro = 54 | new Strong[DoubleFun] with DoubleFunPro { 55 | override def first[A, B, C]( 56 | fa: DoubleFun[A, B] 57 | ): DoubleFun[(A, C), (B, C)] = 58 | DoubleFun { (a1: (A, C), a2: (A, C)) => 59 | val bb = fa.fun(a1._1, a2._1) 60 | ((bb._1, a1._2), (bb._1, a2._2)) 61 | } 62 | } 63 | 64 | def bothLength(a: String, b: String): (Int, Int) = (a.length, b.length) 65 | 66 | doubleFunProStrength.first( 67 | DoubleFun(bothLength) 68 | ) must not be doubleFunProStrength2.first(DoubleFun(bothLength)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/scala/semigroup/SemigroupExamplesSpec.scala: -------------------------------------------------------------------------------- 1 | package semigroup 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | import zio.prelude._ 6 | import zio.prelude.newtypes.{Sum, _} 7 | 8 | class SemigroupExamplesSpec extends AnyFunSpec with Matchers { 9 | 10 | describe("Semigroup methods") { 11 | it("combine can add BigDecimals") { 12 | Sum(1000L).combine(Sum(42L)) mustBe Sum(1042L) 13 | } 14 | 15 | it("join maps containint Ints") { 16 | val map1 = Map("hello" -> Sum(8), "world" -> Sum(1)) 17 | val map2 = Map("hello" -> Sum(2), "cats" -> Sum(3)) 18 | val merged = Map("hello" -> Sum(8 + 2), "world" -> Sum(1), "cats" -> Sum(3)) 19 | 20 | map1.combine(map2) mustBe merged 21 | } 22 | 23 | it("join maps containing List of Ints") { 24 | val map1 = Map("even" -> List(2, 4, 6, 8), "odd" -> List(1, 3, 5, 7)) 25 | val map2 = Map("even" -> List(8, 20)) 26 | val merged = 27 | Map("even" -> List(2, 4, 6, 8, 8, 20), "odd" -> List(1, 3, 5, 7)) 28 | 29 | map1 combine map2 mustBe merged 30 | } 31 | 32 | it("combine can add Ints inside Option") { 33 | val o1: Option[Sum[Int]] = Some(Sum(1)) 34 | val o2: Option[Sum[Int]] = Some(Sum(2)) 35 | o1 combine o2 mustBe Option(3) 36 | o1 combine None mustBe o1 37 | } 38 | 39 | it("merge tuples with doubles and strings") { 40 | val s1 = (Sum(2.5), "foo") 41 | val s2 = (Sum(2.6), "bar") 42 | s1 combine s2 mustBe (5.1, "foobar") 43 | } 44 | 45 | it("semigroup |+| can be used in List.fold") { 46 | List(1, 2, 3) 47 | .map(Sum(_)) 48 | .foldLeft(Sum(0))(_ combine _) mustBe 6 49 | } 50 | 51 | it("combineN can concatenate strings") { 52 | import cats.syntax.semigroup._ 53 | "foo".combineN(3) mustBe "foofoofoo" 54 | } 55 | } 56 | 57 | describe("|+|") { 58 | it( 59 | "adds Ints using |+| using semigroup syntax and group for Int instance" 60 | ) { 61 | import cats.syntax.semigroup._ 62 | 63 | 2 |+| 2 mustBe 4 64 | 1 |+| 41 mustBe 42 65 | } 66 | } 67 | 68 | describe("combineN") { 69 | it("invokes combine given number of times") { 70 | import cats.syntax.semigroup._ 71 | import cats.instances.option._ 72 | 73 | val o1: Option[Int] = Some(1) 74 | o1.combineN(3) mustBe Option(3) 75 | 76 | Option(3).combineN(4) mustBe Option(12) 77 | } 78 | } 79 | 80 | describe("combineAllOption") { 81 | it("can wrap Semigroup with Option and combine all elements") { 82 | import cats.implicits._ 83 | 84 | (Nil: List[Int]).combineAllOption mustBe None 85 | List(1, 2, 3).combineAllOption mustBe Some(6) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/scala/traverse/TraverseExamplesSpec.scala: -------------------------------------------------------------------------------- 1 | package traverse 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | 6 | class TraverseExamplesSpec extends AnyFunSpec with Matchers { 7 | 8 | describe("Traversing List ") { 9 | it("traverse") { 10 | import cats.syntax.traverse._ 11 | val xs1: Vector[Int] = Vector(1, 2, 3) 12 | xs1.traverse(a => Option(a)) mustBe Some(Vector(1, 2, 3)) 13 | } 14 | 15 | it("sequence") { 16 | import zio.prelude._ 17 | val xs1: List[Option[Int]] = List(Some(1), Some(2), None) 18 | xs1.flip mustBe None 19 | 20 | val xs2: List[Option[Int]] = List(Some(1), Some(2), Some(42)) 21 | xs2.flip mustBe Some(List(1, 2, 42)) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/zivariant/ZivariantSpec.scala: -------------------------------------------------------------------------------- 1 | package zivariant 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.must.Matchers 5 | import zio.{ZEnvironment, ZIO} 6 | import zio.prelude._ 7 | 8 | class ZivariantSpec extends AnyFunSpec with Matchers with ZivariantSyntax { 9 | 10 | case class EmployeeId(v: Int) 11 | case class Employee(id: EmployeeId, name: String) 12 | 13 | val db: Map[Int, Employee] = { 14 | Map( 15 | 0 -> "Delenn", 16 | 1 -> "Dr. Stephen Frankli", 17 | 2 -> "Vir Cotto", 18 | 3 -> "G'Kar", 19 | 4 -> "Londo Mollari", 20 | 5 -> "Michael Garibaldi" 21 | ).map { case (k, v) => k -> Employee(EmployeeId(k), v) } 22 | } 23 | 24 | def loadFromDb: Int => Either[Throwable, Employee] = id => 25 | db.get(id) match { 26 | case Some(r) => Right(r) 27 | case None => Left(new RuntimeException(s"Employee with id [$id] not found")) 28 | } 29 | 30 | sealed trait AppError 31 | case class NotFound(details: String) extends AppError 32 | 33 | def getDetails: Employee => String = _.name 34 | 35 | def asDomainError: Throwable => NotFound = 36 | err => NotFound(err.getMessage) 37 | 38 | it("bimap for function returning Either map result") { 39 | val employee: Either[Throwable, Employee] = loadFromDb(4) 40 | 41 | val result: Either[NotFound, String] = employee.bimap(asDomainError, getDetails) 42 | result mustBe Right("Londo Mollari") 43 | } 44 | 45 | it("bimap for function returning Either transform function") { 46 | val getEmployeeDetails: Int => Either[NotFound, String] = 47 | loadFromDb andThen { _.bimap(asDomainError, getDetails) } 48 | 49 | val result: Either[NotFound, String] = getEmployeeDetails(4) 50 | result mustBe Right("Londo Mollari") 51 | } 52 | 53 | // TODO fix zivariant examples 54 | // def idFromEmployee: ZEnvironment[EmployeeId] => ZEnvironment[Int] = ??? // a => a.map(_.v) // TODO _.v 55 | // 56 | // it("zimap for function returning Either transform input, error channel and output") { 57 | // import Zivariant.FunctionEitherZivariant.zimap 58 | // 59 | // val getEmployeeDetails: ZEnvironment[EmployeeId] => Either[NotFound, String] = 60 | // zimap(idFromEmployee, asDomainError, getDetails)(loadFromDb) 61 | // 62 | // val result: Either[NotFound, String] = getEmployeeDetails(EmployeeId(4)) 63 | // result mustBe Right("Londo Mollari") 64 | // } 65 | // 66 | // it("zimap for ZIO transform input, error channel and output") { 67 | // val loadEmployeeFromDb: ZIO[Int, Throwable, Employee] = ZIO.fromFunctionM{ id => 68 | // ZIO.fromEither(db.get(id) match { 69 | // case Some(r) => Right(r) 70 | // case None => Left(new RuntimeException(s"Employee with id [$id] not found")) 71 | // }) 72 | // } 73 | // 74 | // val loadEmployee: ZIO[EmployeeId, NotFound, String] = 75 | // loadEmployeeFromDb.zimap(idFromEmployee, asDomainError, getDetails) 76 | // } 77 | } 78 | --------------------------------------------------------------------------------