Hacker News new | comments | show | ask | jobs | submit login

I'm not sure if it's due to a misunderstanding, but many of your comments seem to be incorrect or nonsequiturs.

> > Haskell makes your functions pure by default. You have to change the type signature if you want to do IO

> The author seems to be conflating IO and side effects. IO is just one of the many ways that side effects can occur.

Indeed, but that does not refute the OP's statement.

> > Functions that do IO are difficult or impossible to test.

> Is your function creating a file? Start your test, assert the file doesn't exist, call your function, assert your file exists. Done.

Right, and (in my opinion) that is more difficult than testing something that doesn't create a file.

> > 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.

Indeed, but again that does not refute the OP's statement which was simply that the syntax is clean.

> > The point is, you can't confidently reason about a function from its signature if IO is involved.

> Sure, you can: the mention of IO tells me this function operates within the IO monad, so I know a lot of operations and laws that apply to it.

You do? Please tell me a law that applies to IO.




> > 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 between

Map Integer String -> String -> Integer

and

Map Integer String -> String -> IO Integer

The 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-...

http://www.haskellforall.com/2012/06/you-could-have-invented...


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.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: