-
Notifications
You must be signed in to change notification settings - Fork 70
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
non-orphan implicit typeclasses #912
Conversation
MovieTheater.apply | ||
}.withId(id).addHints(hints) | ||
|
||
implicit val movieTheaterEq: cats.Eq[MovieTheater] = EqInterpreter.fromSchema(schema) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the main thing to look at here. I still have a ways to go on this PR, but I wanted to put it up now to get early feedback before I get further along with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me.
ohh la la. I may like this. |
|
||
val precompiler = new Alt.Precompiler[Schema, AltEq] { | ||
def apply[A](label: String, instance: Schema[A]): AltEq[A] = { | ||
// Here we "cheat" to recover the `Alt` corresponding to `A`, as this information |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we add support for this in Precompiler
?
} | ||
} | ||
|
||
val precompiler = new Alt.Precompiler[Schema, AltEq] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: usually I just inline this
def primitiveEq[P](primitive: Primitive[P]): Eq[P] = { | ||
primitive match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This smells an awful lot like Primitive.deriving
.
val eqA: Eq[A] = self(schema) | ||
(x: B, y: B) => eqA.eqv(refinement.from(x), refinement.from(y)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val eqA: Eq[A] = self(schema) | |
(x: B, y: B) => eqA.eqv(refinement.from(x), refinement.from(y)) | |
self(schema).contramap(refinement.from) |
same in bijections
): Eq[S] = { (x: S, y: S) => | ||
{ | ||
def forField[A2](field: Field[Schema, S, A2]): Boolean = { | ||
val eqField = field.foldK(new Field.FolderK[Schema, S, Eq]() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: since this is an example we probably want to follow best practices, so I'd suggest some more "precompiled" way: make forField
return an S => Boolean
and move the (x: S, y: S) =>
below so that we traverse the fields while instantiating the whole thing.
implicit val valueEq: Eq[V] = self(value) | ||
Eq[Map[K, V]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kinda interesting how they made Maps comparable without an Eq
for the key
tag match { | ||
case CollectionTag.ListTag => Eq[List[A]] | ||
case CollectionTag.SetTag => Eq[Set[A]] | ||
case CollectionTag.VectorTag => Eq[Vector[A]] | ||
case CollectionTag.IndexedSeqTag => Eq[IndexedSeq[A]] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought: we could have a CollectionTag.deriving
in a similar spirit to Primitive.deriving
MovieTheater.apply | ||
}.withId(id).addHints(hints) | ||
|
||
implicit val movieTheaterEq: cats.Eq[MovieTheater] = EqInterpreter.fromSchema(schema) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me.
modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala
Outdated
Show resolved
Hide resolved
sorry for the comment spam @lewisjkl, this is looking great |
modules/example/src/smithy4s/example/typeclass/EqInterpreter.scala
Outdated
Show resolved
Hide resolved
modules/protocol/resources/META-INF/smithy/smithy4s.meta.smithy
Outdated
Show resolved
Hide resolved
No worries @kubukoz I actually copied that |
@@ -280,6 +280,8 @@ private[internals] object Hint { | |||
case object IndexedSeq extends SpecializedList | |||
} | |||
case object UniqueItems extends Hint | |||
case class Typeclass(id: ShapeId, targetType: String, interpreter: String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nice-to-have would be a type alias wherever a String refers to a Class (for doc purposes)
Perhaps this should extend to the Smithy definitions too
hints: List[Hint], | ||
tpe: NameRef | ||
): Option[Lines] = { | ||
(hints.collectFirst { case h: Hint.Typeclasses => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find collectFirst to be a bit misleading. There can be only one ,of type Hint.Typeclass and if there could be more , I think we would want them all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not misleading, I think it's just wrong. It should be collect
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh of course .I was thinking this was Hints as opposed to a List of hint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's right. There should only ever be one Typeclasses hint which may contain multiple 'Typeclass' instances. It doesn't need to be modeled that way, but that's how I did it so we'd keep the idea that you can only have 1 of a given trait/hint per shape. Collect first is just so I could do the find and map at the same time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but that's how I did it so we'd keep the idea that you can only have 1 of a given trait/hint per shape
I've "violated" that assumption in my commit 😅. For non-meta hints, I think you are correct, but there is a distinction to be had between hints that get rendered and available at runtime and hints that aren't. Trying to abide by the invariant at codegen time seems to be detrimental to the readability of the code .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, sorry ,should have amended my comment. It wasn't "wrong", I was just confused between the existence of a Typeclass
and a Typeclasses
type, which got mixed together in my brain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that totally makes sense, I think it's clearer without the extra typeclasses hint so works for me 👍🏼
private def renderTypeclass(hint: Hint.Typeclass, tpe: NameRef): Line = { | ||
val target = NameRef(hint.targetType) | ||
val interpreter = NameRef(hint.interpreter) | ||
val lowerCasedName = s"${tpe.name.head.toLower.toString}${tpe.name.tail}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have a helper for this : uncapitalise
private def renderTypeclasses( | ||
hints: List[Hint], | ||
tpe: NameRef | ||
): Option[Lines] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can return Lines
removing EqSchemaVisitor in favour of a universalEquals-based implementation.
Build fails because docs are currently linking a non-existing class. We'll wait for the cats-module to be merged to go back to this PR. |
Will add changelog for this and #942 on Monday |
Making it so typeclass instances can be added in companion objects in the generated code. This uses a mechanism similar to refinements to specify the location of the typeclass and the interpreter that provides it.
Will add more details as this PR comes out of draft status.