From 45c71cbc6a3bf695203a1cdb176005f3d7bd1456 Mon Sep 17 00:00:00 2001
From: Arman Bilge
Date: Fri, 27 Jan 2023 06:37:39 +0000
Subject: [PATCH 1/3] `Modifier` is `Contravariant`
---
calico/src/main/scala/calico/html/Modifier.scala | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/calico/src/main/scala/calico/html/Modifier.scala b/calico/src/main/scala/calico/html/Modifier.scala
index e4693a85..387cc164 100644
--- a/calico/src/main/scala/calico/html/Modifier.scala
+++ b/calico/src/main/scala/calico/html/Modifier.scala
@@ -18,7 +18,9 @@ package calico
package html
import calico.syntax.*
+import cats.Contravariant
import cats.Foldable
+import cats.Id
import cats.effect.kernel.Async
import cats.effect.kernel.Resource
import cats.effect.syntax.all.*
@@ -34,8 +36,14 @@ trait Modifier[F[_], E, A]:
inline final def contramap[B](inline f: B => A): Modifier[F, E, B] =
(b: B, e: E) => outer.modify(f(b), e)
-private object Modifier:
- def forSignal[F[_]: Async, E, M, V](signal: M => Signal[F, V])(
+object Modifier:
+ inline given [F[_], E]: Contravariant[Modifier[F, E, _]] =
+ _contravariant.asInstanceOf[Contravariant[Modifier[F, E, _]]]
+ private val _contravariant: Contravariant[Modifier[Id, Any, _]] = new:
+ def contramap[A, B](fa: Modifier[Id, Any, A])(f: B => A) =
+ fa.contramap(f)
+
+ private[html] def forSignal[F[_]: Async, E, M, V](signal: M => Signal[F, V])(
mkModify: (M, E) => V => F[Unit]): Modifier[F, E, M] = (m, e) =>
signal(m).getAndUpdates.flatMap { (head, tail) =>
val modify = mkModify(m, e)
@@ -43,7 +51,8 @@ private object Modifier:
tail.foreach(modify(_)).compile.drain.cedeBackground.void
}
- def forSignalResource[F[_]: Async, E, M, V](signal: M => Resource[F, Signal[F, V]])(
+ private[html] def forSignalResource[F[_]: Async, E, M, V](
+ signal: M => Resource[F, Signal[F, V]])(
mkModify: (M, E) => V => F[Unit]): Modifier[F, E, M] = (m, e) =>
signal(m).flatMap { sig =>
sig.getAndUpdates.flatMap { (head, tail) =>
From a0fa919e28ed944c281c9375a96c8970c384ef68 Mon Sep 17 00:00:00 2001
From: Arman Bilge
Date: Fri, 27 Jan 2023 07:03:12 +0000
Subject: [PATCH 2/3] Replace `Codec` with `encoders`
---
calico/src/main/scala/calico/html/Codec.scala | 96 -------------------
calico/src/main/scala/calico/html/Html.scala | 6 +-
.../src/main/scala/calico/html/HtmlAttr.scala | 37 +++----
calico/src/main/scala/calico/html/Prop.scala | 42 ++++----
.../src/main/scala/calico/html/encoders.scala | 48 ++++++++++
.../calico/html/codegen/CalicoGenerator.scala | 10 +-
6 files changed, 96 insertions(+), 143 deletions(-)
delete mode 100644 calico/src/main/scala/calico/html/Codec.scala
create mode 100644 calico/src/main/scala/calico/html/encoders.scala
diff --git a/calico/src/main/scala/calico/html/Codec.scala b/calico/src/main/scala/calico/html/Codec.scala
deleted file mode 100644
index 0608231b..00000000
--- a/calico/src/main/scala/calico/html/Codec.scala
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2022 Arman Bilge
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package calico.html
-
-import scala.scalajs.js
-
-/**
- * This trait represents a way to encode and decode HTML attribute or DOM property values.
- *
- * It is needed because attributes encode all values as strings regardless of their type, and
- * then there are also multiple ways to encode e.g. boolean values. Some attributes encode those
- * as "true" / "false" strings, others as presence or absence of the element, and yet others use
- * "yes" / "no" or "on" / "off" strings, and properties encode booleans as actual booleans.
- *
- * Scala DOM Types hides all this mess from you using codecs. All those pseudo-boolean
- * attributes would be simply `Attr[Boolean](name, codec)` in your code.
- */
-private sealed abstract class Codec[ScalaType, DomType]:
-
- /**
- * Convert the result of a `dom.Node.getAttribute` call to appropriate Scala type.
- *
- * Note: HTML Attributes are generally optional, and `dom.Node.getAttribute` will return
- * `null` if an attribute is not defined on a given DOM node. However, this decoder is only
- * intended for cases when the attribute is defined.
- */
- def decode(domValue: DomType): ScalaType
-
- /**
- * Convert desired attribute value to appropriate DOM type. The resulting value should be
- * passed to `dom.Node.setAttribute` call, EXCEPT when resulting value is a `null`. In that
- * case you should call `dom.Node.removeAttribute` instead.
- *
- * We use `null` instead of [[Option]] here to reduce overhead in JS land. This method should
- * not be called by end users anyway, it's the consuming library's job to call this method
- * under the hood.
- */
- def encode(scalaValue: ScalaType): DomType
-
-private object Codec:
-
- inline def identity[A]: Codec[A, A] = identityInstance.asInstanceOf[Codec[A, A]]
- private val identityInstance: Codec[Any, Any] = new:
- def decode(domValue: Any): Any = domValue
- def encode(scalaValue: Any): Any = scalaValue
-
- val whitespaceSeparatedStrings: Codec[List[String], String] = new:
- def decode(domValue: String) = domValue.split(" ").toList
-
- def encode(scalaValue: List[String]) =
- if scalaValue.isEmpty then ""
- else
- var acc = scalaValue.head
- var tail = scalaValue.tail
- while tail.nonEmpty do
- acc += " " + tail.head
- tail = tail.tail
- acc
-
- val booleanAsAttrPresence: Codec[Boolean, String] = new:
- def decode(domValue: String): Boolean = domValue ne null
- def encode(scalaValue: Boolean): String = if scalaValue then "" else null
-
- val booleanAsTrueFalseString: Codec[Boolean, String] = new:
- def decode(domValue: String): Boolean = domValue == "true"
- def encode(scalaValue: Boolean): String = if scalaValue then "true" else "false"
-
- val booleanAsYesNoString: Codec[Boolean, String] = new:
- def decode(domValue: String): Boolean = domValue == "yes"
- def encode(scalaValue: Boolean): String = if scalaValue then "yes" else "no"
-
- val booleanAsOnOffString: Codec[Boolean, String] = new:
- def decode(domValue: String): Boolean = domValue == "on"
- def encode(scalaValue: Boolean): String = if scalaValue then "on" else "off"
-
- inline def doubleAsString: Codec[Double, String] = new:
- def decode(domValue: String): Double = domValue.toDouble
- def encode(scalaValue: Double): String = scalaValue.toString
-
- inline def intAsString: Codec[Int, String] = new:
- def decode(domValue: String): Int = domValue.toInt
- def encode(scalaValue: Int): String = scalaValue.toString
diff --git a/calico/src/main/scala/calico/html/Html.scala b/calico/src/main/scala/calico/html/Html.scala
index 46a6d28e..9cc67f15 100644
--- a/calico/src/main/scala/calico/html/Html.scala
+++ b/calico/src/main/scala/calico/html/Html.scala
@@ -45,10 +45,10 @@ sealed trait Html[F[_]](using F: Async[F])
def cls: ClassProp[F] = ClassProp[F]
- def role: HtmlAttr[F, List[String]] = HtmlAttr("role", Codec.whitespaceSeparatedStrings)
+ def role: HtmlAttr[F, List[String]] = HtmlAttr("role", encoders.whitespaceSeparatedStrings)
def dataAttr(suffix: String): HtmlAttr[F, String] =
- HtmlAttr("data-" + suffix, Codec.identity)
+ HtmlAttr("data-" + suffix, encoders.identity)
def children: Children[F] = Children[F]
@@ -56,4 +56,4 @@ sealed trait Html[F[_]](using F: Async[F])
KeyedChildren[F, K](f)
def styleAttr: HtmlAttr[F, String] =
- HtmlAttr("style", Codec.identity)
+ HtmlAttr("style", encoders.identity)
diff --git a/calico/src/main/scala/calico/html/HtmlAttr.scala b/calico/src/main/scala/calico/html/HtmlAttr.scala
index cd1a6918..e00401e0 100644
--- a/calico/src/main/scala/calico/html/HtmlAttr.scala
+++ b/calico/src/main/scala/calico/html/HtmlAttr.scala
@@ -16,57 +16,58 @@
package calico.html
+import cats.Contravariant
import cats.effect.kernel.Async
import cats.effect.kernel.Resource
import fs2.concurrent.Signal
import org.scalajs.dom
-sealed class HtmlAttr[F[_], V] private[calico] (key: String, codec: Codec[V, String]):
+sealed class HtmlAttr[F[_], V] private[calico] (key: String, encode: V => String):
import HtmlAttr.*
@inline def :=(v: V): ConstantModifier[V] =
- ConstantModifier(key, codec, v)
+ ConstantModifier(key, encode, v)
@inline def <--(vs: Signal[F, V]): SignalModifier[F, V] =
- SignalModifier(key, codec, vs)
+ SignalModifier(key, encode, vs)
@inline def <--(vs: Resource[F, Signal[F, V]]): SignalResourceModifier[F, V] =
- SignalResourceModifier(key, codec, vs)
+ SignalResourceModifier(key, encode, vs)
@inline def <--(vs: Signal[F, Option[V]]): OptionSignalModifier[F, V] =
- OptionSignalModifier(key, codec, vs)
+ OptionSignalModifier(key, encode, vs)
@inline def <--(vs: Resource[F, Signal[F, Option[V]]]): OptionSignalResourceModifier[F, V] =
- OptionSignalResourceModifier(key, codec, vs)
+ OptionSignalResourceModifier(key, encode, vs)
object HtmlAttr:
final class ConstantModifier[V] private[calico] (
private[calico] val key: String,
- private[calico] val codec: Codec[V, String],
+ private[calico] val encode: V => String,
private[calico] val value: V
)
final class SignalModifier[F[_], V] private[calico] (
private[calico] val key: String,
- private[calico] val codec: Codec[V, String],
+ private[calico] val encode: V => String,
private[calico] val values: Signal[F, V]
)
final class SignalResourceModifier[F[_], V] private[calico] (
private[calico] val key: String,
- private[calico] val codec: Codec[V, String],
+ private[calico] val encode: V => String,
private[calico] val values: Resource[F, Signal[F, V]]
)
final class OptionSignalModifier[F[_], V] private[calico] (
private[calico] val key: String,
- private[calico] val codec: Codec[V, String],
+ private[calico] val encode: V => String,
private[calico] val values: Signal[F, Option[V]]
)
final class OptionSignalResourceModifier[F[_], V] private[calico] (
private[calico] val key: String,
- private[calico] val codec: Codec[V, String],
+ private[calico] val encode: V => String,
private[calico] val values: Resource[F, Signal[F, Option[V]]]
)
@@ -78,7 +79,7 @@ private trait HtmlAttrModifiers[F[_]](using F: Async[F]):
_forConstantHtmlAttr.asInstanceOf[Modifier[F, E, ConstantModifier[V]]]
private val _forConstantHtmlAttr: Modifier[F, dom.Element, ConstantModifier[Any]] =
- (m, e) => Resource.eval(F.delay(e.setAttribute(m.key, m.codec.encode(m.value))))
+ (m, e) => Resource.eval(F.delay(e.setAttribute(m.key, m.encode(m.value))))
inline given forSignalHtmlAttr[E <: fs2.dom.Element[F], V]
: Modifier[F, E, SignalModifier[F, V]] =
@@ -86,7 +87,7 @@ private trait HtmlAttrModifiers[F[_]](using F: Async[F]):
private val _forSignalHtmlAttr =
Modifier.forSignal[F, dom.Element, SignalModifier[F, Any], Any](_.values) { (m, e) => v =>
- F.delay(e.setAttribute(m.key, m.codec.encode(v)))
+ F.delay(e.setAttribute(m.key, m.encode(v)))
}
inline given forSignalResourceHtmlAttr[E <: fs2.dom.Element[F], V]
@@ -95,7 +96,7 @@ private trait HtmlAttrModifiers[F[_]](using F: Async[F]):
private val _forSignalResourceHtmlAttr =
Modifier.forSignalResource[F, dom.Element, SignalResourceModifier[F, Any], Any](_.values) {
- (m, e) => v => F.delay(e.setAttribute(m.key, m.codec.encode(v)))
+ (m, e) => v => F.delay(e.setAttribute(m.key, m.encode(v)))
}
inline given forOptionSignalHtmlAttr[E <: fs2.dom.Element[F], V]
@@ -105,7 +106,7 @@ private trait HtmlAttrModifiers[F[_]](using F: Async[F]):
private val _forOptionSignalHtmlAttr =
Modifier.forSignal[F, dom.Element, OptionSignalModifier[F, Any], Option[Any]](_.values) {
(m, e) => v =>
- F.delay(v.fold(e.removeAttribute(m.key))(v => e.setAttribute(m.key, m.codec.encode(v))))
+ F.delay(v.fold(e.removeAttribute(m.key))(v => e.setAttribute(m.key, m.encode(v))))
}
inline given forOptionSignalResourceHtmlAttr[E <: fs2.dom.Element[F], V]
@@ -116,7 +117,7 @@ private trait HtmlAttrModifiers[F[_]](using F: Async[F]):
Modifier
.forSignalResource[F, dom.Element, OptionSignalResourceModifier[F, Any], Option[Any]](
_.values) { (m, e) => v =>
- F.delay(v.fold(e.removeAttribute(m.key))(v => e.setAttribute(m.key, m.codec.encode(v))))
+ F.delay(v.fold(e.removeAttribute(m.key))(v => e.setAttribute(m.key, m.encode(v))))
}
final class Aria[F[_]] private extends AriaAttrs[F]
@@ -125,5 +126,5 @@ private object Aria:
inline def apply[F[_]]: Aria[F] = instance.asInstanceOf[Aria[F]]
private val instance: Aria[cats.Id] = new Aria[cats.Id]
-final class AriaAttr[F[_], V] private[calico] (suffix: String, codec: Codec[V, String])
- extends HtmlAttr[F, V]("aria-" + suffix, codec)
+final class AriaAttr[F[_], V] private[calico] (suffix: String, encode: V => String)
+ extends HtmlAttr[F, V]("aria-" + suffix, encode)
diff --git a/calico/src/main/scala/calico/html/Prop.scala b/calico/src/main/scala/calico/html/Prop.scala
index a1988c8c..04ae12dd 100644
--- a/calico/src/main/scala/calico/html/Prop.scala
+++ b/calico/src/main/scala/calico/html/Prop.scala
@@ -27,71 +27,71 @@ import org.scalajs.dom
import scala.scalajs.js
-sealed class Prop[F[_], V, J] private[calico] (name: String, codec: Codec[V, J]):
+sealed class Prop[F[_], V, J] private[calico] (name: String, encode: V => J):
import Prop.*
@inline def :=(v: V): ConstantModifier[V, J] =
- ConstantModifier(name, codec, v)
+ ConstantModifier(name, encode, v)
@inline def <--(vs: Signal[F, V]): SignalModifier[F, V, J] =
- SignalModifier(name, codec, vs)
+ SignalModifier(name, encode, vs)
@inline def <--(vs: Resource[F, Signal[F, V]]): SignalResourceModifier[F, V, J] =
- SignalResourceModifier(name, codec, vs)
+ SignalResourceModifier(name, encode, vs)
@inline def <--(vs: Signal[F, Option[V]]): OptionSignalModifier[F, V, J] =
- OptionSignalModifier(name, codec, vs)
+ OptionSignalModifier(name, encode, vs)
@inline def <--(
vs: Resource[F, Signal[F, Option[V]]]): OptionSignalResourceModifier[F, V, J] =
- OptionSignalResourceModifier(name, codec, vs)
+ OptionSignalResourceModifier(name, encode, vs)
object Prop:
final class ConstantModifier[V, J] private[calico] (
private[calico] val name: String,
- private[calico] val codec: Codec[V, J],
+ private[calico] val encode: V => J,
private[calico] val value: V
)
final class SignalModifier[F[_], V, J] private[calico] (
private[calico] val name: String,
- private[calico] val codec: Codec[V, J],
+ private[calico] val encode: V => J,
private[calico] val values: Signal[F, V]
)
final class SignalResourceModifier[F[_], V, J] private[calico] (
private[calico] val name: String,
- private[calico] val codec: Codec[V, J],
+ private[calico] val encode: V => J,
private[calico] val values: Resource[F, Signal[F, V]]
)
final class OptionSignalModifier[F[_], V, J] private[calico] (
private[calico] val name: String,
- private[calico] val codec: Codec[V, J],
+ private[calico] val encode: V => J,
private[calico] val values: Signal[F, Option[V]]
)
final class OptionSignalResourceModifier[F[_], V, J] private[calico] (
private[calico] val name: String,
- private[calico] val codec: Codec[V, J],
+ private[calico] val encode: V => J,
private[calico] val values: Resource[F, Signal[F, Option[V]]]
)
private trait PropModifiers[F[_]](using F: Async[F]):
import Prop.*
- private inline def setProp[N, V, J](node: N, name: String, codec: Codec[V, J]) =
+ private inline def setProp[N, V, J](node: N, name: String, encode: V => J) =
(value: V) =>
F.delay {
- node.asInstanceOf[js.Dictionary[J]](name) = codec.encode(value)
+ node.asInstanceOf[js.Dictionary[J]](name) = encode(value)
()
}
- private inline def setPropOption[N, V, J](node: N, name: String, codec: Codec[V, J]) =
+ private inline def setPropOption[N, V, J](node: N, name: String, encode: V => J) =
(value: Option[V]) =>
F.delay {
val dict = node.asInstanceOf[js.Dictionary[Any]]
- value.fold(dict -= name)(v => dict(name) = codec.encode(v))
+ value.fold(dict -= name)(v => dict(name) = encode(v))
()
}
@@ -99,14 +99,14 @@ private trait PropModifiers[F[_]](using F: Async[F]):
_forConstantProp.asInstanceOf[Modifier[F, N, ConstantModifier[V, J]]]
private val _forConstantProp: Modifier[F, Any, ConstantModifier[Any, Any]] =
- (m, n) => Resource.eval(setProp(n, m.name, m.codec).apply(m.value))
+ (m, n) => Resource.eval(setProp(n, m.name, m.encode).apply(m.value))
inline given forSignalProp[N, V, J]: Modifier[F, N, SignalModifier[F, V, J]] =
_forSignalProp.asInstanceOf[Modifier[F, N, SignalModifier[F, V, J]]]
private val _forSignalProp =
Modifier.forSignal[F, Any, SignalModifier[F, Any, Any], Any](_.values) { (m, n) =>
- setProp(n, m.name, m.codec)
+ setProp(n, m.name, m.encode)
}
inline given forSignalResourceProp[N, V, J]: Modifier[F, N, SignalResourceModifier[F, V, J]] =
@@ -114,7 +114,7 @@ private trait PropModifiers[F[_]](using F: Async[F]):
private val _forSignalResourceProp =
Modifier.forSignalResource[F, Any, SignalResourceModifier[F, Any, Any], Any](_.values) {
- (m, n) => setProp(n, m.name, m.codec)
+ (m, n) => setProp(n, m.name, m.encode)
}
inline given forOptionSignalProp[N, V, J]: Modifier[F, N, OptionSignalModifier[F, V, J]] =
@@ -122,7 +122,7 @@ private trait PropModifiers[F[_]](using F: Async[F]):
private val _forOptionSignalProp =
Modifier.forSignal[F, Any, OptionSignalModifier[F, Any, Any], Option[Any]](_.values) {
- (m, n) => setPropOption(n, m.name, m.codec)
+ (m, n) => setPropOption(n, m.name, m.encode)
}
inline given forOptionSignalResourceProp[N, V, J]
@@ -131,7 +131,7 @@ private trait PropModifiers[F[_]](using F: Async[F]):
private val _forOptionSignalResourceProp =
Modifier.forSignalResource[F, Any, OptionSignalResourceModifier[F, Any, Any], Option[Any]](
- _.values) { (m, n) => setPropOption(n, m.name, m.codec) }
+ _.values) { (m, n) => setPropOption(n, m.name, m.encode) }
final class EventProp[F[_], E] private[calico] (key: String):
import EventProp.*
@@ -152,7 +152,7 @@ private trait EventPropModifiers[F[_]](using F: Async[F]):
final class ClassProp[F[_]] private[calico]
extends Prop[F, List[String], String](
"className",
- Codec.whitespaceSeparatedStrings
+ encoders.whitespaceSeparatedStrings
):
import ClassProp.*
diff --git a/calico/src/main/scala/calico/html/encoders.scala b/calico/src/main/scala/calico/html/encoders.scala
new file mode 100644
index 00000000..bd87938e
--- /dev/null
+++ b/calico/src/main/scala/calico/html/encoders.scala
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 Arman Bilge
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package calico
+package html
+
+private object encoders:
+ inline def identity[A]: A => A = _identity.asInstanceOf[A => A]
+ private val _identity: Any => Any = x => x
+
+ val whitespaceSeparatedStrings: List[String] => String = strings =>
+ if strings.isEmpty then ""
+ else
+ var acc = strings.head
+ var tail = strings.tail
+ while tail.nonEmpty do
+ acc += " " + tail.head
+ tail = tail.tail
+ acc
+
+ val booleanAsAttrPresence: Boolean => String =
+ if _ then "" else null
+
+ val booleanAsTrueFalseString: Boolean => String =
+ _.toString
+
+ val booleanAsYesNoString: Boolean => String =
+ if _ then "yes" else "no"
+
+ val booleanAsOnOffString: Boolean => String =
+ if _ then "on" else "off"
+
+ val doubleAsString: Double => String = _.toString
+
+ val intAsString: Int => String = _.toString
diff --git a/project/src/main/scala/calico/html/codegen/CalicoGenerator.scala b/project/src/main/scala/calico/html/codegen/CalicoGenerator.scala
index f9e0a6af..1a39f8a0 100644
--- a/project/src/main/scala/calico/html/codegen/CalicoGenerator.scala
+++ b/project/src/main/scala/calico/html/codegen/CalicoGenerator.scala
@@ -67,8 +67,8 @@ private[codegen] class CalicoGenerator(srcManaged: File)
override val codecsImport: String = ""
private def transformCodecName(codec: String) = codec match {
- case c if c.endsWith("AsIs") => s"Codec.identity[${c.dropRight(4)}]"
- case c => s"Codec.${c(0).toLower}${c.substring(1)}"
+ case c if c.endsWith("AsIs") => s"encoders.identity[${c.dropRight(4)}]"
+ case c => s"encoders.${c(0).toLower}${c.substring(1)}"
}
override def generateTagsTrait(
@@ -146,11 +146,11 @@ private[codegen] class CalicoGenerator(srcManaged: File)
val baseImplDef = if (tagType == SvgTagType) {
List(
- s"@inline private[calico] def ${baseImplName}[V](key: String, codec: Codec[V, String], namespace: Option[String]): ${keyKind}[V] = ${keyKindConstructor(keyKind)}(key, codec, namespace)"
+ s"@inline private[calico] def ${baseImplName}[V](key: String, encode: V => String, namespace: Option[String]): ${keyKind}[V] = ${keyKindConstructor(keyKind)}(key, encode, namespace)"
)
} else {
List(
- s"@inline private[calico] def ${baseImplName}[V](key: String, codec: Codec[V, String]): ${keyKind}[F, V] = ${keyKindConstructor(keyKind)}(key, codec)"
+ s"@inline private[calico] def ${baseImplName}[V](key: String, encode: V => String): ${keyKind}[F, V] = ${keyKindConstructor(keyKind)}(key, encode)"
)
}
@@ -198,7 +198,7 @@ private[codegen] class CalicoGenerator(srcManaged: File)
val (defs, defGroupComments) = defsAndGroupComments(defGroups, printDefGroupComments)
val baseImplDef = List(
- s"@inline private[calico] def ${baseImplName}[V, DomV](key: String, codec: Codec[V, DomV]): ${keyKind}[F, V, DomV] = ${keyKindConstructor(keyKind)}(key, codec)"
+ s"@inline private[calico] def ${baseImplName}[V, DomV](key: String, encode: V => DomV): ${keyKind}[F, V, DomV] = ${keyKindConstructor(keyKind)}(key, encode)"
)
val headerLines = List(
From 8ce27561a4c150c6b32cde5e0365e895137a2b91 Mon Sep 17 00:00:00 2001
From: Arman Bilge
Date: Fri, 27 Jan 2023 07:16:21 +0000
Subject: [PATCH 3/3] `Prop` and `HtmlAttr` are `Contravariant`
---
calico/src/main/scala/calico/html/HtmlAttr.scala | 10 ++++++++++
calico/src/main/scala/calico/html/Prop.scala | 11 +++++++++++
2 files changed, 21 insertions(+)
diff --git a/calico/src/main/scala/calico/html/HtmlAttr.scala b/calico/src/main/scala/calico/html/HtmlAttr.scala
index e00401e0..d7d27bdb 100644
--- a/calico/src/main/scala/calico/html/HtmlAttr.scala
+++ b/calico/src/main/scala/calico/html/HtmlAttr.scala
@@ -17,6 +17,7 @@
package calico.html
import cats.Contravariant
+import cats.Id
import cats.effect.kernel.Async
import cats.effect.kernel.Resource
import fs2.concurrent.Signal
@@ -40,7 +41,16 @@ sealed class HtmlAttr[F[_], V] private[calico] (key: String, encode: V => String
@inline def <--(vs: Resource[F, Signal[F, Option[V]]]): OptionSignalResourceModifier[F, V] =
OptionSignalResourceModifier(key, encode, vs)
+ @inline def contramap[U](f: U => V): HtmlAttr[F, U] =
+ new HtmlAttr(key, f.andThen(encode))
+
object HtmlAttr:
+ inline given [F[_]]: Contravariant[HtmlAttr[F, _]] =
+ _contravariant.asInstanceOf[Contravariant[HtmlAttr[F, _]]]
+ private val _contravariant: Contravariant[HtmlAttr[Id, _]] = new:
+ def contramap[A, B](fa: HtmlAttr[Id, A])(f: B => A): HtmlAttr[Id, B] =
+ fa.contramap(f)
+
final class ConstantModifier[V] private[calico] (
private[calico] val key: String,
private[calico] val encode: V => String,
diff --git a/calico/src/main/scala/calico/html/Prop.scala b/calico/src/main/scala/calico/html/Prop.scala
index 04ae12dd..fe49cf01 100644
--- a/calico/src/main/scala/calico/html/Prop.scala
+++ b/calico/src/main/scala/calico/html/Prop.scala
@@ -18,6 +18,8 @@ package calico
package html
import calico.syntax.*
+import cats.Contravariant
+import cats.Id
import cats.effect.kernel.Async
import cats.effect.kernel.Resource
import cats.syntax.all.*
@@ -46,7 +48,16 @@ sealed class Prop[F[_], V, J] private[calico] (name: String, encode: V => J):
vs: Resource[F, Signal[F, Option[V]]]): OptionSignalResourceModifier[F, V, J] =
OptionSignalResourceModifier(name, encode, vs)
+ @inline def contramap[U](f: U => V): Prop[F, U, J] =
+ new Prop(name, f.andThen(encode))
+
object Prop:
+ inline given [F[_], J]: Contravariant[Prop[F, _, J]] =
+ _contravariant.asInstanceOf[Contravariant[Prop[F, _, J]]]
+ private val _contravariant: Contravariant[Prop[Id, _, Any]] = new:
+ def contramap[A, B](fa: Prop[Id, A, Any])(f: B => A): Prop[Id, B, Any] =
+ fa.contramap(f)
+
final class ConstantModifier[V, J] private[calico] (
private[calico] val name: String,
private[calico] val encode: V => J,