Understanding how to use HAppS features

1 view
Skip to first unread message

mi...@evins.net

unread,
Oct 24, 2006, 6:57:54 AM10/24/06
to HAppS
HAppS has added some seemingly appealing features in 0.8.4 -- notably
in the Agents subsystems. However, I haven't been able to adequately
understand how to use SessionKeeper or Users.

In the case of Users, the authentication code stores passwords in the
clear (bad!), and I don't see how to obtain a list of known users. I
found it easier to write my own subsystem from scratch than to figure
out how the supplied code was meant to be used and extended.

I'm having much the same issue with sessions -- sure, I can call
newSession, but it's not immediately obvious to me how to get the
session key out of the returned

Ev (State a) ev SessionKey

value so that I can write it to a cookie or whatever. After scratching
my head over it for a day, I am tempted to write my own session
manager.

That doesn't seem right to me--that it should be easier for me to write
my own versions of these subsystems than to understand how the supplied
ones are meant to be used and extended. I thought perhaps you folks
could help me understand them a little better. Or maybe, if other users
find the examples and source code similarly puzzling, we could figure
out why that is so, and adjust either the APIs, the documentation, or
the examples to make things easier to understand.

Forgive me if I am just being obtuse -- that's been known to happen.

Einar Karttunen

unread,
Oct 24, 2006, 4:18:46 PM10/24/06
to HA...@googlegroups.com
On 24.10 03:57, mi...@evins.net wrote:
> HAppS has added some seemingly appealing features in 0.8.4 -- notably
> in the Agents subsystems. However, I haven't been able to adequately
> understand how to use SessionKeeper or Users.

SessionKeeper is nice and stable. Users is more of a demonstration
"this way one can create a Users agent" when people were asking
how to do it.

> In the case of Users, the authentication code stores passwords in the
> clear (bad!), and I don't see how to obtain a list of known users. I
> found it easier to write my own subsystem from scratch than to figure
> out how the supplied code was meant to be used and extended.

Note that the Users code does *not* have any password functionality.
There is one trivial example using it in examples/ directory which
does not have crypto because that would make it uncompilable for
all people missing the chosen crypto Haskell implementation.

There is nothing stopping to use a password hash. That is the usual
way of doing it if passwords don't need to be recovered. Use symmetric
crypto if you need to recover them.

A third alternative is just to use an encrypting SaverImpl to
make all State encrypted. This works very nicely.

> I'm having much the same issue with sessions -- sure, I can call
> newSession, but it's not immediately obvious to me how to get the
> session key out of the returned
>
> Ev (State a) ev SessionKey
>
> value so that I can write it to a cookie or whatever. After scratching
> my head over it for a day, I am tempted to write my own session
> manager.

do sk <- newSession someTime someValue
res <- foobar sk
sesCookie "mysessionid" (show sk) res

?


> That doesn't seem right to me--that it should be easier for me to write
> my own versions of these subsystems than to understand how the supplied
> ones are meant to be used and extended. I thought perhaps you folks
> could help me understand them a little better. Or maybe, if other users
> find the examples and source code similarly puzzling, we could figure
> out why that is so, and adjust either the APIs, the documentation, or
> the examples to make things easier to understand.

There is a lack of documentation. People helping to write documentation
would be very nice. Personally I know the code too well - it is clear
to me how it works and thus it is harder to find the places lacking
in documentation.

New code addition usually have some Haddock documentation but more
per-module documentation explaining how to use things could
probably make things easier.

If some haddock comment is too vague or missing and one has to look
at the source then adding a Haddock comment that would have saved time
- and submitting it as a patch will help everyone.

- Einar Karttunen

Einar Karttunen

unread,
Oct 24, 2006, 5:13:13 PM10/24/06
to HA...@googlegroups.com
On 24.10 23:18, Einar Karttunen wrote:
> do sk <- newSession someTime someValue
> res <- foobar sk
> sesCookie "mysessionid" (show sk) res
>

To avoid confusion:

remember the withFoo wrapper depending on your datatypes:

e.g.

data State = State { ..., bar :: SessionKeeper Int, ... }

sk <- withBar $ newSession someTime 0

- Einar

mi...@evins.net

unread,
Oct 24, 2006, 8:03:13 PM10/24/06
to HAppS

Einar Karttunen wrote:
> On 24.10 03:57, mi...@evins.net wrote:
> > HAppS has added some seemingly appealing features in 0.8.4 -- notably
> > in the Agents subsystems. However, I haven't been able to adequately
> > understand how to use SessionKeeper or Users.
>
> SessionKeeper is nice and stable. Users is more of a demonstration
> "this way one can create a Users agent" when people were asking
> how to do it.
>
> > In the case of Users, the authentication code stores passwords in the
> > clear (bad!), and I don't see how to obtain a list of known users. I
> > found it easier to write my own subsystem from scratch than to figure
> > out how the supplied code was meant to be used and extended.
>
> Note that the Users code does *not* have any password functionality.
> There is one trivial example using it in examples/ directory which
> does not have crypto because that would make it uncompilable for
> all people missing the chosen crypto Haskell implementation.
>
> There is nothing stopping to use a password hash. That is the usual
> way of doing it if passwords don't need to be recovered. Use symmetric
> crypto if you need to recover them.

That's exactly what I did in my own implementation of authentication. I
would have modified the Users example code to use it, if i could have
figured out how to do that. In the event, it was quicker and easier to
write my own representation of Users and an associated authentication
mechanism than it was to understand how Users was meant to work. It's a
special case of the general difficulty I have with HAppS features,
which may be due more to failings in me than in HAppS, but there it is.

> A third alternative is just to use an encrypting SaverImpl to
> make all State encrypted. This works very nicely.
>
> > I'm having much the same issue with sessions -- sure, I can call
> > newSession, but it's not immediately obvious to me how to get the
> > session key out of the returned
> >
> > Ev (State a) ev SessionKey
> >
> > value so that I can write it to a cookie or whatever. After scratching
> > my head over it for a day, I am tempted to write my own session
> > manager.
>
> do sk <- newSession someTime someValue
> res <- foobar sk
> sesCookie "mysessionid" (show sk) res

app :: Method -> Host -> [String] -> Ev GameState Request Result

app POST _ ["login_action"] = do
rq <- getEvent
gamestate <- get
let username = (lookS 100 rq "username")
let password = (lookS 100 rq "password")
let authData = getAuthData gamestate
let auth = (checkAuthentication username password authData)
if auth then do session <- newSession 30 username
sresult 200 ("Logging in user "++username)
else do sresult 200 ("Login for user "++username++"
failed ")

Application.hs:38:36:
Couldn't match expected type `GameState'
against inferred type `State String'
Expected type: Ev GameState Request t
Inferred type: Ev (State String) ev SessionKey
In a 'do' expression: session <- newSession 30 username
In the expression:
if auth then
do session <- newSession 30 username
sresult 200 ("Logging in user " ++ username)
else
do sresult 200 ("Login for user " ++ (username ++ " failed
"))

I get that app expects the type in the do block to be Ev GameState
Request t, and that newSession instead returns type Ev (State String)
ev SessionKey. I think I understannd that I could alter the definition
of the GameState to accomodate sessionkey values -- but I don't want to
do that. I want GameState data to persist for a long time; I want
session keys to expire in a relatively short time and not be stored in
the persistent game state. I want the web app's request handlers to be
capable of handling authentication requests and handing out session
keys to the client. And indeed, I can accomplish all of this by writing
this code myself (for example, I have a cgi-based program that does
it), if I give up on HAppS -- but of course, HAppS is too nice for me
to want to give it up.

I'm quite sure that I am simply not understanding correctly everything
I need to udnerstand.

Your example is nearly as opaque to me as what it is meant to explain
(at least the second and third lines are). Rather than answering my
question, they provide another input to the process I usually use to
try to understand HAppS code, which is:

- copy an example into a source file and compile it; run it to see
what it does
- alter things until they break compilation or produce an unexpected
result
- read Haddock docs and source code until a likely source of the
breakage falls out
- test my understanding by iterating on the above

It's not quite accurate to say that I know no more than I did before
seeing the above example; now I know a new magic sequence of tokens
that I can use as input to the above process--but it's not a
particularly efficient process.

> > That doesn't seem right to me--that it should be easier for me to write
> > my own versions of these subsystems than to understand how the supplied
> > ones are meant to be used and extended. I thought perhaps you folks
> > could help me understand them a little better. Or maybe, if other users
> > find the examples and source code similarly puzzling, we could figure
> > out why that is so, and adjust either the APIs, the documentation, or
> > the examples to make things easier to understand.
>
> There is a lack of documentation. People helping to write documentation
> would be very nice. Personally I know the code too well - it is clear
> to me how it works and thus it is harder to find the places lacking
> in documentation.

Sure, I understand that problem well. Maybe we can triangulate a happy
medium between my ignorance and your knowledge.

> New code addition usually have some Haddock documentation but more
> per-module documentation explaining how to use things could
> probably make things easier.

Agreed.

> If some haddock comment is too vague or missing and one has to look
> at the source then adding a Haddock comment that would have saved time
> - and submitting it as a patch will help everyone.

I wish I could be more helpful with this; at the moment I'm not sure I
understand HAppS well enough to ask intelligent questions, much less
help provide answers to new users.

Einar Karttunen

unread,
Oct 25, 2006, 4:13:46 AM10/25/06
to HA...@googlegroups.com

SessionKeeper.State *should* be a part of the State. The HAppS code
saves the sessions, to disk which is needed for replay. How would
you replay something using data from the session if the session
would not be there after a crash?.

The session agent code automatically expires old sessions based
on the timeouts given. And thus the session table sizes won't
grow too large.

When using a component from the application state the normal way to do
it is the following:

-- define application state
data AppState { ...
-- and the part for component Foo.
, foo :: FooState
... } deriving(...)

-- And use TH to derive helper functions - which will create
-- e.g. "withFoo :: Ev rq FooState a -> Ev rq AppState a"
$(inferRecordUpdaters ''AppState)
$(inferStartState ''AppState)

-- Now to use actions operating on FooState simply use
-- them with withFoo. e.g.

withFoo $ someFooFun arg1 ... argN

Note the arguments in the Ev monad:
Ev <request type> <state type> <result>

Thus use the with<Something> TH generated functions to transform functions
operating on a sub-state to operating over the whole state.


- Einar Karttunen

mi...@evins.net

unread,
Oct 25, 2006, 6:31:27 AM10/25/06
to HAppS
Einar Karttunen wrote:

> SessionKeeper.State *should* be a part of the State. The HAppS code
> saves the sessions, to disk which is needed for replay. How would
> you replay something using data from the session if the session
> would not be there after a crash?.

Fair enough.

> The session agent code automatically expires old sessions based
> on the timeouts given. And thus the session table sizes won't
> grow too large.
>
> When using a component from the application state the normal way to do
> it is the following:
>
> -- define application state
> data AppState { ...
> -- and the part for component Foo.
> , foo :: FooState
> ... } deriving(...)
>
> -- And use TH to derive helper functions - which will create
> -- e.g. "withFoo :: Ev rq FooState a -> Ev rq AppState a"
> $(inferRecordUpdaters ''AppState)
> $(inferStartState ''AppState)
>
> -- Now to use actions operating on FooState simply use
> -- them with withFoo. e.g.
>
> withFoo $ someFooFun arg1 ... argN
>
> Note the arguments in the Ev monad:
> Ev <request type> <state type> <result>
>
> Thus use the with<Something> TH generated functions to transform functions
> operating on a sub-state to operating over the whole state.

This explanation appears to confirm what I inferred from my last
excursion into reading HAppS source code. That's comforting to some
degree, at least.

I'll have a go at redefining the app state with this in mind, and see
whether I bog down again.

The basic difference between using HAppS and implementing everything on
my own from scratch is that II understand the meaning and purpose of
all the types and functions that I define. For now, using HAppS
properly requires tracing through a lot of type definitions to try to
understand what they are all for, and how someone might use them.

Well, that and bothering you when I get confused, of course.

I tend to bog down in what seems like quite a large number of type
definitions, which I read through in an attempt to understand what the
example code is doing. There are enough of them in enough files that my
working memory fills up before I understand all the relevant
relationships.

What if the examples contained a set of files organized something like
this:

example1: the simple one-liner that serves static files
(with example static files to serve)

example2: adds to example 1 some simple state
(for example a counter)

example3: adds a request handler that displays
a dynamically-generated page that
displays the state

example4: adds an input and a handler that can
be used to change the state

...and so on, for savers, sessions, etc.

Each example would reuse the code from the previous one, so that notes
could highlight the changes, and explain their motivation.

That's a lot of writing, I know. But I'm trying to think of things that
could make the process of learning easier for new HApppS users (of
which I am still certainly one).

mi...@evins.net

unread,
Oct 25, 2006, 7:57:15 AM10/25/06
to HAppS

mi...@evins.net wrote:

> I tend to bog down in what seems like quite a large number of type
> definitions, which I read through in an attempt to understand what the
> example code is doing.

Allow me to illustrate this process more concretely.

So let's suppose I want to start with some very simple game state,
e.g.:

data GameState = GameState [String] deriving(Read,Show)

But you suggest that we want to name the fields and use Template
Haskell to derive updaters and start state. So:

data GameState = GameState { users :: [String] } deriving(Read,Show)

$(inferRecordUpdaters ''GameState)
$(inferStartState ''GameState)

Uh oh; compiling this produces:


GameState.hs:16:2:
Illegal instance declaration for `StartStateEx GameState GameState'
(The instance type must be of form (T a b c)
where T is not a synonym, and a,b,c are distinct type
variables)
In the instance declaration for `StartStateEx GameState GameState'


Hmm...what does this mean? Presumably TH is expanding inferStartState
''GameState to StartStateEx GameState GameState, but I really don't
know much about TH, certainly not enough to understand this error
message.

Okay, so let's see how some example code does this. We can hope that
will be enlightening.

How about user.hs? We find:

data MyState = My { users :: Users String } deriving(Read,Show)

$(inferRecordUpdaters ''MyState)
$(inferStartState ''MyState)


Gosh, that likes like what we're doing, except that it uses some type
"Users String" that is defined elsewhere. So let's see what that
actually means. Looking in the sources for HAppS.Agents.Users, we find
that Users is:

type Username = String

data Users d = Users
{ uData :: M.Map Username d
, uSes :: SessionState Username
} deriving(Read,Show)


(where M is an alias for HAppS.DBMS.RSMap)

Terrific. Now we just need to understand what HAppS.DBMS.RSMap.Map is
meant to be, and what SessionState is meant to be.

Now what were we trying to do again? Oh yeah! We were trying to figure
out what would be a type that inferStartState would be happy with in a
field of GameState. Well, we can certainly go trace down the
definitions of HAppS.DBMS.RSMap.Map and of SessionState and see what
they look like; maybe they will clarify what inferStartState wants as
the type of a field in a state type. Or maybe they will lead to yet
another set of source files to read in order to understand the meanings
of the types used in those definitions.

Or, I could just write some code to map usernames to hashed passwords,
to generate keys and send them to the client, and to check
client-supplied keys against a filestore that I write. Turns out, based
on recent experience, it takes less time to just write that code than
to trace through trying to find out how to fix this type error in the
definition of GameState.

(Let me know if this becomes tiresome and I'll stop; I just wanted to
illustrate how exactly I get hung up on these sorts of issues.)

Einar Karttunen

unread,
Oct 25, 2006, 8:31:39 AM10/25/06
to HA...@googlegroups.com
On 25.10 04:57, mi...@evins.net wrote:
> GameState.hs:16:2:
> Illegal instance declaration for `StartStateEx GameState GameState'
> (The instance type must be of form (T a b c)
> where T is not a synonym, and a,b,c are distinct type
> variables)
> In the instance declaration for `StartStateEx GameState GameState'
>
>
> Hmm...what does this mean? Presumably TH is expanding inferStartState
> ''GameState to StartStateEx GameState GameState, but I really don't
> know much about TH, certainly not enough to understand this error
> message.

That is GHC's way of telling you to add -fglasgow-exts.
Which is needed for multiparameter type classes.

If you look at the examples you will note all of them enabling
-fglasgow-exts via the OPTIONS-pragma.

> Okay, so let's see how some example code does this. We can hope that
> will be enlightening.
>
> How about user.hs? We find:
>
> data MyState = My { users :: Users String } deriving(Read,Show)
>
> $(inferRecordUpdaters ''MyState)
> $(inferStartState ''MyState)
>
>
> Gosh, that likes like what we're doing, except that it uses some type
> "Users String" that is defined elsewhere. So let's see what that
> actually means. Looking in the sources for HAppS.Agents.Users, we find
> that Users is:

Users is an abstract datatype parametrized by the actual data you
want to store for each user.

> Terrific. Now we just need to understand what HAppS.DBMS.RSMap.Map is
> meant to be, and what SessionState is meant to be.

RSMap = simple wrapper for Data.Map
SessionState = the primitive sessions from Agents.SessionKeeper

> Or, I could just write some code to map usernames to hashed passwords,
> to generate keys and send them to the client, and to check
> client-supplied keys against a filestore that I write. Turns out, based
> on recent experience, it takes less time to just write that code than
> to trace through trying to find out how to fix this type error in the
> definition of GameState.

There is nothing stopping you from ignoring the Agents and write your
own. The Agents are just parts that seem to be reusable for many
applications not the only way to do things.

- Einar

Simon Michael

unread,
Oct 25, 2006, 12:26:48 PM10/25/06
to HA...@googlegroups.com
mi...@evins.net wrote:
> What if the examples contained a set of files organized something like
> this:

An excellent plan!

mi...@evins.net

unread,
Oct 25, 2006, 2:10:13 PM10/25/06
to HAppS

Einar Karttunen wrote:
> On 25.10 04:57, mi...@evins.net wrote:
> > GameState.hs:16:2:
> > Illegal instance declaration for `StartStateEx GameState GameState'
> > (The instance type must be of form (T a b c)
> > where T is not a synonym, and a,b,c are distinct type
> > variables)
> > In the instance declaration for `StartStateEx GameState GameState'
> >
> >
> > Hmm...what does this mean? Presumably TH is expanding inferStartState
> > ''GameState to StartStateEx GameState GameState, but I really don't
> > know much about TH, certainly not enough to understand this error
> > message.
>
> That is GHC's way of telling you to add -fglasgow-exts.
> Which is needed for multiparameter type classes.
>
> If you look at the examples you will note all of them enabling
> -fglasgow-exts via the OPTIONS-pragma.

Oho! That was indeed the problem in this example. Code is building now;
I can proceed and see if I've understood things any better thanks to
your other patient explanations.

> > Okay, so let's see how some example code does this. We can hope that
> > will be enlightening.
> >
> > How about user.hs? We find:
> >
> > data MyState = My { users :: Users String } deriving(Read,Show)
> >
> > $(inferRecordUpdaters ''MyState)
> > $(inferStartState ''MyState)
> >
> >
> > Gosh, that likes like what we're doing, except that it uses some type
> > "Users String" that is defined elsewhere. So let's see what that
> > actually means. Looking in the sources for HAppS.Agents.Users, we find
> > that Users is:
>
> Users is an abstract datatype parametrized by the actual data you
> want to store for each user.
>
> > Terrific. Now we just need to understand what HAppS.DBMS.RSMap.Map is
> > meant to be, and what SessionState is meant to be.
>
> RSMap = simple wrapper for Data.Map
> SessionState = the primitive sessions from Agents.SessionKeeper

I didn't mean to ask about this point; I've already traced through the
source code and gleaned more than this information. Rather, I was
hoping it would be helpful to you to see the process that one ignorant
beginner goes through in trying to figure out why something doesn't
work.

> > Or, I could just write some code to map usernames to hashed passwords,
> > to generate keys and send them to the client, and to check
> > client-supplied keys against a filestore that I write. Turns out, based
> > on recent experience, it takes less time to just write that code than
> > to trace through trying to find out how to fix this type error in the
> > definition of GameState.
>
> There is nothing stopping you from ignoring the Agents and write your
> own. The Agents are just parts that seem to be reusable for many
> applications not the only way to do things.

Sure; I understand that; I can write code to do anything that HAppS
does. What I'm having trouble doing is writing code that works nicely
with HAppS, so that I don't have to write code that does what it does.
The above remarks are about how hard it is for me to understand how to
do that. I had inferred from your earlier remarks that you might find
it useful to know what a new user stumbles over.

new...@lava.net

unread,
Nov 28, 2006, 9:27:15 PM11/28/06
to HAppS
So was anyone able to come up with code that worked?
What I got from this thread so far is:
- global application state needs to encapsulate the session state
- TH commands create helpers to go between the two states.
the session functions can be called with the helpers

but all the code examples that were posted were declared non-working.
I'm wrestling with the same problem now and could use some guidance.

Tim

new...@lava.net

unread,
Nov 29, 2006, 10:54:56 PM11/29/06
to HAppS
new...@lava.net wrote:
> So was anyone able to come up with code that worked?

I worked through the details and constructed an example:

http://www.thenewsh.com/~newsham/HAppS-StateExample.tgz

Reply all
Reply to author
Forward
0 new messages