Let me put it plainly. I'm not going to explain the theory, just cold hard practicality of how to do what you want to do when you see this in the wild, which you will if you haven't already. Let's call this a pattern in haskell. It is the monad transformer pattern, which you'll see many (possibly even most) libraries use at one time or another once they get past a certain amount of complexity. When you don't understand it, you feel like you can't quite do anything in haskell, at least not without copy paste or lots of random fiddling. But then once you know it, suddenly most of the libraries in the language open up to you. At least that is how it was for me. Whenever you see a library that has a runBlah function which will have a type: Blah a -> IO b. Examples: shelly :: MonadIO m => Sh a -> m a (from the shelly library) runStateT :: StateT s m a -> s -> m (a, s) (from control.monad.state) runInputT :: MonadException m => Settings m -> InputT m a -> m a (from haskeline) runResourceT :: MonadBaseControl IO m => ResourceT m a -> m a (from the resourceT package) atomically :: STM a -> IO a (from base, from the shared transactional memory related functions) runParserT :: Stream s m t => ParsecT s u m a -> u -> SourceName -> s -> m (Either ParseError a) (from parsec) runFastCGI :: CGI CGIResult -> IO () (from the fastcgi library) So as you can see they are different, but they all have some similarities in that they all return either IO a, or possibly m a where m has some class restrictions on it. IO will almost always meet those restrictions if you try it. Sometimes they have special arguments to get their environments set up so that they have the info they need to work. Sometimes they don't. What they all have in common is that they all take an argument that sort of lives in a specific type (InputT, ResourceT, ParsecT, Sh, STM, StateT, etc.). Sometimes there are several alternatives with slightly different functionality. If you find a function that returns one of those types, then it was meant to be run as an argument to a function like one of these. Sometimes the types are simplified with type aliases so you don't always recognize them off the bat. Parser a aliases to Parsec String () a which is an alias for ParsecT String () Identity a. In your original message it was the case that CGI CGIResult is an alias for CGIT IO CGIResult, so the base monad was in that case CGIT, and not CGI. So you've figured this out, now you can run a procedure composed of functions which return CGI a. Suddenly you realize in the middle of the procedure you need to run an IO function. Perhaps you need to get some info from a file. Well each of these monads uses the transformer library. If you go onto hackage and you look up say, ParsecT for example, you will see a whole lot of instances like these for each of them. MonadError e m => MonadError e (ParsecT s u m) MonadReader r m => MonadReader r (ParsecT s u m) MonadState s m => MonadState s (ParsecT s' u m) MonadTrans (ParsecT s u) Monad (ParsecT s u m) Functor (ParsecT s u m) MonadPlus (ParsecT s u m) Applicative (ParsecT s u m) Alternative (ParsecT s u m) MonadIO m => MonadIO (ParsecT s u m) MonadCont m => MonadCont (ParsecT s u m) These instances all represent different tricks that can be performed while stuck in the ParsecT monad. The one that concerns us right now is that instance for MonadIO. Any type that is an instance of MonadIO has the ability to use the liftIO function, which is defined in the MonadIO class. All this lets you do is call any IO function in it. Sometimes the instance is a little wonky and you might not recognize it right off the bat, for example: (Monad (CGIT m), MonadIO m) => MonadIO (CGIT m) So, is CGIT an instance of MonadIO or not? Well it is if CGIT m is a Monad and the m, whatever it is, is an instance MonadIO. If you look at IO, IO happens to be an instance of MonadIO, so therefore you can use liftIO in there whenever you please. Now lets look at the instances for STM: Monad STM Functor STM Typeable1 STM MonadPlus STM Applicative STM Alternative STM Huh there's no MonadIO instance here. How am I supposed to read my file? Well the answer is that STM is meant for atomic actions and the only way it can guarantee that no one tried to do anything funny while it was in the STM monad is to restrict it so that you cannot do IO in the middle of it. There is no real way to subvert it, since you don't have access to the internals of STM, you could never write a MonadIO instance for it. The designer intended it to be this way. That tells you quite a bit about what that library was intended for. So now notice all those other instances. What do they do? Well they do lots of useful things the library writer thought you might like to be able to do. Applicative lets you use applicative style programming within the monad. You can see tons of examples of that in Parsec, just google it. Alternative allows you to use the alternative operator when you are within that monad, which I find super useful and I wish people used it more. So you can try to do one thing and if it fails, try another (x <- (tryA <|> tryB)). It can be used to greatly simplify code, especially in parsers. Functor allows you to fmap its return result to another type without exiting the monad. (string <- fmap show $ (char 'a' :: Parser Char) :: Parser String) from parsec for example. This is a really common one and nearly all such types will have a Functor instance. MonadState would allow you to get and set a variable in the monad's environment. Sometimes that state is set by you in the run function (which in fact it is for both StateT and ParsecT). Sometimes it is the internal state of the library. If you can supply it you can use it to carry some arbitrary state along the program for you, and access and update it whenever you need to, and even return it at the end. So when you see a mysterious monad somewhere in a library, give it a look on hackage. You may find that it has some sensible functionality for you to make use of. You may also get a sense of how the library creator expects you to use his library. On Sun, Mar 10, 2013 at 7:21 PM, emacstheviking <objitsu@gmail.com> wrote:
David,
At times like this I think am not even fit to code PHP for my day job. I am going to have to read that very carefully when I wake up tomorrow.
Thanks.
On 10 March 2013 22:59, David McBride <toad3k@gmail.com> wrote:
Your ipCamExec is IO (), but you are running it in the CGI a monad which is a type alias for CGIT IO a. CGIT is an instance of MonadIO, so try liftIO ipCamExec. liftIO has a type MonadIO m => IO a -> m a, which means that if you replace m with CGIT IO, you would get IO a -> CGIT IO a, which is exactly what you need.
On Sun, Mar 10, 2013 at 6:40 PM, emacstheviking <objitsu@gmail.com> wrote:
I am writing a stop-motion capture application using AngularJS and it's going OK. I was inspired to do so after installing "IPCamera" on my phone and Sony tablet. A typical IPCamera session lives on an internal address like this, this example will turn on the LED on the camera:
http://192.168.0.5:8080/enabletorch
Just because I can (or so I thought), I decided to write a tiny little FastCGI application in Haskell to act as a proxy using the PATH_INFO variable. This means that in to my Javascript code I have this code in a service file:
angular.module('stomoServices', ['ngResource']). factory( 'IPCamera', function($resource, urlIPCameraAPI) { return $resource( urlIPCameraAPI, {}, { ledOn: { method: 'GET', params: {featureReq: 'enabletorch' }}, ledOff: { method: 'GET', params: {featureReq: 'disabletorch' }}, focusOn: { method: 'GET', params: {featureReq: 'focus' }}, focusOff: { method: 'GET', params: {featureReq: 'nofocus'}} }); });
and I then issue commands like "IPCamera.ledOn()" etc. All very nice except that it doesn't work yet because I can't get the worlds seemingly simplest CGI application to compile yet! Here is the code that I have, it could be "cleared up" but this is what I have so far:
main :: IO () main = runFastCGI . handleErrors $ do command <- getVar "PATH_INFO" case command of Nothing -> outputError 400 "Missing IPCamera instruction (PATH_INFO)" [] Just cmd -> ipCamExec (tail cmd) >> output "OK" -- tail drops the "/" where ipCamExec :: String -> IO () ipCamExec url = do simpleHTTP (getRequest url) -- don't want or need response. return () -- to match the return type or so I thought.
and the error message I cannot seem to understand as it fills me with monadic fear which I can't get out of:
ipcamera.hs:16:7: Couldn't match expected type `CGIT IO a0' with actual type `IO ()' In the return type of a call of `ipCamExec' In the first argument of `(>>)', namely `ipCamExec (tail cmd)' In the expression: ipCamExec (tail cmd) >> output "OK"
Please could some kind souls explain to me in simple terms just what is going on and why I am close to tears right now? I have read the definitions of CGIResult and CGI and they leave me cold. I am trying to understand monads more but at times like this I once again realise what a complete rank beginner I am!
Thanks. Sean.
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners