5 ms·
This is awesome and flies in the face of many arguments that functional and imperative programming languages need to be kept "pure" and mutually exclusive.
by wintom 11y ago
This is awesome and flies in the face of many arguments that functional and imperative programming languages need to be kept "pure" and mutually exclusive.
- tel 11y agoWho makes those arguments? Or rather, who makes them with such little sophistication? What's being described here is well-known and easily available in typed FP languages. It's a tiny application of the general properties of monads, dressed up to not have to say the word "monad". The novel bit is to give it a link-bait title.
- deleted 11y ago[deleted]
- coolsunglasses 11y agoIt'd be great if there was an interface that could be used to abstractly express the idea of computations that could possibly dependent on "previous" computations, thereby enabling you to write sequential instructions. (>>=) :: Monad m => forall a b. m a -> (a -> m b) -> m b Prelude> getLine >>= putStrLn blah blah Prelude> do a <- [1, 2]; b <- [4, 5]; if (even a) then (return b) else (return a) [1,1,4,5] Prelude> do a <- Just 1; b <- Just 1; if (even a) then (return b) else (return a) Just 1 Prelude> do a <- Just 4; b <- Just 2; if (even a) then (return b) else (return a) Just 2 Prelude> do a <- Nothing; b <- Just 2; if (even a) then (return b) else (return a) Nothing -- depending on a computation that may not have succeeded Then you could distinguish pure and impure code just from the types by assigning a type wherever effects need to be performed to get our effecty value. Prelude> :t getLine getLine :: IO String Prelude> :t putStrLn putStrLn :: String -> IO () Prelude> :t (>>=) (>>=) :: Monad m => m a -> (a -> m b) -> m b Prelude> :t (>>=) getLine (>>=) getLine :: (String -> IO b) -> IO b Prelude> :t (>>=) getLine putStrLn (>>=) getLine putStrLn :: IO () Then we could make a syntax to wrap it all up and let you write code as if it was actually sequential, imperative instructions. main = do putStrLn "Enter your name please: " name <- getLine putStrLn ("Hello, " ++ name) Maybe we could make a programming language around this, maybe name it after a mathematician that contributed to the development of lambda calculus.
- Dewie3 11y agoIt would be great if this hypothetical mechanism didn't have any interpretive overhead too, for the cases when we need to use it for imperative code for efficiency reasons.
- coolsunglasses 11y agoThere is no overhead here. IO gets erased at compile-time in GHC, IO is kept abstract and interacted with via Functor/Applicative/Monad to cooperate with the linearization. Monad was introduced to clean up the nesting and give it a nice syntax.
- Dewie3 11y agoI'm talking about the Monad style in general, with the do notation, closures and what have you. Typically such things add an interpretive overhead, in the same way that a formatting print function adds interpretive overhead unless it is implemented with a macro, some kind of metaprogramming technique to evaluate and inline the interpretive component, or other techniques. I've read that the monadic style in Haskell has this kind of overhead, which is further pronounced with monad stacks. Is that not the case?
- dllthomas 11y agoSpeaking specifically to do notation, it has no impact on performance, being a purely syntactic transformation. I have heard that transformer stacks can add "wrapping/unwrapping overhead", but haven't looked at it in depth, most of my Haskell work not being terribly performance sensitive at that scale.
- Dewie3 11y ago> Speaking specifically to do notation, it has no impact on performance, being a purely syntactic transformation. Of for Christ's sake, I of course mean the bind and the `>>` and whatever that it desugars to. You aren't adding something to the discussion. But thanks for reminding me that syntactic sugar has no runtime overhead... There seems to be a lot of potential for removing all cruft that comes from things like this, namely to remove the interpretive overhead. I used to think of "interpretive" as only those language executioners. But I suspect that a lot of constructs have them. Macros, or whatever mechanism you use to avoid that overhead should maybe have a place in languages that try to both be high-level and to be somewhat high performing.
- Dewie3 11y agoYou might be overplaying whatever fear people have of mixing imperative and functional code. It is fairly straightforward to not let mutable variables muck up pure functions -- A pure function doesn't do anything but return a value. A pure function which computes the length of a list might be implemented by an imperative procedure that loops through the list and updates a counter, which is then returned. Whether a pure function is implemented in a functional or imperative way is... an implementation detail. As far as IO effects are concerned, Haskell's approach let's IO values be first-class. You can use regular, pure functions on, where in a different system you might indeed have to try to keep the "pure" and "impure" code separate. And they say that Haskell programmers are afraid of IO... yeah, so afraid that it is treated as a first-class concept and value. :-)
- brudgers 11y agoFor historical perspective, this is the underlying idea for Haskell's IO Monad Types and what allows it to be purely functional from the perspective of the compiler. At least according to Simon Peyton Jones in this interview: http://www.se-radio.net/2008/08/episode-108-simon-peyton-jones-on-functional-programming-and-haskell/ http://www.se-radio.net/2008/08/episode-108-simon-peyton-jon...
- yareally 11y agoI'm not sure who is making those sort of arguments. Aside from many imperative languages that implement functional language features (pretty much all of them these days), there already exist hybrid languages. For example, Scala is an imperative/functional programming language hybrid. Yes, you can use Scala as mostly imperative I suppose, but I've found you end up doing some things in a functional manner in many cases because it's far easier.
- the_af 11y agoErik Meijer is making those sort of arguments, though he tends to change his mind frequently. I'm copying the conclusions of his talk "The Curse of the Excluded Middle" ( https://queue.acm.org/detail.cfm?id=2611829 https://queue.acm.org/detail.cfm?id=2611829 ), which is appropriately subtitled "'Mostly functional' programming does not work": "The idea of "mostly functional programming" is unfeasible. It is impossible to make imperative programming languages safer by only partially removing implicit side effects. Leaving one kind of effect is often enough to simulate the very effect you just tried to remove. On the other hand, allowing effects to be "forgotten" in a pure language also causes mayhem in its own way. Unfortunately, there is no golden middle, and we are faced with a classic dichotomy: the curse of the excluded middle, which presents the choice of either (a) trying to tame effects using purity annotations, yet fully embracing the fact that your code is still fundamentally effectful; or (b) fully embracing purity by making all effects explicit in the type system and being pragmatic by introducing nonfunctions such as unsafePerformIO. The examples shown here are meant to convince language designers and developers to jump through the mirror and start looking more seriously at fundamentalist functional programming."
- pron 11y agoI really don't like how some PFP proponents -- as Meijer does in that article -- make a leap from a correct premise to an unjustified conclusion. Even if we accept that unrestricted, unchecked, non-transactional mutation of shared state is a source of many bugs, it does not for one second follow that going fully referentially-transparent must be the only solution to ensure correctness. To me that argument sounds like, "wrist pain is a source of misery for many developers, hence we should chop off our arms and program using gestures with our tongue, which turns out to be a much more elegant solution" (I'm exaggerating, of course). It's as if provably verifiable, safe, imperative programming languages don't exist, and haven't been used to write safety-critical system for quite some time (actually, verifiable imperative languages have been used much more than PFP languages to develop such systems). Also, the appeal to lambda-calculus(/i) as a more "mathematical" approach to computation (as if that is a goal in itself) discounts the fact that LC -- even as a theoretical model -- has some serious drawbacks, such as the lack of a good model of computational complexity.