VinylRecords / Vinyl

Extensible Records for Haskell. Pull requests welcome! Come visit us on #vinyl on freenode.
http://hackage.haskell.org/package/vinyl
MIT License
262 stars 48 forks source link

More convenient construction #111

Open heptahedron opened 6 years ago

heptahedron commented 6 years ago

I took a look at the HList lib, which I had heard contained some polyvariadic constructors, because I thought they would significantly help with Vinyl, where currently construction is fairly verbose (since all fields must be wrapped in some data constructor in addition to the noise brought about from :& and RNil).

Here's my first crack at it, is this something that would be accepted if I were to make a pull request?

https://gist.github.com/heptahedron/45c188e7528d9438ec9ae56ecaecd6ec

acowley commented 6 years ago

I agree that construction is annoyingly awkward. I use variadic functions in a couple projects and I'm not against them, but nor am I totally in love. The typical drawback is that you need to write a lot of types, but perhaps that wouldn't be much of a change for most uses of vinyl.

Can you take a look at adding some unit test examples and/or updating existing tests to use the builder to make sure it slots in without friction? If everything is working, I'd be tempted to add this as a function named vinyl. In particular, let's make sure one can write, vinyl (#name =: "joe") (#age =: 99) as building ElField records is brutally noisy with all those symbols.

sboosali commented 6 years ago

yeah, I don't like polyvariadic functions either, and we need test cases (/ examples) that infer correctly (without annotations). also

1) shorter aliases for (:&) and RNil can help. e.g.

  (#name =: "joe") ^ (#age =: 99) ^ nil

as well as aliases for grouping record-cons with a constructor/destructor of commonly used functors. like Identity, Const (when converting to/from), ElField (as above), Lazy versus the default strict, Compose, etc. patterns give us constructors that can be matched on too.

e.g.

1 :* 2 :* 3 :* Z
-- inferred:
-- :: (Num a, Num b, Num c) => Rec Identity [a,b,c]

given:

pattern (:*) :: a -> Rec I as -> Rec I (a ': as)
pattern (:*) x xs = I x :& xs
infixr 7 :*

pattern Z :: forall (f :: k -> *). Rec f '[]
pattern Z = RNil

pattern (:#) :: forall a (b :: k) (bs :: [k]). a -> Rec (C a) bs -> Rec (C
a) (b ': bs)
pattern (:#) x xs = C x :& xs
infixr 7 :#

...

see https://github.com/sboosali/export/blob/master/sources/Export/Vinyl.hs#L49

and, identity function helpers for specializing overloaded literals; like the default-ly overloaded integers, and the commonly overloaded strings. since, depending on how generic the functions consuming an overloaded record are, you'll get type defaulting and type errors (like from print) e.g.

int :: Int -> Identity Int
text :: Text -> Identity Text

int 1 :& text "hello" :& nil

or

pattern Int :: Int -> Identity Int
pattern Text :: Text -> Identity Text

for pattern matching too, as above.

2) imo, the sugar of a method that is overloaded over tuples of any size is cleaner than overloading on arity (i.e. an "uncurried" version of your "vinyl" method).

e.g.

record (#name =: "joe", #age =: 99)

as pseudo-OverloadedTuples. related: https://github.com/sboosali/export/blob/master/sources/Export/Curry.hs#L16

the benefit to this is that syntactically, tuples are more obviously related to records, and more impor/tantly, inference is better.

Or, given a module that instantiates labels as Field constructors rather than Proxy, which has a Clojure-like look. e.g.

record
 ( #name "joe"
   , #age 99
   )

we can specialize the record method above to ElField, or keep it general to n-tuples of any functor. I can draw this out tomorrow.

so anyways, I strongly agree with vinyl needing better "officially recommended" syntax, but I wanted to propose some alternatives to polyvariadic functions (which again, are hard to read and cause inference problems for me, whenever I use them, iirc format and sh).

On Wed, Feb 21, 2018 at 7:12 PM, Anthony Cowley notifications@github.com wrote:

I agree that construction is annoyingly awkward. I use variadic functions in a couple projects and I'm not against them, but nor am I totally in love. The typical drawback is that you need to write a lot of types, but perhaps that wouldn't be much of a change for most uses of vinyl.

Can you take a look at adding some unit test examples and/or updating existing tests to use the builder to make sure it slots in without friction? If everything is working, I'd be tempted to add this as a function named vinyl. In particular, let's make sure one can write, vinyl (#name =: "joe") (#age =: 99) as building ElField records is brutally noisy with all those symbols.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/VinylRecords/Vinyl/issues/111#issuecomment-367553869, or mute the thread https://github.com/notifications/unsubscribe-auth/ACNoMXwCC-PMtxfFeKA3gjYKnknPY6P0ks5tXNsOgaJpZM4SOqVF .

--

(this message was composed with dictation: charitably interpret typos)Sam Boosalis

acowley commented 6 years ago

Those are good suggestions to think about, @sboosali, thanks for bringing them up! Personally, I'm weakly negative on more operators as I have trouble keeping them straight in my memory. I also wouldn't want to shadow (^) from Prelude.

I used to be more concerned about the functor zoo with vinyl, but, for myself, where I really want the concise syntax is always Identity. I don't know how representative that experience is, but when I want Const or Maybe or anything less common, I'm fine with writing it out because it's probably worth drawing attention to the deviation from the far more common (for me) use of Identity.

I'd also be fine with the uncurried approach, and that (#name "joe", #age 99) syntax is striking me as quite appealing. Does that really fit into things cleanly?

sboosali commented 6 years ago

I agree about being explicit about the functors, and I use Compose and ElField myself more (than those I listed).

And, I wasn't actually suggesting shadowing anything from prelude :-), just that a one-char thing looks nicer (like #, etc).

[Tuple syntax]

I think I did it when ghc8 came out, but I can find it or double check. You create a IsLabel instance for ElField constructors (which shadows other similar syntax hacks that instantiate IsLabel for ((->) a)).

It's not clean in that: the field sugar either requires overlapping instances, aggressively exporting the instance, or (my preference) just adding an orphan instance for people to explicitly voluntarily import; and the tuple sugar itself requires an arbitrary (finite) maximum tuple size supported.

Anyways, let me try it out (sorry for the delay, I was going to do it this weekend).

On Feb 26, 2018 6:36 PM, "Anthony Cowley" notifications@github.com wrote:

Those are good suggestions to think about, @sboosali https://github.com/sboosali, thanks for bringing them up! Personally, I'm weakly negative on more operators as I have trouble keeping them straight in my memory. I also wouldn't want to shadow (^) from Prelude.

I used to be more concerned about the functor zoo with vinyl, but, for myself, where I really want the concise syntax is always Identity. I don't know how representative that experience is, but when I want Const or Maybe or anything less common, I'm fine with writing it out because it's probably worth drawing attention to the deviation from the far more common (for me) use of Identity.

I'd also be fine with the uncurried approach, and that (#name "joe", #age 99) syntax is striking me as quite appealing. Does that really fit into things cleanly?

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/VinylRecords/Vinyl/issues/111#issuecomment-368726578, or mute the thread https://github.com/notifications/unsubscribe-auth/ACNoMdbbpQxafr7IFvH4cvMfJL_pPwb5ks5tY2o7gaJpZM4SOqVF .