nikita-volkov / record

Anonymous records
http://hackage.haskell.org/package/record
MIT License
245 stars 8 forks source link

Strict records are not supported #12

Open passy opened 9 years ago

passy commented 9 years ago

Total Haskell beginner here, so please excuse my ignorance if this is something obvious or unrelated. :)

This vanilla example compiles just fine

data Cache k v =
    Cache { capacity :: !Int
          , queue    :: !(HashPSQ.HashPSQ k Priority v)
          }

… but when wrapping it with Record like this

data Cache k v =
    Cache [record| { capacity :: !Int
                   , queue    :: !(HashPSQ.HashPSQ k Priority v)
          } |]

… I see this error

Types.hs:12:19:
    Parser failure: Failed reading: satisfy. Contexts: ["type'","'('"]

… referring to the queue declaration. I guess this would also benefit from #4, but I'm not sure whether this is me doing something wrong or whether the parser doesn't understand this construct.

nikita-volkov commented 9 years ago

Strict records are not yet supported. However I plan to implement support for them in the next major release.

passy commented 9 years ago

@nikita-volkov Excellent, thanks for the reply. I updated the title accordingly, if you want to keep this for your backlog. :)

rabipelais commented 9 years ago

@nikita-volkov how are you planning to do that? As I see it, the strictness specifier lives outside of the type. For example, for a data constructor, NormalC expects [(Strictness, Type)], but the Record quasiquoter only goes upto the type. Am I seeing something wrong?

nikita-volkov commented 9 years ago

@rabipelais Simple. Same as with tuples, there'll be two kinds of records: the one with all fields lazy and the one with all fields strict. The development branch demonstrates the projected syntax for both. The discussion and criticism is welcome.

aavogt commented 9 years ago

If you want specific fields to be lazy while others are not, you could wrap the lazy field in data Thunk a = Thunk a. Maybe it's worth the trouble to have the quasiquoter automagically wrap/unwrap Thunk so that accessing a lazy field foo doesn't mean writing [l|foo.unthunk1|]. For example [l|foo|] might expand out to whateverFooDoesRightNow . unthunk. unthunk and unthunk1 might be defined like in https://gist.github.com/aavogt/3d4347f4fc8ca850c6a0

nikita-volkov commented 9 years ago

@aavogt Seems like it would require too much sacrifices for little gain. Introduction of implicit "unthunk" would make the machinery behind records much harder to understand, while an important point of this project is the simplicity of the concept. Also this would introduce a memory footprint overhead for lazy records, thus nullifying a very important advantage of the library compared to HList-based solutions.

aavogt commented 9 years ago

I guess you can avoid the one extra pointer introduced by Thunk if you have newtypes that all involved functions actually listen to when deciding a value should be forced:

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MultiParamTypeClasses #-}
import Control.Lens
newtype Lazy a = Lazy a deriving Show
newtype Bang a = Bang a deriving Show

-- not sure if anybody cares about the outer Bang/Lazy
_1BL f (Bang (Lazy a,b)) = f a <&>  \a' -> Bang (Lazy a', b)
_1BB f (Bang (Bang !a,b)) = f a <&> \ !a' -> Bang (Bang a', b)

_1LL f (Lazy ~(Lazy a,b)) = f a <&>  \a' -> Lazy (Lazy a', b)
_1LB f (Lazy ~(Bang !a,b)) = f a <&> \ !a' -> Lazy (Bang a', b)

class F1 o i where
    f1 :: Lens (o (i x,y)) (o (i x',y)) x x'

instance F1 Bang Lazy where f1 = _1BL
instance F1 Bang Bang where f1 = _1BB
instance F1 Lazy Bang where f1 = _1LB
instance F1 Lazy Lazy where f1 = _1LL

I'd be interested if you can actually measure data Thunk slowing things down: in the last benchmark in http://code.haskell.org/~aavogt/HList-benchmark/a.html, lookups in HList don't show up as slower than tuples/data until you get up to 10 elements, is 10 extra pointer dereferences as far as I know.