Skip to content

Commit

Permalink
About conversions.
Browse files Browse the repository at this point in the history
  • Loading branch information
tarao committed Nov 28, 2023
1 parent 46f1ba9 commit af7b3a0
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 7 deletions.
34 changes: 31 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,30 @@ lazy val docs = project
),
tlSiteApiModule := Some((core.jvm / projectID).value),
laikaConfig := {
import laika.config.{ApiLinks, LinkConfig}
val apiBaseUrl = "https://javadoc.io/doc/com.github.tarao/record4s_3"
import laika.config.{ApiLinks, LinkConfig, SourceLinks}
val version = mdocVariables.value("VERSION")
val apiUrl = {
val group = groupId.value
val project = projectName.value
val scalaVer = scalaBinaryVersion.value
val base = s"https://javadoc.io/doc/${group}/${project}_${scalaVer}"
s"${base}/${version}/"
}
val sourceUrl = {
val scmUrl = scmInfo.value.get.browseUrl.toString
val moduleBase = {
val rootDir = (root.all / baseDirectory).value.toString
val crossDir = (core.jvm / crossProjectBaseDirectory).value.toString
crossDir.stripPrefix(s"${rootDir}/")
}
val base = (core.jvm / baseDirectory).value
val dir = (core.jvm / Compile / scalaSource)
.value
.toString
.stripPrefix(s"${base}/")

s"${scmUrl}/tree/v${version}/${moduleBase}/${dir}/"
}

laikaConfig
.value
Expand All @@ -194,11 +216,17 @@ lazy val docs = project
LinkConfig
.empty
.addApiLinks(
ApiLinks(s"""${apiBaseUrl}/${mdocVariables.value("VERSION")}/"""),
ApiLinks(apiUrl),
)
.addApiLinks(
ApiLinks(s"https://scala-lang.org/api/${scalaVersion.value}/")
.withPackagePrefix("scala"),
)
.addSourceLinks(
SourceLinks(
baseUri = sourceUrl,
suffix = "scala",
),
),
)
},
Expand Down
92 changes: 92 additions & 0 deletions docs/advanced/conversions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
Defining Your Own Conversions
=============================

Converting records to/from @:api(scala.Product)s is described in [Conversions] section.
You can also define conversions for any other types.

As an example, we use the following `Person` class as the target of conversions.

```scala mdoc
class Person(val name: String, val age: Int) {
override def toString: String = s"Person(${name} (${age}))"
}
```

Making this class a case class is the easiest way to achieve the conversions but we will
define conversions by hand for exercise without making it a case class.

RecordLike
----------

Giving a @:api(com.github.tarao.record4s.RecordLike) instance for a type enables the type
to be converted to a record. `RecordLike` consists of type members and a method to
retrieve fields as an iterable.

- Type members
- `FieldTypes`
- Type of label-value pairs as a tuple type or a structural type
- `ElemLabels`
- Type of field labels as a tuple type
- `ElemTypes`
- Type of field values as a tuple type
- Method
- `iterableOf(r: R): Iterable[(String, Any)]`
- Retrieve label-value pairs from the conversion target of type `R`

A `RecordLike` instance for `Person` class can be given as the following.

```scala mdoc
import com.github.tarao.record4s.RecordLike

given RecordLike[Person] with
type FieldTypes = (("name", String), ("age", Int))
type ElemLabels = ("name", "age")
type ElemTypes = (String, Int)

def iterableOf(p: Person): Iterable[(String, Any)] =
Seq(("name", p.name), ("age", p.age))
```

Then, `Record.from` works for a `Person` instance.

```scala mdoc:mline
import com.github.tarao.record4s.Record

Record.from(Person("tarao", 3))
```

It is also possible to use a `Person` as a right-hand-side argument of concatenation.

```scala mdoc:invisible
import com.github.tarao.record4s.%
```

```scala mdoc:mline
%(email = "[email protected]") ++ Person("tarao", 3)
```

More complicated example can be found in the source code of
@:source(com.github.tarao.record4s.RecordLike).

Converter
---------

Giving a @:api(com.github.tarao.record4s.Converter) instance for a type enables the type
to be converted from a record. `Converter` is simply an interface with `apply: From =>
To`. `Convert` for `Person` class can be given as the following.

```scala mdoc
import com.github.tarao.record4s.Converter

type PersonRecord = % { val name: String; val age: Int }

given [R <: PersonRecord]: Converter[R, Person] with
def apply(record: R): Person =
Person(record.name, record.age)
```

Then, `to[Person]` on a record converts the record to a `Person`.

```scala mdoc:mline
%(name = "tarao", age = 3).to[Person]
```
4 changes: 4 additions & 0 deletions docs/advanced/directory.conf
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
laika.title = Advanced Topics
laika.navigationOrder = [
generic.md
conversions.md
]
8 changes: 4 additions & 4 deletions docs/conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ case class Person(name: String, age: Int)
Record.from(Person("tarao", 3))
```

Anything other than @:api(scala.Product) can be converted to a record if
@:api(com.github.tarao.record4s.RecordLike) instance is given.
Anything other than @:api(scala.Product) can be converted to a record if [RecordLike]
instance is given.

To Something
------------
Expand All @@ -30,8 +30,8 @@ You can use `to` method to convert a record to a @:api(scala.Product).
%(name = "ikura", age = 1).to[Person]
```

A record can be converted to anything other than @:api(scala.Product) if
@:api(com.github.tarao.record4s.Converter) instance is given. @:todo(link to the API)
A record can be converted to anything other than @:api(scala.Product) if [Converter]
instance is given.

From / To JSON
--------------
Expand Down

0 comments on commit af7b3a0

Please sign in to comment.