Re: [Dev] Concerns regarding the mechanism

2015-08-03 Thread Bryan Richter
On Sat, Aug 01, 2015 at 03:08:47AM +0300, Nikita Karetnikov wrote:
  Oh, wow. Thanks for the tip. I will probably do that. Even after
  spending a few hours on this esqueleto, it will still save me time to
  switch.
 
  Just gotta figure out how to share database connection information...
 
 Is it really that bad?  If it's a major slowdown, just go ahead, but
 paste what you've tried.  Also, I'd suggest moving a subselect into a
 separate Haskell function, even with strings.

Here's what is happening so far:

-- | Find underfunded patrons.
-- This function is used after something may have broken the invariant that
-- all funded pledges are covered by the patron's account balance. It 
returns
-- pertinent information about each underfunded pledge.
--
underfundedUsers :: (MonadIO m, Functor m)
 = ProjectId
 - SqlPersistT m [Underfunded]
underfundedUsers projId = do
-- the inner query
totalOutlays - fmap processOutlayRows $
select $
from $ \(pl `InnerJoin` pr `InnerJoin` u `InnerJoin` ac) - do
on_ $ ac ^. AccountId ==. u ^. UserAccount
on_ $ u ^. UserId ==. pl ^. PledgeUser
on_ $ pr ^. ProjectId ==. pl ^. PledgeProject
where_ $ pl ^. PledgeFundedShares . val 0
groupBy $ (u ^. UserId)
return (u ^. UserId
   ,sum_ (($*.) (pr ^. ProjectShareValue)
(pl ^. PledgeFundedShares))
   )
let process' = processIntermediateRows totalOutlays
intemediate - fmap process' $
select $
from $ \(pl `InnerJoin` u `InnerJoin` ac `InnerJoin` pr) - do
on_ $ pr ^. ProjectId ==. pl ^. PledgeProject
on_ $ ac ^. AccountId ==. u ^. UserAccount
on_ $ u ^. UserId ==. pl ^. PledgeUser
return (u ^. UserId
   ,ac ^. AccountBalance
   ,pl ^. PledgeFundedShares
   ,pr ^. ProjectId
   ,pr ^. ProjectShareValue
   ,ac ^. AccountId)
  where
-- | Just cleans up types and removes rows that returned Nothing
-- for the sum. (There actually shouldn't be any because of the inner
-- join, but whatevs.)
processOutlayRows :: [(Value UserId, Value (Maybe Milray))]
  - Map UserId Milray
processOutlayRows = M.fromList
  . catMaybes
  . map sequence -- 'lift' the Maybe
  . unwrapValues

-- | Primarily calculates the available balance for each pledge.
-- That is defined, for each pledge, as the difference between the
-- patron's total balance and the sum of the outlay going to the
-- patron's *other* pledges.
processIntermediateRows :: Map UserId Milray
- [(Value UserId
,Value Milray
,Value Int64
,Value ProjectId
,Value Milray
,Value AccountId)]
- [Underfunded]
processIntermediateRows totalOutlays =
map ( (\(u, a, p, v) - Underfunded u a p v))
. map getAvailable
. unwrapValues
)
  where
getAvailable (UserId, Milray, Int64, ProjectId, Milray, AccountId) =
-- XXX TODO
undefined

 Note that with esqueleto, if you change the schema, you'll get a compile
 time warning.  While with strings, it'll happen at runtime.

True, but I have to accept that risk right now. Tests will help.


signature.asc
Description: Digital signature
___
Dev mailing list
Dev@lists.snowdrift.coop
https://lists.snowdrift.coop/mailman/listinfo/dev


Re: [Dev] Concerns regarding the mechanism

2015-08-01 Thread Curtis Gagliardi
Did Bryan reply offlist?  What is that a reply to?

On Fri, Jul 31, 2015, at 05:08 PM, Nikita Karetnikov wrote:
  Oh, wow. Thanks for the tip. I will probably do that. Even after
  spending a few hours on this esqueleto, it will still save me time to
  switch.
 
  Just gotta figure out how to share database connection information...
 
 Is it really that bad?  If it's a major slowdown, just go ahead, but
 paste what you've tried.  Also, I'd suggest moving a subselect into a
 separate Haskell function, even with strings.
 
 Note that with esqueleto, if you change the schema, you'll get a compile
 time warning.  While with strings, it'll happen at runtime.
 ___
 Dev mailing list
 Dev@lists.snowdrift.coop
 https://lists.snowdrift.coop/mailman/listinfo/dev
___
Dev mailing list
Dev@lists.snowdrift.coop
https://lists.snowdrift.coop/mailman/listinfo/dev


Re: [Dev] Concerns regarding the mechanism

2015-07-31 Thread Aaron Wolf

On 07/31/2015 03:17 PM, Bryan Richter wrote:
 On Thu, Jul 30, 2015 at 05:18:00PM +0300, Nikita Karetnikov wrote:
 I have to admit the current mechanism implementation makes me uneasy.
 Everything is mutable and some functions, like underfundedPatrons, do
 too much, making it hard to test and even follow.
 
 Incidentally, where's what I would like underfundedPatrons to look
 like:
 
 select * from (
 select pl.user
 , balance - (outlay.tot - (funded_shares * share_value)) as avail
 , pr.id as project
 , pr.share_value

This isn't certain to be back-end accurate terms: I don't object
strongly, but share_value still seems opaque. It isn't a value that is
ever specified by itself, it just is a variable that is a synonym of (
num-active-patrons * min-pledge-level ), right? I think I'm okay with
keeping it if it isn't confusing anyone else, but we need the definition
to be really easy to find at least…

 , pl.funded_shares
 , ac.id account
 from pledge pl
 join user u on pl.user = u.id
 join account ac on ac.id = u.account
 join project pr on pl.project = pr.id
 join (
 select u.id as user, sum(funded_shares * share_value) as tot
 from pledge pl
 join project pr on pl.project = pr.id
 join user u on pl.user = u.id
 join account ac on u.account = ac.id
 group by u.id
 ) outlay on outlay.user = u.id
 ) as q
 where avail  share_value * funded_shares
 order by q.user
 
 This is a pure function of the underlying tables (except perhaps for
 the use of share_value, which is strictly speaking non-normalized and
 thus spooky). It could be easily tested in isolation. The subquery q
 could be turned into a database view and thus encapsulated.
 
 The problem is I don't know how to interface this query with the
 Haskell code, yet. I would very much like to. Thanks to years of
 experience, I can write queries like this in a matter of minutes, but
 it still takes me hours to hammer them into the persistent/esqueleto
 mold.
 
 I also want compile time guarantees (types). There's a un(i)typed
 multiplication in there between funded_shares and share_value that
 should be a compile-time error. The sum itself is another potential
 type error, since in general it could return null (Nothing) — although
 I think it never will in this case. I should also be able to ensure
 that every use of this query expects the correct result types.
 
 So... any thoughts? For now I think I'll just crash through some more
 persistent and esqueleto to end up with the equivalent data. If anyone
 wants to look into this, though, I'm pretty sure you'd get good
 support from upstream.
 
 -*-*-*- holy crap -*-*-*-
 
 This is what I want:
 
 https://github.com/tomjaguarpaw/haskell-opaleye
 
 Using Opaleye would probably require some deep restructuring of the
 Snowdrift Model code, so I'll hold off for now. I do hope we can move
 towards using it, though.
 
 

Not qualified to really weigh in, but Opaleye sounds great on the
surface. Seems reasonable to me to keep it in mind and start using it /
replacing things with it if it's better. As long as we keep all the
Haskell functional / typed goodness, I'm all for whatever is smoothest.


 
 ___
 Dev mailing list
 Dev@lists.snowdrift.coop
 https://lists.snowdrift.coop/mailman/listinfo/dev
 

-- 
Aaron Wolf Snowdrift.coop https://snowdrift.coop
___
Dev mailing list
Dev@lists.snowdrift.coop
https://lists.snowdrift.coop/mailman/listinfo/dev


Re: [Dev] Concerns regarding the mechanism

2015-07-31 Thread Bryan Richter
On Fri, Jul 31, 2015 at 02:41:41PM -0700, Curtis Gagliardi wrote:
 I'd probably drop down to postgres-simple and run that query as is.  I
 don't think the lack of typing needs to be a deal breaker if it's going
 to be easier to write and easier to test.  

Oh, wow. Thanks for the tip. I will probably do that. Even after
spending a few hours on this esqueleto, it will still save me time to
switch.

Just gotta figure out how to share database connection information...


signature.asc
Description: Digital signature
___
Dev mailing list
Dev@lists.snowdrift.coop
https://lists.snowdrift.coop/mailman/listinfo/dev


Re: [Dev] Concerns regarding the mechanism

2015-07-30 Thread fr33domlover
On 2015-07-30
Nikita Karetnikov nik...@karetnikov.org wrote:

 I have to admit the current mechanism implementation makes me uneasy.
 Everything is mutable and some functions, like underfundedPatrons, do
 too much, making it hard to test and even follow.
 
 When I start a new Haskell program, I usually try to express the core
 idea with types, making illegal states unrepresentable.  Then, I write
 pure functions connecting the said types together, which compute the
 result.  IO is only used on the edges (to receive and send the data).
 This is a very common technique.  Can't we use it for the mechanism?
 
 How do you feel about this?  I think the first step should be the
 rigorous spec of the things we care about, i.e., anything touching
 money.  Then we follow the process described above.  Testing becomes
 trivial because the core stuff is pure while IO actions are just
 reads/writes.


Sounds good to me (but I'm probably not the person to ask). I just wanted to
express a little thought: One of the strengths of Haskell is the advanced type
system and all the static verification it can give you. It means critical
things can be verified statically instead of relying on tests to catch them
(Yesod in general seems to take this approach seriously).

All the m0ney related stuff is probably at least somewhat critical, so perhaps
more work with types could replace some tests.

Just a random idea, I don't know how the code actually works right now :P


---
fr33domlover http://www.rel4tion.org/people/fr33domlover
GPG key ID:  63E5E57D (size: 4096)
GPG key fingerprint: 6FEE C222 7323 EF85 A49D  5487 5252 C5C8 63E5 E57D


signature.asc
Description: PGP signature
___
Dev mailing list
Dev@lists.snowdrift.coop
https://lists.snowdrift.coop/mailman/listinfo/dev