1. 14
  1.  

  2. 7

    For more depth and specificity, see Why free monads matter.

    Free monads are not the only way to separate IO and computation, but it’s one relatively popular way. They lend themselves nicely to making “test” interpreters of your data model that mock effects but don’t actually run them.

    Example: The production interpreter performs IO, the fake/test/mock one logs effects performed via a Writer and your tests can assert and check things like, “it wrote ${content} three times to ${file}” without having to check the secondary outcomes of having performed said effects. Your tests can be a lot more precise and more realistic without adding actual effects to the test suite. Have to be judicious in your assertions still - you don’t want to be updating tedious assertions every time something changes.

    IME: Having types that track effects period is vital for making this work outside of small demo projects. Not just for my own code, but so I know what other peoples' code is doing as well. You know how people individually (painfully) discover how Java’s URL equality works? You don’t get nasty surprises like that in Haskell. You can write code that performs the same check, but you don’t get to abuse Eq for it.

    1. 1

      You don’t get nasty surprises like that in Haskell. You can write code that performs the same check, but you don’t get to abuse Eq for it.

      Unless you abuse unsafePerformIO, which can lurk deeply in some dependency. You need just as much trust in the sanity of library authors when authoring Haskell as you do in any other language.

      1. 3

        Unless you abuse unsafePerformIO,

        You can’t use uPIO to make an Eq instance to do what Java’s URL equality does. If you think it can, I’d like a demonstration of such using only unsafePerformIO. I think you’ll find it can only fire off IO once. Which makes sense, because unsafePerformIO is ordinarily used for benign effects that don’t change your results. For an idea of what I mean:

        unsafePerformIO this and evaluate it twice: https://hackage.haskell.org/package/time-1.3/docs/Data-Time-Clock.html#v:getCurrentTime

        Think about what happens, then realize why you can’t abuse uPIO to do what you think it can do.

        I know people that have a casual familiarity with Haskell love to bring up uPIO as some kind of indictment on the language’s safety but it’s a pretty boring objection. Haskell users needed some escape hatches. Being practically minded, the implementers put escape hatches in, with some restrictions (Safe Haskell). Haskell still does the right thing by default, pervasively. And that helps a lot.

        Why are so many Scalaz users that know Haskell are perpetually complaining about Scala/Java libraries if there’s no difference between Haskell + unsafePerformIO and going back to a language ecosystem that has no native effect typing?

        So what is it? Haskell is too practical for allowing escape hatches? Haskell implementations must live up to a stereotype of impracticality so programmers have better reasons for not using it?

        We have machine-checked safety in Haskell above and beyond the usual effect typing with Safe Haskell so if you don’t trust library authors not to lie in their types, use Safe Haskell. Among other things, it bans the use of unsafePerformIO.

        This trope that effect typing isn’t useful because there’s a couple escape hatches for edge-cases is not serious.

        1. 1

          Thank you, sincerely, for responding. I’m happy to take “-1 incorrect” downvotes on the chin if my post is, indeed, incorrect, but it does hurt to see a “-1 troll” when I was commenting earnestly.

          You can’t use uPIO to make an Eq instance to do what Java’s URL equality does. If you think it can, I’d like a demonstration of such using only unsafePerformIO. I think you’ll find it can only fire off IO once. Which makes sense, because unsafePerformIO is ordinarily used for benign effects that don’t change your results. For an idea of what I mean:

          What do you mean by “it can only fire off IO once”? Do you mean if you call my_url == other_url, for some naughty Eq instance that uses unsafePerformIO, multiple times in the same program, that the IO will only execute once? Does it execute only once for the same pair of values, or once for that whole Eq instance?

          So what is it? Haskell is too practical for allowing escape hatches? Haskell implementations must live up to a stereotype of impracticality so programmers have better reasons for not using it?

          I don’t know why you felt the need to say this. I didn’t say anything about Haskell’s practicality. I didn’t say it was wrong to have escape hatches. I don’t think it’s wrong for a language that promises safety to have escape hatches; if Rust didn’t have unsafe it would be useless.

          This trope that effect typing isn’t useful because there’s a couple escape hatches for edge-cases is not serious.

          On the contrary, I believe that IO in Haskell and effects systems in general (which have been proposed for Rust) are very useful tools for programmers. That doesn’t make them panaceas, and it doesn’t mean programmers using these tools should be ignorant of the fact that code (often in a dependency) can undermine them.

          Anyway, I wrote this small program:

          module Foo where
          
          import System.IO.Unsafe (unsafePerformIO)
          import Network.BSD (getHostByName, hostAddresses)
          import Data.List (intersect)
          
          data Foo = Foo String
          
          instance Eq Foo where
            -- equal iff. x = y = (line from input)
            (Foo x) == (Foo y) = unsafePerformIO $ do
              inp <- getLine
              return (inp == x && inp == y)
          
          data Hostname = Hostname String
          
          instance Eq Hostname where
            (Hostname x) == (Hostname y) = unsafePerformIO $ do
              entry_x <- getHostByName x
              entry_y <- getHostByName y
              let common = intersect (hostAddresses entry_x) (hostAddresses entry_y)
              return $ (length common) >= 1
          
          main :: IO ()
          main = do
            putStrLn $ show (Hostname "github.com" == Hostname "www.github.com")
            putStrLn "does Foo \"Bar\" == Foo \"Bar\"? type 'Bar<RET>'"
            putStrLn $ show (Foo "Bar" == Foo "Bar")
            putStrLn "does Foo \"Bar\" == Foo \"Bar\"? type 'No<RET>'"
            putStrLn $ show (Foo "Bar" == Foo "Bar")
            putStrLn $ show (Hostname "github.com" == Hostname "www.github.com")
          

          If I use hosts I’ve defined in /etc/hosts and change their hostsfile entries between the first and second Hostname equality check, I see different values printed. This seems consistent with the Java URL debacle to me, what am I missing?

          1. 4

            So, it’s subtle. Bear with me, and I apologize that the only way I can really explain this is by getting very formal.

            The actual defined semantics are something to the effect that the Eq instance will not necessarily be called more than once per pair of identical values, but there’s no memoizing in place to make that so, in contexts where the compiler can’t otherwise know it’s the same values.

            So if your expression is using values that are statically known, it’ll definitely be called only the one time, because that’s how graph-reduction works. If, in addition, you have another of the identical expression near the first, common subexpression elimination is likely to coalesce these into one, but that’s not guaranteed.

            If your expression is using values from outside the lexical scope, ie if it’s taking them as parameters, then it kind-of depends on how far outside the scope. If they’re statically knowable with enough inlining of functions within the same module, you’ll probably get the only-once behavior… per pair of actual values, that is - if you call it twice and both times the values are statically knowable, but they’re different, then you’ll get at least one effectual call for each pair. If the situation would require cross-module inlining to know that it’s the same values, that will probably never happen; it can, but usually it only does when you’ve taken pains to make sure of it.

            As a subtlety that I only know about because it needs to be hacked around if you’re in the unfortunate position of having to do… well… this:

            {-# NOINLINE pleaseDoNotDoThis #-}
            pleaseDoNotDoThis :: IORef Int
            pleaseDoNotDoThis = unsafePerformIO $ newIORef 0
            

            … as a hack to get a piece of mutable state that you can refer to from anywhere in the program, which unfortunately is the only way to work with certain C-based libraries …

            Then you need the NOINLINE pragma because otherwise inlining may cause you to wind up with different IORefs that don’t share data, at each point to which the top-level expression is inlined. Because the guarantee is that it’s called at least once, at some time prior to its result being used the first time, but the language doesn’t promise that it won’t be called more than once. In fact, this code is likely to break someday, since NOINLINE doesn’t make that promise, either, it only eliminates the most common source of duplicate invocation.

            I mention this example basically to show the depth to which unsafePerformIO causes expectations about the language to break down!

            Anyway, so to return to the URL example, you actually can implement the Java semantics in Haskell… but you can’t guarantee that they won’t cache stale data for the duration of your process, which means there’s very little benefit to those semantics in the first place.

            For what it’s worth, I do feel that unsafePerformIO is a serious blight on the language, and I strongly recommend using safe Haskell to limit your exposure to it. Depending on what you’re doing, that may or may not be possible.

            1. 1

              Anyway, so to return to the URL example, you actually can implement the Java semantics in Haskell

              I was trying to limit the surface area of the conversation which is why I scoped it to uPIO specifically :)

              is a serious blight on the language

              How would you prefer re-obtaining the usual thunk eval semantics be provided for? I assume you don’t object to Control.Exception.evaluate for going in the other direction + seq?

              1. 1

                Limiting the surface area is fair, sorry. :)

                Pass the monad all the way down. Seriously. The inconveniences this creates are a design problem that the language needs to solve, and I’ll describe my ideas, but not having a complete solution doesn’t mean the current one is good.

                So, yes, this means you can’t do (a == b) but must do v <- equalIO a b, with a different symbol and a separate line in your do-block. I’d argue that using a different symbol is desirable; you really don’t want to invoke the wrong one by accident.

                Requiring a separate line is a nuisance, but nothing that can’t be sugared around; Idris has !-notation (it’s not described in the reference manual; search for “!-notation” as a string in the tutorial PDF), which lets you do your monadic comparison in the middle of a larger monadic expression, and it expands into doing the comparison first, then calling >>= to pass its result into the outer expression.

                I’m aware that I haven’t really answered how to deal with cases where you want to pass your impure function via a library which expects a pure one. I strongly urge that libraries which need to be used like that should be refactored.

                Fundamentally, I feel that this is a long-term code health issue, easy to ignore for a year or two, but really important to get fixed eventually. Newcomers to the language won’t be bit by it, but if we want the language to retain its best advocates in the long run…

                Edit to add: Heh - it seems you and I are both in the habit of doing edit-to-add things; I see a sentence on your post that wasn’t there when I started mine! Yes, I don’t object to Control.Exception.evaluate. I do advocate, in the long run, also changing the language so that pure functions can’t silently throw exceptions; I do understand that they can still produce bottom by running indefinitely, but the debuggability of that scenario is much higher.

            2. 1

              If I use hosts I’ve defined in /etc/hosts and change their hostsfile entries between the first and second Hostname equality check, I see different values printed. This seems consistent with the Java URL debacle to me, what am I missing?

              It only checks if they’re the same once. Which is consistent with how uPIO works.

              This is why I was trying to make you think about getCurrentTime :: IO UTCTime. Call-by-need in Haskell includes preservation of results for a given value.

              But that doesn’t work for values whose answers should change each time they’re evaluated!

              Thus, IO.

              Here’s another guidepost. If results are preserved after the first time a thunk is evaluated, then how we do benchmark anything?

              https://github.com/bos/criterion/blob/master/Criterion/Types.hs#L68

              http://hackage.haskell.org/package/base-4.8.0.0/docs/Control-Exception.html#v:evaluate

              This is very often a confusing point and most Haskellers, even those that use it at work, don’t really grok this. This is why I was planning on having a chapter devoted to IO in the book I’m working on so I can stop explaining it. :)

      2. 2

        Heh, I read this as Ultradetestable Coding Style

        Disappointed that wasn’t the title. :P