|
This is just my view on whether Haskell is pure, being offered up
for criticism. I haven't seen this view explicitly articulated
anywhere before, but it does seem to be implicit in a lot of
explanations - in particular the description of Monads in SBCs
"Tackling the Awkward Squad". I'm entirely focused on the IO monad
here, but aware that it's just one concrete case of an abstraction.
Warning - it may look like trolling at various points. Please keep going to the end before making a judgement. To make the context explicit, there are two apparently conflicting viewpoints on Haskell...
See what I mean about the trolling thing? I'm actually quite serious about this, though - and by the end I think Haskell advocates will generally approve. First assertion... Haskell is a pure functional language, but only from the compile-time point of view. The compiler manipulates and composes IO actions (among other things). The final resulting IO actions are finally swallowed by unsafePerformIO or returned from main. However, Haskell is an impure side-effecting language from the run-time point of view - when the composed actions are executed. Impurity doesn't magically spring from the ether - it results from the translation by the compiler of IO actions to executable code and the execution of that code. In this sense, IO actions are directly equivalent to the AST nodes in a C compiler. A C compiler can be written in a purely functional way - in principle it's just a pure function that accepts a string (source code) and returns another string (executable code). I'm fudging issues like separate compilation and #include, but all of these can be resolved in principle in a pure functional way. Everything a C compiler does at compile time is therefore, in principle, purely functional. In fact, in the implementation of Haskell compilers, IO actions almost certainly *are* ASTs. Obviously there's some interesting aspects to that such as all the partially evaluated and unevaluated functions. But even a partially evaluated function has a representation within a compiler that can be considered an AST node, and even AST nodes within a C compiler may represent partially evaluated functions. Even the return and bind operators are there within the C compiler in a sense, similar to the do notation in Haskell. Values are converted into actions. Actions are sequenced. Though the more primitive form isn't directly available to the programmer, it could easily be explicitly present within the compiler. What about variables? What about referential transparency? Well, to a compiler writer (and equally for this argument) an identifier is not the same thing as the variable it references. One way to model the situation is that for every function in a C program, all explicit parameters are implicitly within the IO monad. There is one implicit parameter too - a kind of IORef to the whole system memory. Identifiers have values which identify where the variable is within the big implicit IORef. So all the manipulation of identifiers and their reference-like values is purely functional. Actual handling of variables stored within the big implicit IORef is deferred until run-time. So once you accept that there's an implicit big IORef parameter to every function, by the usual definition of referential transparency, C is as transparent as Haskell. The compile-time result of each function is completely determined by its (implicit and explicit) parameters - it's just that that result is typically a way to look up the run-time result within the big IORef later. What's different about Haskell relative to C therefore...
So - what do you think? _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
Le Wed, 28 Dec 2011 17:39:52 +0000,
Steve Horne <[hidden email]> a écrit : > This is just my view on whether Haskell is pure, being offered up for > criticism. I haven't seen this view explicitly articulated anywhere > before, but it does seem to be implicit in a lot of explanations - in > particular the description of Monads in SBCs "Tackling the Awkward > Squad". I'm entirely focused on the IO monad here, but aware that > it's just one concrete case of an abstraction. > > Warning - it may look like trolling at various points. Please keep > going to the end before making a judgement. It is not yet a troll for me as I am a newbie ^^ > To make the context explicit, there are two apparently conflicting > viewpoints on Haskell... > > 1. The whole point of the IO monad is to support programming with > side-effecting actions - ie impurity. > 2. The IO monad is just a monad - a generic type (IO actions), a > couple of operators (primarily return and bind) and some rules - > within a pure functional language. You can't create impurity by > taking a subset of a pure language. > > My view is that both of these are correct, each from a particular > point of view. Furthermore, by essentially the same arguments, C is > also both an impure language and a pure one. > > See what I mean about the trolling thing? I'm actually quite serious > about this, though - and by the end I think Haskell advocates will > generally approve. > > First assertion... Haskell is a pure functional language, but only > from the compile-time point of view. The compiler manipulates and > composes IO actions (among other things). The final resulting IO > actions are finally swallowed by unsafePerformIO or returned from > main. However, Haskell is an impure side-effecting language from the > run-time point of view - when the composed actions are executed. > Impurity doesn't magically spring from the ether - it results from > the translation by the compiler of IO actions to executable code and > the execution of that code. > > In this sense, IO actions are directly equivalent to the AST nodes in > a C compiler. A C compiler can be written in a purely functional way > - in principle it's just a pure function that accepts a string > (source code) and returns another string (executable code). I'm > fudging issues like separate compilation and #include, but all of > these can be resolved in principle in a pure functional way. > Everything a C compiler does at compile time is therefore, in > principle, purely functional. > > In fact, in the implementation of Haskell compilers, IO actions > almost certainly *are* ASTs. Obviously there's some interesting > aspects to that such as all the partially evaluated and unevaluated > functions. But even a partially evaluated function has a > representation within a compiler that can be considered an AST node, > and even AST nodes within a C compiler may represent partially > evaluated functions. > > Even the return and bind operators are there within the C compiler in > a sense, similar to the do notation in Haskell. Values are converted > into actions. Actions are sequenced. Though the more primitive form > isn't directly available to the programmer, it could easily be > explicitly present within the compiler. > > What about variables? What about referential transparency? > > Well, to a compiler writer (and equally for this argument) an > identifier is not the same thing as the variable it references. > > One way to model the situation is that for every function in a C > program, all explicit parameters are implicitly within the IO monad. > There is one implicit parameter too - a kind of IORef to the whole > system memory. Identifiers have values which identify where the > variable is within the big implicit IORef. So all the manipulation of > identifiers and their reference-like values is purely functional. > Actual handling of variables stored within the big implicit IORef is > deferred until run-time. > > So once you accept that there's an implicit big IORef parameter to > every function, by the usual definition of referential transparency, > C is as transparent as Haskell. The compile-time result of each > function is completely determined by its (implicit and explicit) > parameters - it's just that that result is typically a way to look up > the run-time result within the big IORef later. Now as you ask here is my point of view: IO monad doesn't make the language impure for me, since you can give another implementation which is perfectly pure and which has the same behaviour (although completely unrealistic): -- An alternative to IO monad data IO_ = IO_ { systemFile :: String -> String -- ^ get the contents of the file , handlers :: Handler -> (Int, String) -- ^ get the offset of the handler } type IO a = IO { trans :: IO_ -> (a, IO_) } instance Monad IO where bind m f = IO { trans = \io1 -> let (a, io2) = trans m io1 in trans (f a) io2 } return a = IO { trans = \io_ -> (a, io_) } -- An example: hGetChar hGetChar :: Handler -> IO Char hGetChar h = IO { trans = \io_ -> let (pos, file) = handlers io_ h c = (systemFile file) !! pos newHandlers = \k -> if h==k then (1+pos, file) else handlers io_ k in (c, IO_ {systemFile = systemFile io_ ,handlers = newHandlers}) } Now how would this work? In a first time, you load all your system file before running the program (a "side-effect" which does not modify already used structures; it is just initialization), then you run the program in a perfectly pure way, and at the end you commit all to the system file (so you modify structures the running program won't access as it has terminated). I am too lazy (like many Haskellers) to show how to implement the other IO functions; but if you believe me you can do it, you can see that I haven't done any side effect. Now the "main" function has type: IO (), that is it is an encapsulated function of type : IO_ -> ((), IO_); in other words, simply an environment transformation. main really is a pure function. It is quite different from C, as in Haskell you need to provide the environment in which it is executed (stored in the type IO_); in C, it is not the case "read(5/*file descriptor, that is an int which cannot contain an environment*/, buff/*buffer (int*), cannot contain an environment*/, len/*size (int) to be read, cannot contain an environment*/); /*return type: void, cannot contain an environment*/" but this expression modifies an environment, which is the side effect. To make C pure, we need to say that all function calls are implicitly given an environment, but in C we have no way to expose this environment, and the typing system doesn't tell us if the call of the function only access or also modifies the environment. In haskell, the environment is explicitely passed to each function (although the 'do' notations tends to hide it, but it is only syntactic sugar), so by reading the signature and unfolding all "data/type/newtype" definitions, we can tell if there is or not side effect; for C, we must unfold 'terms' to understand that. In Haskell, 'hGetChar h >>= \c -> hPutChar i' always has the same value, but 'trans (hGetChar h >>= \c -> hPutChar i) (IO_ A)' 'trans (hGetChar h >>= \c -> hPutChar i) (IO_ B)' may have different values according to A and B. In C, you cannot express this distinction, since you only have: 'read(h, &c, 1); write(i, &c, 1);' and cannot pass explicitely the environment. As you can see my point of view is really different, as I do not think in the "AST way" (even if I have always heard of it, and think it is another good way to think of the monads; in fact the 2 point of view are the same for me, but express differently). > What's different about Haskell relative to C therefore... > > 1. The style of the "AST" is different. It still amounts to the same > thing in this argument, but the fact that most AST nodes are > simply partially-evaluated functions has significant practical > consequences, especially with laziness mixed in too. There's a > deep connection between the compile-time and run-time models (contrast > C++ templates). > 2. The IO monad is explicit in Haskell - side-effects are only > permitted (even at run-time) where the programmer has explicitly > opted to allow them. > 3. IORefs are explicit in Haskell - instead of always having one you > can have none, one or many. This is relevant to an alternative > definition of referential transparency. Politicians aren't > considered transparent when they bury the relevant in a mass of > the irrelevant, and even pure functions can be considered to lack > transparency in that sense. Haskell allows (and encourages) you to > focus in on the relevant - to reference an IORef Bool or an IORef > Int rather than dealing with an IORef Everything. > > That last sentence of the third point is my most recent eureka - not > so long ago I posted a "Haskell is just using misleading definitions > - it's no more transparent than C" rant, possibly on Stack Overflow. > Wrong again :-( > > So - what do you think? > my 2 cents NB. I encountered a situation where my vision of things was broken: hGetContents :: Handle -> IO ByteString in Data.ByteString.Lazy as BS The following two programs BS.hGetContents h >>= \b -> close h >> let x = BS.length b in print x -- ^ Dangerous, never do it! BS.hGetContents h >>= \b -> let x = BS.length b in close h >> print x -- ^ The right way to do is a problem for me, as BS.length doesn't receive an environment as a parameter, so as they are given the SAME b, they should give the same result. (For me all BS.ByteString operations should be done inside a Monad; the non-lazy ByteString doesn't have this problem of course) _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by Steve Horne
Steve Horne wrote:
> This is just my view on whether Haskell is pure, being offered up for > criticism. I haven't seen this view explicitly articulated anywhere > before, but it does seem to be implicit in a lot of explanations - in > particular the description of Monads in SBCs "Tackling the Awkward > Squad". I'm entirely focused on the IO monad here, but aware that it's > just one concrete case of an abstraction. > > Warning - it may look like trolling at various points. Please keep going > to the end before making a judgement. > > To make the context explicit, there are two apparently conflicting > viewpoints on Haskell... > > 1. The whole point of the IO monad is to support programming with > side-effecting actions - ie impurity. > 2. The IO monad is just a monad - a generic type (IO actions), a couple > of operators (primarily return and bind) and some rules - within a > pure functional language. You can't create impurity by taking a > subset of a pure language. > > My view is that both of these are correct, each from a particular point > of view. Furthermore, by essentially the same arguments, C is also both > an impure language and a pure one. [...] Purity has nothing to do with the question of whether you can express IO in Haskell or not. The word "purity" refers to the fact that applying a value foo :: Int -> Int (a "function") to another value *always* evaluates to the same result. This is true in Haskell and false in C. The beauty of the IO monad is that it doesn't change anything about purity. Applying the function bar :: Int -> IO Int to the value 2 will always give the same result: bar 2 = bar (1+1) = bar (5-3) Of course, the point is that this result is an *IO action* of type IO Int , it's not the Int you would get "when executing this action". Best regards, Heinrich Apfelmus -- http://apfelmus.nfshost.com _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
On 28/12/2011 20:44, Heinrich Apfelmus wrote:
> Steve Horne wrote: >> This is just my view on whether Haskell is pure, being offered up for >> criticism. I haven't seen this view explicitly articulated anywhere >> before, but it does seem to be implicit in a lot of explanations - in >> particular the description of Monads in SBCs "Tackling the Awkward >> Squad". I'm entirely focused on the IO monad here, but aware that >> it's just one concrete case of an abstraction. >> >> Warning - it may look like trolling at various points. Please keep >> going to the end before making a judgement. >> >> To make the context explicit, there are two apparently conflicting >> viewpoints on Haskell... >> >> 1. The whole point of the IO monad is to support programming with >> side-effecting actions - ie impurity. >> 2. The IO monad is just a monad - a generic type (IO actions), a couple >> of operators (primarily return and bind) and some rules - within a >> pure functional language. You can't create impurity by taking a >> subset of a pure language. >> >> My view is that both of these are correct, each from a particular >> point of view. Furthermore, by essentially the same arguments, C is >> also both an impure language and a pure one. [...] > > Purity has nothing to do with the question of whether you can express > IO in Haskell or not. > > The beauty of the IO monad is that it doesn't change anything about > purity. Applying the function > > bar :: Int -> IO Int > > to the value 2 will always give the same result: > Yes - AT COMPILE TIME by the principle of referential transparency it always returns the same action. However, the whole point of that action is that it might potentially be executed (with potentially side-effecting results) at run-time. Pure at compile-time, impure at run-time. What is only modeled at compile-time is realized at run-time, side-effects included. Consider the following... #include <stdio.h> int main (int argc, char*argv) { char c; c = getchar (); putchar (c); return 0; } The identifier c is immutable. We call it a variable, but the compile-time value of c is really just some means to find the actual value in the "big implicit IORef" at runtime - an offset based on the stack pointer or whatever. Nothing mutates until compile-time, and when that happens, the thing that mutates (within that "big implicit IORef") is separate from that compile-time value of c. In C and in Haskell - the side-effects are real, and occur at run-time. That doesn't mean Haskell is as bad as C - I get to the advantages of Haskell at the end of my earlier post. Mostly unoriginal, but I think the bit about explicit vs. implicit IORefs WRT an alternate view of transparency is worthwhile. I hope If convinced you I'm not making one of the standard newbie mistakes. I've done all that elsewhere before, but not today, honest. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
Le 28/12/2011 22:45, Steve Horne a écrit :
Yes - AT COMPILE TIME by the principle of referential transparency it always returns the same action. However, the whole point of that action is that it might potentially be executed (with potentially side-effecting results) at run-time. Pure at compile-time, impure at run-time. What is only modeled at compile-time is realized at run-time, side-effects included.Sorry, perhaps this is not a standard newbie mistake, but you - apparently - believe that an execution of an action on the "real world" is a side effect. I don't think it is. Even if a Haskell programme fires an atomic bomb, a very impure one, there are no side effects within the programme itself. If you disagree, show them. I don't think that speaking about "compile-time purity" is correct. Jerzy Karczmarczuk _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
On 28/12/2011 22:01, Jerzy Karczmarczuk wrote:
Le 28/12/2011 22:45, Steve Horne a écrit :True. But side-effects within the program itself are not the only relevant side-effects.Yes - AT COMPILE TIME by the principle of referential transparency it always returns the same action. However, the whole point of that action is that it might potentially be executed (with potentially side-effecting results) at run-time. Pure at compile-time, impure at run-time. What is only modeled at compile-time is realized at run-time, side-effects included.Sorry, perhaps this is not a standard newbie mistake, but you - apparently - believe that an execution of an action on the "real world" is a side effect. As Simon Baron-Cohen says in "Tackling the Awkward Squad"... Yet the ultimate purpose of running a program is invariably to cause some side effect: a changed file, some new pixels on the screen, a message sent, or whatever. Indeed it’s a bit cheeky to call input/output “awkward” at all. I/O is the raison d’ˆetre of every program. — a program that had no observable effect whatsoever (no input, no output) would not be very useful.Of course he then says... Well, if the side effect can’t be in the functional program, it will have to be outside it.Well, to me, that's a bit cheeky too - at least if taken overliterally. Even if you consider a mutation of an IORef to occur outside the program, it affects the later run-time behaviour of the program. The same with messages sent to stdout - in this case, the user is a part of the feedback loop, but the supposedly outside-the-program side-effect still potentially affects the future behaviour of the program when it later looks at stdin. A key point of functional programming (including its definitions of side-effects and referential transparency) is about preventing bugs by making code easier to reason about. Saying that the side-effects are outside the program is fine from a compile-time compositing-IO-actions point of view. But as far as understanding the run-time behaviour of the program is concerned, that claim really doesn't change anything. The side-effects still occur, and they still affect the later behaviour of the program. Declaring that they're outside the program doesn't make the behaviour of that program any easier to reason about, and doesn't prevent bugs. A final SBC quote, still from "Tackling the Awkward Squad"... There is a clear distinction, enforced by the type system, between actions which may haveSBC may consider the side-effects to be outside the program, but he still refers to "actions which may have side-effects". The side-effects are still there, whether you consider them inside or outside the program, and as a programmer you still have to reason about them. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
On 29 December 2011 10:51, Steve Horne <[hidden email]> wrote:
> As Simon Baron-Cohen says in "Tackling the Awkward Squad"... I think you've mixed up your Simons; that should be Simon Peyton Jones. Cheers, Bernie. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
On 28/12/2011 23:56, Bernie Pope wrote:
> On 29 December 2011 10:51, Steve Horne<[hidden email]> wrote: > >> As Simon Baron-Cohen says in "Tackling the Awkward Squad"... > I think you've mixed up your Simons; that should be Simon Peyton Jones. > Oops - sorry about that. FWIW - I'm diagnosed Aspergers. SBC diagnosed me back in 2001, shortly after 9/1/1. Yes, I *am* pedantic - which doesn't always mean right, of course. Not relevant, but what the hell. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
We can do functional programming on Java. We use all the design patterns for that. At the very end, everything is just some noisy, hairy, side-effectfull, gotofull machinery code. The beauty of Haskell is that it allows you to limit the things you need to reason about. If I see a function with the type "(a, b) -> a" I don't need to read a man page to see where I should use it or not. I know what it can do by its type. In C I can not do this. What can I say about a function "int foo(char* bar)"? Does it allocate memory? Does it asks a number for the user on stdin? Or does it returns the length of a zero-ending char sequence? In fact it can do anything, and I can't forbid that. I can't guarantee that my function has good behaviour. You need to trust the man page. Em 28/12/2011 22:24, "Steve Horne" <[hidden email]> escreveu:
On 28/12/2011 23:56, Bernie Pope wrote: _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by AUGER Cédric
Sorry for the delay. I've written a couple of long replies already, and
both times when I'd finished deleting all the stupid stuff there was nothing left - it seems I'm so focussed on my own view, I'm struggling with anything else today. Maybe a third try... On 28/12/2011 19:38, AUGER Cédric wrote: > Le Wed, 28 Dec 2011 17:39:52 +0000, > Steve Horne<[hidden email]> a écrit : > >> This is just my view on whether Haskell is pure, being offered up for >> criticism. I haven't seen this view explicitly articulated anywhere >> before, but it does seem to be implicit in a lot of explanations - in >> particular the description of Monads in SBCs "Tackling the Awkward >> Squad". I'm entirely focused on the IO monad here, but aware that >> it's just one concrete case of an abstraction. > IO monad doesn't make the language impure for me, since you can give > another implementation which is perfectly pure and which has the same > behaviour (although completely unrealistic): > Now how would this work? > In a first time, you load all your system file before running the > program (a "side-effect" which does not modify already used structures; > it is just initialization), then you run the program in a perfectly > pure way, and at the end you commit all to the system file (so you > modify structures the running program won't access as it has > terminated). I don't see how interactivity fits that model. If a user provides input in response to an on-screen prompt, you can't do all the input at the start (before the prompt is delayed) and you can't do all the output at the end. Other than that, I'm OK with that. In fact if you're writing a compiler that way, it seems fine - you can certainly delay output of the generated object code until the end of the compilation, and the input done at the start of the compilation (source files) is separate from the run-time prompt-and-user-input thing. See - I told you I'm having trouble seeing things in terms of someone elses model - I'm back to my current obsession again here. > In Haskell, > 'hGetChar h>>= \c -> hPutChar i' always has the same value, but > 'trans (hGetChar h>>= \c -> hPutChar i) (IO_ A)' > 'trans (hGetChar h>>= \c -> hPutChar i) (IO_ B)' > may have different values according to A and B. > > In C, you cannot express this distinction, since you only have: > 'read(h,&c, 1); write(i,&c, 1);' and cannot pass explicitely the > environment. Agreed. Haskell is definitely more powerful in that sense. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by Steve Horne
On Wed, Dec 28, 2011 at 3:45 PM, Steve Horne
<[hidden email]> wrote: > On 28/12/2011 20:44, Heinrich Apfelmus wrote: >> >> Steve Horne wrote: >>> >>> This is just my view on whether Haskell is pure, being offered up for >>> criticism. I haven't seen this view explicitly articulated anywhere before, >>> but it does seem to be implicit in a lot of explanations - in particular the >>> description of Monads in SBCs "Tackling the Awkward Squad". I'm entirely >>> focused on the IO monad here, but aware that it's just one concrete case of >>> an abstraction. >>> >>> Warning - it may look like trolling at various points. Please keep going >>> to the end before making a judgement. >>> >>> To make the context explicit, there are two apparently conflicting >>> viewpoints on Haskell... >>> >>> 1. The whole point of the IO monad is to support programming with >>> side-effecting actions - ie impurity. >>> 2. The IO monad is just a monad - a generic type (IO actions), a couple >>> of operators (primarily return and bind) and some rules - within a >>> pure functional language. You can't create impurity by taking a >>> subset of a pure language. >>> >>> My view is that both of these are correct, each from a particular point >>> of view. Furthermore, by essentially the same arguments, C is also both an >>> impure language and a pure one. [...] >> >> >> Purity has nothing to do with the question of whether you can express IO >> in Haskell or not. >> > ... > > >> The beauty of the IO monad is that it doesn't change anything about >> purity. Applying the function >> >> bar :: Int -> IO Int >> >> to the value 2 will always give the same result: >> > Yes - AT COMPILE TIME by the principle of referential transparency it always > returns the same action. However, the whole point of that action is that it > might potentially be executed (with potentially side-effecting results) at > run-time. Pure at compile-time, impure at run-time. What is only modeled at > compile-time is realized at run-time, side-effects included. > I don't think I would put it that way - the value 'bar 2' is a regular Haskell value. I can put it in a list, return it from a function and all other things: myIOActions :: [IO Int] myIOActions = [bar 2, bar (1+1), bar (5-3)] And I can pick any of the elements of the list to execute in my main function, and I get the same main function either way. > Consider the following... > > #include <stdio.h> > > int main (int argc, char*argv) > { > char c; > c = getchar (); > putchar (c); > return 0; > } > > The identifier c is immutable. We call it a variable, but the compile-time > value of c is really just some means to find the actual value in the "big > implicit IORef" at runtime - an offset based on the stack pointer or > whatever. Nothing mutates until compile-time, and when that happens, the > thing that mutates (within that "big implicit IORef") is separate from that > compile-time value of c. > > In C and in Haskell - the side-effects are real, and occur at run-time. > > That doesn't mean Haskell is as bad as C - I get to the advantages of > Haskell at the end of my earlier post. Mostly unoriginal, but I think the > bit about explicit vs. implicit IORefs WRT an alternate view of transparency > is worthwhile. > > I hope If convinced you I'm not making one of the standard newbie mistakes. > I've done all that elsewhere before, but not today, honest. > > > > _______________________________________________ > Haskell-Cafe mailing list > [hidden email] > http://www.haskell.org/mailman/listinfo/haskell-cafe _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by Thiago Negri
On 29/12/2011 00:57, Thiago Negri wrote:
> > We can do functional programming on Java. We use all the design > patterns for that. > > At the very end, everything is just some noisy, hairy, > side-effectfull, gotofull machinery code. > > The beauty of Haskell is that it allows you to limit the things you > need to reason about. If I see a function with the type "(a, b) -> a" > I don't need to read a man page to see where I should use it or not. I > know what it can do by its type. In C I can not do this. What can I > say about a function "int foo(char* bar)"? Does it allocate memory? > Does it asks a number for the user on stdin? Or does it returns the > length of a zero-ending char sequence? In fact it can do anything, and > I can't forbid that. I can't guarantee that my function has good > behaviour. You need to trust the man page. > Haskell - side-effects are only permitted (even at run-time) where the programmer has explicitly opted to allow them.". So yes. The "it could do anything!!!" claims are over the top and IMO counterproductive, though. The type system doesn't help the way it does in Haskell, but nevertheless, plenty of people reason about the side-effects in C mostly-successfully. Mostly /= always, but bugs can occur in any language. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by Antoine Latter-2
On 29/12/2011 01:53, Antoine Latter wrote:
> The beauty of the IO monad is that it doesn't change anything about > purity. Applying the function > > bar :: Int -> IO Int > > to the value 2 will always give the same result: > >> Yes - AT COMPILE TIME by the principle of referential transparency it always >> returns the same action. However, the whole point of that action is that it >> might potentially be executed (with potentially side-effecting results) at >> run-time. Pure at compile-time, impure at run-time. What is only modeled at >> compile-time is realized at run-time, side-effects included. >> > I don't think I would put it that way - the value 'bar 2' is a regular > Haskell value. I can put it in a list, return it from a function and > all other things: > > myIOActions :: [IO Int] > myIOActions = [bar 2, bar (1+1), bar (5-3)] > > And I can pick any of the elements of the list to execute in my main > function, and I get the same main function either way. using all the functional tools of the language. But if this points out a flaw in my logic, it's only a minor issue in my distinction between compile-time and run-time. Basically, there is a phase when a model has been constructed representing the source code. This model is similar in principle to an AST, though primarily (maybe entirely?) composed of unevaluated functions rather than node-that-represents-whatever structs. This phase *must* be completed during compilation. Of course evaluation of some parts of the model can start before even parsing is complete, but that's just implementation detail. Some reductions (if that's the right term for a single evaluation step) of that model cannot be applied until run-time because of the dependence on run-time inputs. Either the reduction implies the execution of an IO action, or an argument has a data dependency on an IO action. Many reductions can occur either at compile-time or run-time. In your list-of-actions example, the list is not an action itself, but it's presumably a part of the expression defining main which returns an IO action. The evaluation of the expression to select an action may have to be delayed until run-time with the decision being based on run-time input. The function that does the selection is still pure. Even so, this evaluation is part of the potentially side-effecting evaluation and execution of the main IO action. Overall, the run-time execution is impure - a single side-effect is enough. So... compile-time pure, run-time impure. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by Steve Horne
Steve Horne wrote:
> Heinrich Apfelmus wrote: >> >> Purity has nothing to do with the question of whether you can express >> IO in Haskell or not. >> > .... > >> The beauty of the IO monad is that it doesn't change anything about >> purity. Applying the function >> >> bar :: Int -> IO Int >> >> to the value 2 will always give the same result: >> > Yes - AT COMPILE TIME by the principle of referential transparency it > always returns the same action. However, the whole point of that action > is that it might potentially be executed (with potentially > side-effecting results) at run-time. Pure at compile-time, impure at > run-time. What is only modeled at compile-time is realized at run-time, > side-effects included. Well, it's a matter of terminology: "impure" /= "has side effects". The ability of a language to describe side effects is not tied to its (im)purity. Again, purity refers to the semantics of functions (at run-time): given the same argument, will a function always return the same result? The answer to this question solely decides whether the language is pure or impure. Note that this depends on the meaning of "function" within that language. In C, side-effects are part of the semantics of functions, so it's an impure language. In Haskell, on the other hand, functions will always return the same result, so the language is pure. You could say that side effects have been moved from functions to some other type (namely IO) in Haskell. Best regards, Heinrich Apfelmus -- http://apfelmus.nfshost.com _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
Sorry, a long and pseudo-philosophical treatise. Trash it before reading.
Heinrich Apfelmus: > You could say that side effects have been moved from functions to some > other type (namely IO) in Haskell. I have no reason to be categorical, but I believe that calling the interaction of a Haskell programme with the World - a "side effect" is sinful, and it is a source of semantical trouble. People do it, SPJ (cited by S. Horne) did it as well, and this is too bad. People, when you eat a sandwich: are you doing "side effects"?? If you break a tooth on it, this IS a side effect, but neither the eating nor digesting it, seems to be one. This term should be used in a way compatible with its original meaning, that something happens implicitly, "behind the curtain", specified most often in an informal way (not always deserving to be called "operational"). If you call all the assignments "side effects", why not call - let x = whatever in Something - also a "local side-effect"? Oh, that you can often transform let in the application of lambda, thus purely functional? Doesn't matter, Steve Horne will explain you that (sorry for the irony): "let is a compile-time pure construct ; at execution this is impure, because x got a value". S.H. admits that he reasons within his model, and has problems with others. Everybody has such problems, but I see here something the (true) Frenchies call "un dialogue de sourds". For me a Haskell programme is ABSOLUTELY pure, including the IO. The issue is that `bind` within the IO monad has an implicit parameter, the World. In fact, a stream of Worlds, every putWhatever, getLine, etc. passes to a new instance. We do not control this World, we call it "imperative" (whatever this means, concerning eating a sandwich, or exploding an impure neutron bomb), so we abuse the term "side effect" as hell! The "Haskell sector" of the global world, the programme itself is just a function. Pure as the robe of an angel. Simply, you are not allowed by the Holy Scripts to look under this robe. == The rest is a (pure of course) délire. Well, you might not believe me, but philosophically you don't need to imagine the World as imperative. Personally I am a believer in the Quantum Religion. If you accept all them Holy Dogmas of Unitarity, of Linearity, etc., if you believe in the True Quantum Nature of the real world, - then it becomes ... functional. Pure. Without a single trace of any "side effects". The problem is that residing inside this world precludes the possibility of considering *observed things* as pure, they are conceptually detached from the stream of the Universe Vectors. They "change", so you say: HaHa!! A particle got ASSIGNED a new position! This is an imperative side-effect! - - while from the point of view of an external observer, a common evolution operator transformed both of you, YOU and the particle into a new instance of this sector. OK, I stop here, otherwise the digestion of your sandwiches may produce some side effects. Jerzy Karczmarczuk Caen, France. (William the Conqueror was here. Produced one nice side-effect.) _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by Heinrich Apfelmus
On Wed, Dec 28, 2011 at 2:44 PM, Heinrich Apfelmus
<[hidden email]> wrote: > > The beauty of the IO monad is that it doesn't change anything about purity. > Applying the function > > bar :: Int -> IO Int > > to the value 2 will always give the same result: > > bar 2 = bar (1+1) = bar (5-3) Strictly speaking, that doesn't sound right. The "result" of an IO operation is outside of the control (and semantics) of the Haskell program, so Haskell has no idea what it will be. Within the program, there is no result. So Int -> IO Int is not really a function - it does not map a determinate input to a determinate output. The IO monad just makes it look and act like a function, sort of, but what it really does is provide reliable ordering of non-functional operations - invariant order, not invariant results. To respond to original post: no language that supports IO can be purely functional in fact, but with clever design it can mimic a purely functional language. Cheers Gregg _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
On Thu, Dec 29, 2011 at 07:19:17AM -0600, Gregg Reynolds wrote:
> On Wed, Dec 28, 2011 at 2:44 PM, Heinrich Apfelmus > <[hidden email]> wrote: > > > > The beauty of the IO monad is that it doesn't change anything about purity. > > Applying the function > > > > bar :: Int -> IO Int > > > > to the value 2 will always give the same result: > > > > bar 2 = bar (1+1) = bar (5-3) > > Strictly speaking, that doesn't sound right. The "result" of an IO > operation is outside of the control (and semantics) of the Haskell > program, so Haskell has no idea what it will be. Within the program, > there is no result. So Int -> IO Int is not really a function - it > does not map a determinate input to a determinate output. The IO > monad just makes it look and act like a function, sort of, but what it > really does is provide reliable ordering of non-functional operations > - invariant order, not invariant results. Not only strictly speaking. In practice too: bar _ = do s <- readFile "/tmp/x.txt" return (read s) Once you're in a monad that has 'state', the return value doesn't strictly depend anymore on the function arguments. At least that's my understanding. regards, iustin _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by Steve Horne
Here's an alternative perspective to consider: consider some
data structure, such as a queue. There are two ways you can implement this, one the imperative way, with mutators, and the other the purely functional way, with no destructive updates. The question then, I ask, is how easy does a programming language make it to write the data structure in the latter fashion? How easy is it for you to cheat? Edward Excerpts from Steve Horne's message of Wed Dec 28 12:39:52 -0500 2011: > This is just my view on whether Haskell is pure, being offered up for > criticism. I haven't seen this view explicitly articulated anywhere > before, but it does seem to be implicit in a lot of explanations - in > particular the description of Monads in SBCs "Tackling the Awkward > Squad". I'm entirely focused on the IO monad here, but aware that it's > just one concrete case of an abstraction. > > Warning - it may look like trolling at various points. Please keep going > to the end before making a judgement. > > To make the context explicit, there are two apparently conflicting > viewpoints on Haskell... > > 1. The whole point of the IO monad is to support programming with > side-effecting actions - ie impurity. > 2. The IO monad is just a monad - a generic type (IO actions), a couple > of operators (primarily return and bind) and some rules - within a > pure functional language. You can't create impurity by taking a > subset of a pure language. > > My view is that both of these are correct, each from a particular point > of view. Furthermore, by essentially the same arguments, C is also both > an impure language and a pure one. > > See what I mean about the trolling thing? I'm actually quite serious > about this, though - and by the end I think Haskell advocates will > generally approve. > > First assertion... Haskell is a pure functional language, but only from > the compile-time point of view. The compiler manipulates and composes IO > actions (among other things). The final resulting IO actions are finally > swallowed by unsafePerformIO or returned from main. However, Haskell is > an impure side-effecting language from the run-time point of view - when > the composed actions are executed. Impurity doesn't magically spring > from the ether - it results from the translation by the compiler of IO > actions to executable code and the execution of that code. > > In this sense, IO actions are directly equivalent to the AST nodes in a > C compiler. A C compiler can be written in a purely functional way - in > principle it's just a pure function that accepts a string (source code) > and returns another string (executable code). I'm fudging issues like > separate compilation and #include, but all of these can be resolved in > principle in a pure functional way. Everything a C compiler does at > compile time is therefore, in principle, purely functional. > > In fact, in the implementation of Haskell compilers, IO actions almost > certainly *are* ASTs. Obviously there's some interesting aspects to that > such as all the partially evaluated and unevaluated functions. But even > a partially evaluated function has a representation within a compiler > that can be considered an AST node, and even AST nodes within a C > compiler may represent partially evaluated functions. > > Even the return and bind operators are there within the C compiler in a > sense, similar to the do notation in Haskell. Values are converted into > actions. Actions are sequenced. Though the more primitive form isn't > directly available to the programmer, it could easily be explicitly > present within the compiler. > > What about variables? What about referential transparency? > > Well, to a compiler writer (and equally for this argument) an identifier > is not the same thing as the variable it references. > > One way to model the situation is that for every function in a C > program, all explicit parameters are implicitly within the IO monad. > There is one implicit parameter too - a kind of IORef to the whole > system memory. Identifiers have values which identify where the variable > is within the big implicit IORef. So all the manipulation of identifiers > and their reference-like values is purely functional. Actual handling of > variables stored within the big implicit IORef is deferred until run-time. > > So once you accept that there's an implicit big IORef parameter to every > function, by the usual definition of referential transparency, C is as > transparent as Haskell. The compile-time result of each function is > completely determined by its (implicit and explicit) parameters - it's > just that that result is typically a way to look up the run-time result > within the big IORef later. > > What's different about Haskell relative to C therefore... > > 1. The style of the "AST" is different. It still amounts to the same > thing in this argument, but the fact that most AST nodes are simply > partially-evaluated functions has significant practical > consequences, especially with laziness mixed in too. There's a deep > connection between the compile-time and run-time models (contrast > C++ templates). > 2. The IO monad is explicit in Haskell - side-effects are only > permitted (even at run-time) where the programmer has explicitly > opted to allow them. > 3. IORefs are explicit in Haskell - instead of always having one you > can have none, one or many. This is relevant to an alternative > definition of referential transparency. Politicians aren't > considered transparent when they bury the relevant in a mass of the > irrelevant, and even pure functions can be considered to lack > transparency in that sense. Haskell allows (and encourages) you to > focus in on the relevant - to reference an IORef Bool or an IORef > Int rather than dealing with an IORef Everything. > > That last sentence of the third point is my most recent eureka - not so > long ago I posted a "Haskell is just using misleading definitions - it's > no more transparent than C" rant, possibly on Stack Overflow. Wrong > again :-( > > So - what do you think? _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
In reply to this post by Gregg Reynolds-4
Quoth Gregg Reynolds <[hidden email]>,
> On Wed, Dec 28, 2011 at 2:44 PM, Heinrich Apfelmus > <[hidden email]> wrote: >> >> The beauty of the IO monad is that it doesn't change anything about purity. >> Applying the function >> >> bar :: Int -> IO Int >> >> to the value 2 will always give the same result: >> >> bar 2 = bar (1+1) = bar (5-3) > > Strictly speaking, that doesn't sound right. Look again at the sentence you trimmed off the end: >> Of course, the point is that this result is an *IO action* of >> type IO Int, it's not the Int you would get "when executing this >> action". I believe that more or less points to the key to this discussion. If it didn't make sense to you, or didn't seem relevant to the question of pure functions, then it would be worth while to think more about it. Donn _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
|
On Dec 29, 2011, at 9:16 AM, Donn Cave wrote: > Quoth Gregg Reynolds <[hidden email]>, >> On Wed, Dec 28, 2011 at 2:44 PM, Heinrich Apfelmus >> <[hidden email]> wrote: >>> >>> The beauty of the IO monad is that it doesn't change anything about purity. >>> Applying the function >>> >>> bar :: Int -> IO Int >>> >>> to the value 2 will always give the same result: >>> >>> bar 2 = bar (1+1) = bar (5-3) >> >> Strictly speaking, that doesn't sound right. > > Look again at the sentence you trimmed off the end: > >>> Of course, the point is that this result is an *IO action* of >>> type IO Int, it's not the Int you would get "when executing this >>> action". > > I believe that more or less points to the key to this discussion. > If it didn't make sense to you, or didn't seem relevant to the > question of pure functions, then it would be worth while to think > more about it. Ok, let's parse it out. "…it's not the int you would get 'when executing this action". Close, but no cooky: it's not any kind of int at all (try doing arithmetic with it). "IO Int" is a piece of rhetoric for the mental convenience of the user; Haskell does not and cannot know what the result of an IO action is, because it's outside the scope of the language (and computation). (The "Int" part of "IO Int" refers to the input, not the output; it's just a sort of type annotation.) It's not even a computation, unless you want to take a broad view and include oracles, interaction, etc. in your definition of computation. -Gregg _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
| Powered by Nabble | Edit this page |
