Skip to content

Commit

Permalink
Merge pull request #2 from julianpeeters/dev
Browse files Browse the repository at this point in the history
Initial functionality
  • Loading branch information
julianpeeters committed Dec 16, 2023
2 parents ecd0761 + 253ea47 commit 4129853
Show file tree
Hide file tree
Showing 14 changed files with 500 additions and 6 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Scala CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
name: Build and Test
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
scala: [3.3.1]
java: ['11', '17']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v3.13.0
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
- name: Run tests
run: sbt ++${{ matrix.scala }} test
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Release

on:
push:
tags: ["*"]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v3.13.0
with:
distribution: temurin
java-version: 11
cache: sbt
- run: sbt ci-release
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
102 changes: 96 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,105 @@
# destructured

Common typeclasses, but parameterized by `A` instead of `F[_]`.
Common typeclasses and constructors, but parameterized by `A` instead of `F[_]`

### Why?
### Libraries for Scala 3 (JS, JVM, and Native platforms)
- [`destructured-cats`](#destructured-cats): typeclasses that provide cats typeclasses for the underlying functor, e.g., `Applicative[Option]`
- [`destructured-scala`](#destructured-scala): typeclasses that provide the underlying data constructors of a type `A`, e.g., `Some[T]`

This might be useful if your model uses subtypes in its definition.

#### Why?

This library can be useful if your model uses subtypes in its definition.

For example, if the compiler knows that a type `A`, is, at a call site, a
`Some[?]` or a `None.type`, then `destructured` typeclasses can be used to
summon `cats` typeclasses for the underlying `Option`:
`Some[T]` or a `None.type`, then `destructured` typeclasses can be used to
summon `cats` `Applicative` typeclass for the underlying `Option`:

```scala
import destructured.cats.{ApplicativeOf, given}

def f[A](a: A)(using A: ApplicativeOf[A]): ApplicativeOf[A] = A
val a: Some[Int] = Some(1)
// a: Some[Int] = Some(value = 1)
val b: Option[String] = f(a).pure("foo")
// b: Option[String] = Some(value = "foo")
```

<small>(for a more realistic example, see the [dynamical](https://github.com/julianpeeters/dynamical) library)</small>


## `destructured-cats`

```scala
"com.julianpeeters" %% "destructured-cats" % "0.1.0"
```

Supported types: `Applicative[Option]`, `Functor[Option]`, `// TODO`

##### Examples:

##### `ApplicativeOf`


```scala
import destructured.cats.{ApplicativeOf, given}

def f[A](a: A)(using A: ApplicativeOf[A]): ApplicativeOf[A] = A
val a: Some[Int] = Some(1)
// a: Some[Int] = Some(value = 1)
val b: Option[String] = f(a).pure("foo")
// b: Option[String] = Some(value = "foo")
```


##### `FunctorOf`

```scala
import destructured.cats.{FunctorOf, given}

val fa: Option[Int] = Option(1)
// fa: Option[Int] = Some(value = 1)
def f[A](a: A)(using A: FunctorOf[A]): FunctorOf[A] = A
val fb: Option[Int] = f(fa).map(fa)(_ + 1)
// fb: Option[Int] = Some(value = 2)
```
A.pure("hello, world")


## `destructured-scala`

```scala
"com.julianpeeters" %% "destructured-scala" % "0.1.0"
```

The following constructors are supported:

| Option | Either |
| :---: | :---: |
| Some | Left |
| None | Right |

##### Examples:

##### `Some[T]`

```scala
import destructured.scala.{CtorOf, given}

val a: Some[Int] = Some(1)
// a: Some[Int] = Some(value = 1)
def f[A](a: A)(using A: CtorOf[A]): CtorOf[A] = A
val b: Some[String] = f(a).apply("foo")
// b: Some[String] = Some(value = "foo")
```

##### `Either[L, R]`

```scala
import destructured.scala.{CtorOf, given}

val a: Right[Boolean, Int] = Right(1)
// a: Right[Boolean, Int] = Right(value = 1)
def f[A](a: A)(using A: CtorOf[A]): CtorOf[A] = A
val b: Right[Boolean, String] = f(a).apply("foo")
// b: Right[Boolean, String] = Right(value = "foo")
```
65 changes: 65 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
inThisBuild(List(
crossScalaVersions := Seq(scalaVersion.value),
description := "Typeclasses of a lower kind.",
organization := "com.julianpeeters",
homepage := Some(url("https://github.com/julianpeeters/destructured")),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
developers := List(
Developer(
"julianpeeters",
"Julian Peeters",
"julianpeeters@gmail.com",
url("http://github.com/julianpeeters")
)
),
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-source:future",
"-Werror",
"-Wunused:all",
"-Wvalue-discard",
"-Ykind-projector:underscores"
),
scalaVersion := "3.3.1",
versionScheme := Some("semver-spec"),
))

lazy val root = project.in(file(".")).aggregate(tests)

lazy val cats = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.in(file("modules/cats"))
.settings(
name := "destructured-cats",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % "2.10.0"
)
)

lazy val scala = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.in(file("modules/scala"))
.settings(
name := "destructured-scala",
)

lazy val tests = project.in(file("modules/tests"))
.settings(
name := "destructured-tests",
libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "0.7.29" % Test
)
)
.dependsOn(cats.jvm, scala.jvm)
.enablePlugins(NoPublishPlugin)

lazy val docs = project.in(file("docs/gitignored"))
.settings(
mdocOut := file("."),
mdocVariables := Map(
"SCALA" -> crossScalaVersions.value.map(e => e.takeWhile(_ != '.')).mkString(", "),
"VERSION" -> version.value.takeWhile(_ != '+'),
)
)
.dependsOn(cats.jvm, scala.jvm)
.enablePlugins(MdocPlugin)
.enablePlugins(NoPublishPlugin)
95 changes: 95 additions & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# destructured

Common typeclasses and constructors, but parameterized by `A` instead of `F[_]`

### Libraries for Scala @SCALA@ (JS, JVM, and Native platforms)
- [`destructured-cats`](#destructured-cats): typeclasses that provide cats typeclasses for the underlying functor, e.g., `Applicative[Option]`
- [`destructured-scala`](#destructured-scala): typeclasses that provide the underlying data constructors of a type `A`, e.g., `Some[T]`


#### Why?

This library can be useful if your model uses subtypes in its definition.

For example, if the compiler knows that a type `A`, is, at a call site, a
`Some[T]` or a `None.type`, then `destructured` typeclasses can be used to
summon `cats` `Applicative` typeclass for the underlying `Option`:

```scala mdoc:reset
import destructured.cats.{ApplicativeOf, given}

def f[A](a: A)(using A: ApplicativeOf[A]): ApplicativeOf[A] = A
val a: Some[Int] = Some(1)
val b: Option[String] = f(a).pure("foo")
```

<small>(for a more realistic example, see the [dynamical](https://github.com/julianpeeters/dynamical) library)</small>


## `destructured-cats`

```scala
"com.julianpeeters" %% "destructured-cats" % "@VERSION@"
```

Supported types: `Applicative[Option]`, `Functor[Option]`, `// TODO`

##### Examples:

##### `ApplicativeOf`


```scala mdoc:reset
import destructured.cats.{ApplicativeOf, given}

def f[A](a: A)(using A: ApplicativeOf[A]): ApplicativeOf[A] = A
val a: Some[Int] = Some(1)
val b: Option[String] = f(a).pure("foo")
```


##### `FunctorOf`

```scala mdoc:reset
import destructured.cats.{FunctorOf, given}

val fa: Option[Int] = Option(1)
def f[A](a: A)(using A: FunctorOf[A]): FunctorOf[A] = A
val fb: Option[Int] = f(fa).map(fa)(_ + 1)
```


## `destructured-scala`

```scala
"com.julianpeeters" %% "destructured-scala" % "@VERSION@"
```

The following constructors are supported:

| Option | Either |
| :---: | :---: |
| Some | Left |
| None | Right |

##### Examples:

##### `Some[T]`

```scala mdoc:reset
import destructured.scala.{CtorOf, given}

val a: Some[Int] = Some(1)
def f[A](a: A)(using A: CtorOf[A]): CtorOf[A] = A
val b: Some[String] = f(a).apply("foo")
```

##### `Either[L, R]`

```scala mdoc:reset
import destructured.scala.{CtorOf, given}

val a: Right[Boolean, Int] = Right(1)
def f[A](a: A)(using A: CtorOf[A]): CtorOf[A] = A
val b: Right[Boolean, String] = f(a).apply("foo")
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package destructured.cats

import cats.Applicative

trait ApplicativeOf[FT]:
def pure[A](x: A): Pure[FT, A]

type Pure[X, A] = X match
case Option[a] => Option[A]

given [T](using A: Applicative[Option]): ApplicativeOf[Option[T]] =
new ApplicativeOf[Option[T]]:
def pure[A](x: A): Pure[Option[T], A] =
A.pure(x)

given [T](using A: Applicative[Option]): ApplicativeOf[Some[T]] =
new ApplicativeOf[Some[T]]:
def pure[A](x: A): Pure[Some[T], A] =
A.pure(x)

given (using A: Applicative[Option]): ApplicativeOf[None.type] =
new ApplicativeOf[None.type]:
def pure[A](x: A): Pure[None.type, A] =
A.pure(x)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package destructured.cats

import cats.Functor

trait FunctorOf[FT]:
def map[B](fa: FA[FT])(f: Func[FT, B]): FB[FT, B]

type FA[X] = X match
case Option[a] => Option[a]

type FB[X, B] = X match
case Option[a] => Option[B]

type Func[X, B] = X match
case Option[a] => a => B

given [A] (using F: Functor[Option]): FunctorOf[Option[A]] =
new FunctorOf[Option[A]]:
def map[B](fa: FA[Option[A]])(f: Func[Option[A], B]): FB[Option[A], B] =
F.map(fa)(f)

given [A] (using F: Functor[Option]): FunctorOf[Some[A]] =
new FunctorOf[Some[A]]:
def map[B](fa: FA[Some[A]])(f: Func[Some[A], B]): FB[Some[A], B] =
F.map(fa)(f)

given (using F: Functor[Option]): FunctorOf[None.type] =
new FunctorOf[None.type]:
def map[B](fa: FA[None.type])(f: Func[None.type, B]): FB[None.type, B] =
F.map(fa)(f)
8 changes: 8 additions & 0 deletions modules/core/shared/src/main/scala/destructured/type.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package destructured

type Pure[X, A] = X match
case Option[a] => Option[A]

type Ctor[X, A] = X match
case Some[a] => Some[A]
case None.type => None.type

0 comments on commit 4129853

Please sign in to comment.