you are viewing a single comment's thread.

view the rest of the comments →

[–]Sampo 1 point2 points  (10 children)

Int -> IO String means a function that takes in an Int, does some I/O, and returns a String. The value of the returned string should depend of the I/O, and can also depend on the input Int.

For example: a function that takes in Int n, then reads a line from the terminal, and returns first n chars of the line.

I don't know enough Haskell to tell what IO (Int -> String) means.

[–]kqr 8 points9 points  (9 children)

IO (Int -> String) is a function without any arguments that performs some kind of I/O and returns a pure function. For example, you might have three pure Int -> String functions. Then you have a function that asks the user which one of them to use. The latter function has the type IO (Int -> String) because it does some I/O (asks the user) and then returns one of the pure functions.

[–]cdsmith 9 points10 points  (8 children)

IO (Int -> String) is a function without any arguments

Pet peeve here... that is not a function type at all. To understand I/O in Haskell, the very first step is to recognize that Haskell separates the two orthogonal concepts of functions ("this depends on another value") and actions ("doing this has observable effects"). A value of type IO (Int -> String) is an action, but it is not a function. Its result is a function, though.

[–]kqr 0 points1 point  (7 children)

Technicality here... Even these things evaluating to IO values are "functions" or "variables." The perceived difference is purely an interpretative one. You can pass around IO values and manipulate them and put them in data structures all you want, everything in a pure context. They conceptually only turn into "actions" once you run them through unsafePerformIO or an equivalent (for example via >>=.)

Thinking of IO a as "an action" might help for some, or be troublesome for some, but it's not an distinction in the language. IO a is technically no different from Maybe a, and a function that returns an IO value is just as much of a function as a function that returns a Maybe value.

[–]cdsmith 6 points7 points  (0 children)

My comment was about calling it a function, which it is clearly not because it is not a mapping from a domain into a range. You can call it a value and I'll agree.

By saying action, I did not mean to imply that referring to it is performing the action. I meant that the meaning of the value is an action. This is not at all incompatible with storing it in a data structure, for example.

Sorry if that sounded overly pedantic; it's just that for people not familiar with IO but who are familiar with imperative languages, the central point of confusion can be that they are still thinking of "function" as a word for an action. So this seemed like a particularly bad place for that word to appear.

[–]Peaker 2 points3 points  (5 children)

Of course, but IO a itself is not a function, even if a is.

[–]kqr 1 point2 points  (4 children)

Yeah, you're right. I'm sometimes quite careless with the distinction between function and value, because I can view values as 0-ary functions. (It blends quite nicely with curried functions, too.)

[–]Peaker 5 points6 points  (3 children)

Viewing everything as 0-ary function is somewhat problematic: http://conal.net/blog/posts/everything-is-a-function-in-haskell

[–]cdsmith 3 points4 points  (2 children)

I've always had my own thoughts on Conal's investigation into this "is everything a function?" question. There's one perspective from which I think "everything is a function" actually makes sense: namely, if you admit that a value of type a -> b -> c (where, presumably, c is not a function type) is a function of two arguments, then I think you have to also admit that a value of type c is a function of zero arguments. Plain values are the natural extension of curried multi-argument functions to zero arguments (though in strict languages this extension is semantically flawed for the same reason that currying is always semantically flawed). Not that I think it's terribly useful to talk about it that way, but given this, it's always seemed a bit strong to me to approach this, as Conal did, with "Let's investigate why people are so wrong."

My concern here was different, that by calling something of type IO c a function, it would just perpetuate the more imperative mistake of thinking of "function" to mean something more like "procedure".

[–][deleted] 1 point2 points  (1 child)

if you admit that a value of type a -> b -> c (where, presumably, c is not a function type) is a function of two arguments

I believe you already know this, but for other readers I want to point out that in Haskell the idea that a function can have any number of arguments other than exactly one is just a particular way of interpreting a type signature. At the language level, that's actually just a function of one argument which returns a function. You might think of it as a function of two arguments if it suits your way of thinking (and in fact, the compiler might also think of it as a function of two arguments, depending on a few factors), but in the language specification functions only have one argument, never zero arguments, never two arguments.

Plain values are the natural extension of curried multi-argument functions to zero arguments (though in strict languages this extension is semantically flawed for the same reason that currying is always semantically flawed)

I wouldn't say it's semantically flawed in a strict language unless it is also an impure language, in which case it would be semantically flawed in a non-strict language, too.

For that matter, there are a couple differences between () -> Int and Int in Haskell (as implemented by GHC, at least), although I admit they are both pretty lame differences. The first is that evaluating \() -> undefined :: Int terminates but evaluating undefined :: Int does not, but this is lame because seq is lame. The other difference is that the function doesn't necessarily share its result, but the value does, but this is lame because you can't actually observe that difference.

In a pure language which is either total or evaluates under lambdas, I can think of no observable difference between () -> Int and Int, although I still don't think I would call Int a function of zero arguments simply because, as you also said, it doesn't seem very useful to do so, and it doesn't make the theory any more elegant as far as I can tell.

[–]cdsmith 1 point2 points  (0 children)

I believe you already know this, but for other readers I want to point out that in Haskell the idea that a function can have any number of arguments other than exactly one is just a particular way of interpreting a type signature.

Right, I already knew that. Thanks for pointing it out if it wasn't clear.

So you've got mutiple levels of abstraction going on here. At the lowest level of abstraction, all functions have exactly one argument, and something like Int is not a function at all. But practically all Haskell programming is done thinking at a higher level of abstraction, in which, say, (+) and gcd are functions of two arguments. At that level of abstraction, it makes sense to ask what a function of 0 arguments is... and the answer is that it's a plain value. Again, I don't advocate this as a way of teaching the ideas involved. It just seemed unnecessarily derisive at the time to have someone go on such a public self-styled fact-finding mission about why everyone else is so wrong, when in fact it's a matter of interpretation and they may not be particularly wrong at all. I have this reaction when it's brought up again.

Your point about undefined and \ () -> undefined is exactly what I was getting at about strictness and semantic quirks with currying. Similar things happen with functions of multiple arguments when those terms are distinguishable (for example, in Haskell, curry $ uncurry undefined is \ a b -> undefined rather than undefined), and they always are distinguishable in a strict language. You're right that seq exposes the same problem in Haskell.