Skip to content

Commit 20ecbcc

Browse files
committed
update README.md
1 parent 880e7a9 commit 20ecbcc

1 file changed

Lines changed: 83 additions & 0 deletions

File tree

README.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,89 @@ CaseComplete uses Scala 3's macro system to:
6262
2. **Compile-time Validation**: When you call `.compile()`, it verifies all case class fields have handlers
6363
3. **Field Name Extraction**: Extracts field names from selectors like `_.fieldName` at compile time
6464

65+
## CaseComplete vs Pattern Matching
66+
67+
While pattern matching on case classes is a powerful Scala feature, it has limitations when it comes to ensuring complete field handling. CaseComplete provides **dual-purpose functionality**: it not only allows you to implement transformations that are validated at compile-time, but also provides an interface that guarantees every implementation will have these properties.
68+
69+
### The Problem with Pattern Matching
70+
71+
Pattern matching on case classes is just a specific implementation of `A => B` functions. **You cannot enforce the use of pattern matching at the interface level** - the interface only specifies the function signature, not how it should be implemented. This means there's no compile-time guarantee that all fields will be handled.
72+
73+
```scala
74+
abstract class AbstractRepository[ENTITY_TYPE, FILTER_TYPE, UPDATE_TYPE](
75+
tableName: String,
76+
evalFilter: FILTER_TYPE => Set[Fragment],
77+
evalUpdate: UPDATE_TYPE => Set[Fragment]
78+
) {
79+
// some methods etc
80+
}
81+
82+
case class MovieFilter(
83+
title_like: Option[String] = None,
84+
director_eq: Option[String] = None,
85+
releaseYear_eq: Option[Year] = None,
86+
rating_gte: Option[Double] = None
87+
)
88+
89+
case class MovieUpdate(
90+
title: Option[String],
91+
rating: Option[Double],
92+
cast: Option[List[Person]]
93+
)
94+
95+
object MovieRepository extends AbstractRepository[Movie, MovieFilter, MovieUpdate] (
96+
tableName = "movies",
97+
evalFilter = {
98+
case MovieFilter(title_like, director_eq, releaseYear_eq, rating_gte) => List(
99+
title_like.map(title => fr"title ILIKE $title"),
100+
director_eq.map(director => fr"director = $director"),
101+
releaseYear_eq.map(year => fr"release_year = $year"),
102+
rating_gte.map(rating => fr"rating >= $rating")
103+
).flatten.toSet
104+
}, // This is good - we'll have to extend pattern matching if fields are added
105+
evalUpdate = update => {
106+
List(
107+
update.title.map(title => fr"title = $title"),
108+
update.cast.map(cast => fr"cast = $cast")
109+
).flatten.toSet
110+
} // Whoops - the `rating` field is not handled, but it still compiles!
111+
)
112+
```
113+
114+
### The CaseComplete Solution
115+
116+
With CaseComplete, you can define the `AbstractRepository` to enforce complete field handling:
117+
118+
```scala
119+
abstract class AbstractRepository[ENTITY_TYPE, FILTER_TYPE, UPDATE_TYPE](
120+
tableName: String,
121+
evalFilter: CaseComplete[FILTER_TYPE, Option[Fragment]],
122+
evalUpdate: CaseComplete[UPDATE_TYPE, Option[Fragment]]
123+
) {
124+
// some methods etc
125+
}
126+
```
127+
128+
Now, providing an implementation that isn't validated at compile-time is **impossible**. All classes that inherit from `AbstractRepository` must provide implementations that handle every field:
129+
130+
```scala
131+
object MovieRepository extends AbstractRepository[Movie, MovieFilter, MovieUpdate] (
132+
tableName = "movies",
133+
evalFilter = CaseComplete.build[MovieFilter, Option[Fragment]]
134+
.usingNonEmpty(_.title_like)(title => fr"title ILIKE $title")
135+
.usingNonEmpty(_.director_eq)(director => fr"director = $director")
136+
.usingNonEmpty(_.releaseYear_eq)(year => fr"release_year = $year")
137+
.usingNonEmpty(_.rating_gte)(rating => fr"rating >= $rating")
138+
.compile,
139+
evalUpdate = CaseComplete.build[MovieUpdate, Option[Fragment]]
140+
.usingNonEmpty(_.title)(title => fr"title = $title")
141+
.usingNonEmpty(_.rating)(rating => fr"rating = $rating")
142+
.usingNonEmpty(_.cast)(cast => fr"cast = $cast")
143+
.compile
144+
)
145+
```
146+
147+
65148
## API Reference
66149

67150
### CaseComplete.build

0 commit comments

Comments
 (0)