Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ProductDerivation - constructWith method #11

Merged
merged 11 commits into from
May 27, 2024
20 changes: 20 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,26 @@ Calling `constuct`, specifying how each field's value will be computed, will ret
product, `DerivationType`. Since `Random` is a SAM type, this expression of `Long => DerivationType` provides
a suitable implementation for the new typeclass.

#### Monadic Producer Product Typeclasses

Often your producer will return `F[_]`, like `Option` or `Either`, in this example:
```scala
trait Parser[T]:
def parse(input: String): Either[Exception, T]
```
In this case there is a helper method called `constructWith`, which allows you to specify polymorphic `pure` and `bind`(aka flatMap) over your `F[_]` to help `constructWith` traverse producer typeclass results:
```scala
object Parser extends ProductDerivation[Parser] {
inline def join[DerivationType <: Product: ProductReflection]: Parser[DerivationType] = inputStr =>
constructWith[DerivationType, Either](
[MonadicTypeIn, MonadicTypeOut] => _.flatMap,
[MonadicType] => Right(_),
[FieldType] => context =>
context.parse(inputStr)
)
}
```

### Deriving Sum Types

Deriving sums, or coproducts, is possible by making a choice of which of their variants is represented by the
Expand Down
25 changes: 25 additions & 0 deletions src/core/wisteria.ProductDerivationMethods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,31 @@ trait ProductDerivationMethods[TypeclassType[_]]:
[FieldType] => context ?=> lambda[FieldType](context) *: accumulator
.reverse

protected transparent inline def constructWith[DerivationType <: Product, F[_]]
(using reflection: ProductReflection[DerivationType], requirement: ContextRequirement)
(inline bind: [MonadicTypeIn, MonadicTypeOut] => F[MonadicTypeIn] => (MonadicTypeIn => F[MonadicTypeOut]) => F[MonadicTypeOut],
inline pure: [MonadicType] => MonadicType => F[MonadicType],
inline lambda: [FieldType] =>
requirement.Optionality[TypeclassType[FieldType]] =>
(typeclass: requirement.Optionality[TypeclassType[FieldType]],
default: Default[Optional[FieldType]],
label: Text,
index: Int & FieldIndex[FieldType]) ?=>
F[FieldType])
: F[DerivationType] =

type Fields = reflection.MirroredElemTypes
type Labels = reflection.MirroredElemLabels

val resultingTuple: F[Tuple] = fold[DerivationType, Fields, Labels, F[Tuple]](pure(EmptyTuple), 0): accumulator =>
[FieldType] => context ?=>
bind(accumulator): acc =>
bind(lambda[FieldType](context)): result =>
pure(result *: acc)

bind(resultingTuple): tuple =>
pure(reflection.fromProduct(tuple.reverse))

protected transparent inline def contexts[DerivationType <: Product]
(using reflection: ProductReflection[DerivationType], requirement: ContextRequirement)
[ResultType]
Expand Down
1 change: 1 addition & 0 deletions src/core/wisteria.SumDerivationMethods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ trait SumDerivationMethods[TypeclassType[_]]:
val size: Int = valueOf[Tuple.Size[reflection.MirroredElemTypes]]
val variantLabel = label

// Here label comes from context of fold's predicate
fold[DerivationType, Variants, Labels](variantLabel, size, 0, true)(label == variantLabel):
[VariantType <: DerivationType] => context => lambda[VariantType](context)
.vouch(using Unsafe)
Expand Down
42 changes: 39 additions & 3 deletions src/test/tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import rudiments.*
import contingency.*
import vacuous.*

import scala.util.Try

//object Month:
//given Presentation[Month] = _.toString.tt

Expand Down Expand Up @@ -116,6 +118,32 @@ object Eq extends Derivation[Eq]:
complement(right).lay(false): rightValue =>
leftValue === rightValue

trait Parser[ValueType] {
def parse(s: String): Option[ValueType]
}

object Parser extends ProductDerivation[Parser] {
given Parser[Int] with
def parse(s: String): Option[Int] = s.toIntOption

given Parser[Boolean] with
def parse(s: String): Option[Boolean] = s.toBooleanOption

inline def join[DerivationType <: Product: ProductReflection]: Parser[DerivationType] = inputStr =>
IArray.from(inputStr.split(',')).pipe: inputArr =>
constructWith[DerivationType, Option](
[MonadicTypeIn, MonadicTypeOut] => _.flatMap,
[MonadicType] => Some(_),
[FieldType] => context =>
if index < inputArr.length then
context.parse(inputArr(index))
else
None
)
}

case class ParserTestCaseClass(intValue: Int, booleanValue: Boolean)

@main
def main(): Unit =
val george = Person("George Washington".tt, 61, true)
Expand All @@ -130,7 +158,7 @@ def main(): Unit =

import Tree.*
println(Branch(4, Branch(1, Leaf, Branch(2, Leaf, Leaf)), Leaf).present)

given Errant[VariantError] = errorHandlers.throwUnsafely

println("President:Richard Nixon,37".tt.read[Human].present)
Expand All @@ -149,5 +177,13 @@ def main(): Unit =
val human3 = "Person:george washington,1,yes".tt.read[Human]
println(human1 === human2)
println(human2 === human3)
val human4 = "Broken:george washington,1,yes".tt.read[Human]

val human4 = Try("Broken:george washington,1,yes".tt.read[Human])
println(human4.isFailure)
println(human4.failed.get.getMessage())

println("withContext:")
val parserForTest = summon[Parser[ParserTestCaseClass]]
val successfulParse = parserForTest.parse("120,false")
println(successfulParse.exists(_.intValue == 120))
println(successfulParse.exists(_.booleanValue == false))
println(parserForTest.parse("error").isEmpty)
Loading