5 ms·
if it has side-effects, it has to be something like Int -> Int -> IO Bool otherwise the type checker won't let you perform any side-effecting operations (b
by uryga 6y ago
if it has side-effects, it has to be something like
Int -> Int -> IO Bool
otherwise the type checker won't let you perform any side-effecting operations
(btw Haskell's `IO Bool` would be spelled `IO<Bool>` in C++/Java syntax)
- magicalhippo 6y agoAnd there's no way to get rid of the IO then, presumably (I mean otherwise I could have just used that as a wrapper). edit: So at work, just about every value depends directly or indirectly on stuff that comes from files or from the database. So would they all have to be wrapped by IO?
- Twisol 6y agoIn Haskell, you often build pure transformations, and then lift them into an effectful context. If I have a function `String -> [String]`, say to parse a line of CSV into its elements, I can lift that to `IO String -> IO [String]` using the IO monad's `fmap`. And then I can compose it with something hypothetical like `readLine :: File -> IO String`, which actually reads the line. The core logic of a program often doesn't need to care deeply about state or system resources. Pure functional programming is about writing as much as you can in this "functional core", and then lifting the assembled pieces of pipeline into the "imperative shell" (such as the IO monad).
- magicalhippo 6y agoOk, so you make pure functions and turn them dirty, so to speak. Makes sense. In our case, almost all core code depends on various parameters, which come from the database. For example, GB recently left the EU so everything involving GB is now processed under different rules, except old stuff which has to be processed under the old rules. Thus being part of EU or not is a date-dependent database query (it already was, not the first time a country's EU status has changed). So if I get your explanation correctly, I'd code the core logic as if these parameters were pure, side-effect free, which would make the core logic side-effect free. In the case above, I'd pass a function which maps a (pure) date and string into a (pure) bool, to test for EU membership. I'd then turn that whole thing dirty via the IO thingy, passing "IO parameters" and receiving "IO results", so I can pass it my EU test function which does a database query. edit: And I presume my "dirty" database-connecting function can also mutate things, so it can do caching. Don't want to hit that database too often.
- uryga 6y ago> In the case above, I'd pass a function which maps a (pure) date and string into a (pure) bool, to test for EU membership. i doubt you could make (or really, even want to make) `checkEUMembership` pure, I'm guessing it'd involve a DB lookup of some kind. in general, you can't always "pull out all the IO" into an only-pure-logic "core"; like if you want to look up one thing and then look up another thing based on the result of the first lookup. and that's okay! i'm not going to write a whole monad tutorial, but using an `IO Foo` is kind of like using a `Promise<Foo>`¹; you do stuff like this (in JS syntax): getX(...).then((x) => getYForX(x).then((y) => foo(x, y) // note - nested lambdas/closures, `x` is closed-over ) ) "do-notation" lets you avoid callback hell, similarly to async/await. --- ¹ Unfortunately, JS's Promise#then mixes two things: • "dirtying" a pure function: getNumberFromDB().then((x) => x*2) which in Haskell would use fmap :: (a -> b) -> IO a -> IO • piping the result into another side-effecting function: getNumberFromDB().then((x) => getNameForNumberFromDB(x) ) which in Haskell would use the "bind" operator: (>>=) :: IO a -> (a -> IO b) -> IO b
- magicalhippo 6y ago> i doubt you could make (or really, even want to make) `checkEUMembership` pure, I'm guessing it'd involve a DB lookup of some kind Well that was kinda the root of my question. The core logic doesn't really care as such, as long as it could determine EU membership somehow, but actual code would have to use a DB lookup[1]. That of course spirals back to what would that really buy you. You'd write code pretending it's pure while it really isn't. I can see part of the appeal, but I can do that in my current language. Of course I don't get an error if I do something silly in the middle of some otherwise "pure" module, so there's that. Anyway, illuminating. I enjoy thinking about these things and challenging my self-taught ways. Thank you all for your contributions, much appreciated! [1]: An aside but, due to an error on the government side, GB is part of EU today as far as one of their validation checks is concerned. So today only we have to pretend along, for just that one field. This stuff is fun!
- uryga 6y ago
- chriswarbo 6y agoAs a concrete example, there's a nice function in Haskell's standard library called `interact :: (String -> String) -> IO ()`: https://hackage.haskell.org/package/base-4.14.1.0/docs/Prelude.html#v:interact https://hackage.haskell.org/package/base-4.14.1.0/docs/Prelu... Its argument is a function of type `String -> String` and it returns an `IO ()`, i.e. an i/o action with a trivial result. That action will call the given function on contents of stdin, and writes its result to stdout. Or, equivalently, we can think of `interact f` as transforming a pure string-processing function `f` into a stdio CLI. Note that laziness (specifically "lazy IO") causes stdin to be read 'on demand', giving us a streaming computation without any extra work. Here's an example implementation of 'wc': module Main where import System.IO main :: IO () main = interact count count :: String -> String count input = show (length (unwords input)) Bonus: if we want to show off, we could implement 'count' using function composition like this: count = show . length . unwords
- zvrba 6y agoBut is _just reading_ from a database a "side-effect"? It is non-deterministic, but it has no side-effects. So IO jumbles together the two notions.
- uryga 6y agopedantically, talking to the database will involve sending a request or some kind of IPC, all of which are usually considered side effects. [handwavy analogy alert] people often call it "side-effects" as a shorthand. in reality an expression being of type `IO Foo` mostly just tells the compiler that order of execution matters (which isn't the case with pure functions): do print "a" print "b" -- obviously not the same as do print "b" print "a" and also that it can't eliminate common expressions: do a <- readBytes file 100 b <- readBytes file 100 doStuff a b -- obviously not the same as do x <- readBytes file 100 doStuff x x it's a way of enforcing ordering in a lazy language where evaluation order isn't really defined.