> > boo :: Map Integer String -> String -> Integer> This syntax is clean but it doesn't tell me anything that> def boo(map: Map[Integer, String], key: String) : Integer> doesn't tell me.OP's point was not so much that the Haskell syntax is clean, but more that there is a huge difference betweenMap Integer String -> String -> IntegerandMap Integer String -> String -> IO IntegerThe latter is allowed to do IO, while the former is not. There are very few languages that allow you to express this difference and have the compiler enforce it (Haskell being one of them). So "Map Integer String -> String -> Integer" tells you a lot more than "def boo(map: Map[Integer, String], key: String) : Integer" does.For more information about what exactly types tell you, look up parametricity or read Philip Wadler's "Theorems for free". -----
 "Haskell being one of them"Also Fortran being one of them (you can mark functions and subroutines 'pure'). -----
 > You do? Please tell me a law that applies to IO.How about three? Right identity, left identity and associativity.That's for the laws. For the properties, IO is a monad so I can compose any value in the IO monad with other monads (Maybe, ST, etc...). -----
 There are certainly laws as to what goes on when you do various things with IO values. The relevant point, though, was that access to IO means there are few (any?) laws restricting what is actually going on when that integer is generated.Actually, I kind of wish IO was split up a bit... I have been meaning to play with rolling some typeclasses (with an IO instance and a testing instance) that wrap up IO of various types, so I define my functions in terms of these and spot what kind of IO various functions might do - I'm not yet sure how much of a win it will be, but it seems like something to explore. -----
 I've seen them created a few times. I'm pretty sure there are at least a few implementations in Hackage of such divided IO monads. More generally, the haute method for solving this would be to use Operational or Free monads to create your restricted monad type and then interpret it into IO.http://www.haskellforall.com/2012/07/purify-code-using-free-... -----
 I think optimally, with a set of typeclasses, one could define instances for IO and just use things with no interpretation or build a datastructure with free monads to perform whatever other inspections or manipulations you want. There could be some incompatability between the two notions that I'm missing, though... -----
 I think I've seen the typeclass route as well before. It may have just been a blog post and not a library implementation, though. The two methods have different epistemological ideas, but could be combined. For instance:`````` {-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving, FlexibleInstances #-} module CrWr where import Control.Monad.Free import Data.IORef data RWRefF t a = NewRef t (IORef t -> a) | PeekRef (IORef t) (t -> a) | PutRef (IORef t) t a deriving Functor newtype RWRef t a = RWRef (Free (RWRefF t) a) deriving Monad newRef :: t -> RWRef t (IORef t) newRef t = RWRef \$ liftF \$ NewRef t id peekRef :: IORef t -> RWRef t t peekRef r = RWRef \$ liftF \$ PeekRef r id putRef :: IORef t -> t -> RWRef t () putRef r t = RWRef \$ liftF \$ PutRef r t () interpret :: RWRef t a -> IO a interpret (RWRef (Pure a)) = return a interpret (RWRef (Free (NewRef t f))) = newIORef t >>= interpret . RWRef . f interpret (RWRef (Free (PeekRef r f))) = readIORef r >>= interpret . RWRef . f interpret (RWRef (Free (PutRef r t next))) = writeIORef r t >> interpret (RWRef next) -- Highly specialized class of IO monads class Monad m => RWIntRefMonad m where new :: Int -> m (IORef Int) peek :: IORef Int -> m Int put :: IORef Int -> Int -> m () toIO :: m a -> IO a instance RWIntRefMonad (RWRef Int) where new = newRef peek = peekRef put = putRef toIO = interpret incr :: RWIntRefMonad m => IORef Int -> m () incr r = peek r >>= put r . (+1) main = do r <- newIORef 0 -- We need to specialize before the m gets erased toIO (incr r :: RWRef Int ()) readIORef r >>= print`````` -----
 > Actually, I kind of wish IO was split up a bit... I have been meaning to play with rolling some typeclasses (with an IO instance and a testing instance) that wrap up IO of various types, so I define my functions in terms of these and spot what kind of IO various functions might do - I'm not yet sure how much of a win it will be, but it seems like something to explore.There's a section on this practice in Real World Haskell: http://book.realworldhaskell.org/read/programming-with-monad... -----
 Right, that's very much what I had in mind - I might very well have got it from there and forgotten; I've read much if not all of RWH at one point or another.I would point out that in addition to the mentioned testing use (itself not to be underestimated - QuickCheck is amazing and making more things tractable to QuickCheck is great), there's also some other big potential wins. If I have a function that deals with local files in a monad that's an instance of MonadHandle, I can write an instance for a RemoteIO type that performs file operations on a remote system through a network connection, and suddenly the function that was local also works remotely. -----
 OH! That's where I saw it. I couldn't remember, but I know the idea has been floating around for a long while. -----
 > > Functions that do IO are difficult or impossible to test.> Absolutely not, IO functions are not harder to test than regular ones. Is your function creating a file? Start your test, assert the file doesn't exist, call your function, assert your file exists. Done.They are harder to test (although definitely not impossible). What if your function does a network request, and the result is highly dependent on latency etc, sure you can mock an endpoint that responds with the various properties desired, but it's much harder than just using Quickcheck or HUnit or one of the other testing libraries. -----
 Global variables certainly create opportunities for side-effects. The author may be throwing everything into one big pot and labeling it I/O, but they're right that you can't sneak a global variable access into a function without changing the function's type. That's a valid point even if it's poorly worded.> This is great for homework, but in the real life, IO needs to be performed throughout your entire applicationMy experience has been that I could get away with pushing I/O out to the fringes of the app and make 80-90% of the code pure. But everybody lives in a different "real life" and it's not unlikely that the kinds of things I do on the side for fun just don't require it.I fully concur with the rest of your remarks. -----
 > Absolutely not, IO functions are not harder to test than regular ones. Is your function creating a file? Start your test, assert the file doesn't exist, call your function, assert your file exists. Done.There are a lot of things you're not testing for. Permission issues, etc. Besides, you have -plenty of functions doing IO on databases, or doing parallel operations. That's definitely a pain to test.> > boo :: Map Integer String -> String -> Integer> This syntax is clean but it doesn't tell me anything that> def boo(map: Map[Integer, String], key: String) : Integer> doesn't tell meIt tells you it's not doing IO. Besides, what about this:`````` def boo (foo: Some Obj): void `````` What does this do? Write to a file? Change a field? I don't know. -----
 About the difference between the signatures. Well, one is in Haskell and one is in an imperative language. I think there's a significant software-engineering-philosophical difference, though. Concatenative programming languages are kinda based on this same philosophy.When you call boo function in Haskell, you have the power to create the "universe" which is both necessary and sufficient for the function to perform some self-contained computation. When enough of these computations are composed and nested together, you can really see how data flows through some logical procedure, possibly one with lots of essential complexity. On the other hand, when I look at the same signature translated into Java, for all I know it could be the 4th function call in a stateful 8-function-long set of functions you always have to call in order, except you don't have to call the 6th function if you pass true to the 2nd function. Easy enough, right?The fewer combinations of arbitrary state are allowed to determine the behavior of your program, the fewer unanticipated scenarios arise. I think this is a good software engineering principle. The corollary that I think Haskell really brings home when it comes to designing programs is that it's better to change the way data flows in one small section than to add a little more state somewhere and handwave away state combinations which you think are impossible. This idea applies in both Haskell and Java. -----
 > This is great for homework, but in the real life, IO needs to be performed throughout your entire application, and unsafePerformIO is just not an option.Large Haskell applications benefit from different overall design than the typical C++ or Java app. Our very large Haskell codebase doesn't use IO much nowadays, and as we go back through to refactor older code it diminishes even more. -----
 > Absolutely not, IO functions are not harder to test than regular onesTesting IO involves systems and context that are external to both your code and your tests. This makes them more complex to set up and more error prone. -----

Search: