spartanz / schemaz

A purely-functional library for defining type-safe schemas for algebraic data types, providing free generators, SQL queries, JSON codecs, binary codecs, and migration from this schema definition
https://spartanz.github.io/schemaz
Apache License 2.0
164 stars 18 forks source link

#6 Flesh out the GenModule #19

Closed jkobejs closed 6 years ago

jkobejs commented 6 years ago

Closes #6.

LGLO commented 6 years ago

I appreciate this is PR is still a WIP, but I'll share my personal feeling now, to give feedback early. I wish to see comments. Perhaps most of functions have only one sensible implementation that type checks but nevertheless natural language explanation would help in understanding the code.

Could you also explain why do we need Free Applicative in this case? I know that FP ninjas know this already but I'm not one of them.

I understand you've written this code in free time and caring for noobs is not your top-prio, but I think that comments could enable more people to contribute here.

Small parts that I do understand looks good so far.

jkobejs commented 6 years ago

You are right @LGLO, it would be good to have comments where they are needed and usage of free applicative requires explanation.

At first we defined record schema as nonempty list of record fields. But that was not expressive enough. Imagine that you have person record that looks like

case class Person(name: String, age: Int)

and you want to generate Gen[Person] out of your Schema[Person]. If you do it by hand first thing what you would do is to create gens for person fields, Gen[String] and Gen[Int]. Now if Gen is applicative it is relatively easy to create Gen[Person] out of gens for fields. You could create it if Gen is monad but applicative fits here naturally since we don't need sequential computation to create this object. You are done now.

But what to do if you want to have generic implementation? By just looking at record schema for type A and list of it fields you don't know how to create A if you have values of its fields. That is why I used free applicative that has fields Field[A, A0] for its algebra. If you have free applicative you can easily interpret it to some other type constructor F[_] if F is applicative using foldMap method that has signature:

  def foldMap[G[_]:Applicative](f: F ~> G): G[A] 

That is what I did in Gen module. I defined applicative for gen, natural transformation from field algebra to gen and used foldMap to get gen for record type. I hope that this helps.

I believe that remaining methods are straightforward and that you don't need explanation for them and code comments since those methods are private and oneliners. Maybe more descriptive name would help, if you have suggestions just comment on them.

vil1 commented 6 years ago

@josipgrgurica thanks for your work and for your helpful comment above.

@LGLO did Josip's comment make things clearer to you ? My intuition is that every meaningful operation on records need to take place in an Applicative context (that's merely an intuition and I would be unable to give it a stricter explanation, but in my mind, products and applicatives are strongly connected).

@josipgrgurica I think it would be useful to keep track of this kind of explanations of our design decisions somewhere. We don't have documentation laid out yet, but I think it would be worthwhile to copy your explanations in a DESIGN_NOTES.md somewhere for now.

jkobejs commented 6 years ago

@vil1 You're welcome. Sure, I also think that it would be useful to have design decisions documented, should I do it as part of this PR?

vil1 commented 6 years ago

Ideally, yes. I think you can just paste your explanation in a DESIGN_NOTES.md at the root of the project for now.

vil1 commented 6 years ago

You'll have to run scalafmt too in order to please Mr Travis :)

jkobejs commented 6 years ago

Closing and opening new to fire Travis CI.