5 ms·
It'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" computatio
by coolsunglasses 11y ago
It'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.
- dllthomas 11y ago"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..." You asked a question, I addressed the parts of it I could address. I don't have a full picture of what you do and don't understand, to reference in pruning my response. If you're aware that do-notation is syntactic sugar then of course I would expect you to be aware that it has no runtime overhead - but I regularly encounter people confused on that first point. My comment adds to the discussion if you're one of those confused people (which apparently you aren't), if one of those people winds up reading the thread, and also to clarify your question for anyone else interested in answering. My answer wasn't intended to be derisive in any way - I don't appreciate abuse in response.
- asQuirreL 11y agoThere doesn't need to be an overhead to this extra type disciplining of side-effectful computing. It is perfectly possible for the compiler to take the pure monadic code and implement it with actual side effects (which I believe GHC does do). What I do miss though (dearly) is that there is a certain cost as to what can be encapsulated. For instance, in Haskell, it is no longer so easy to hide side effects behind a pure interface.
- coolsunglasses 11y ago>For instance, in Haskell, it is no longer so easy to hide side effects behind a pure interface. This is not true. There are a number of ways to do this in Haskell. Also this is tantamount to, "my programming language won't let me lie", and is often associated with an infacility with Haskell. I mean no offense, just speaking from experience. You don't need to hide effects behind a pure interface once you're comfortable with the abstractions provided. Edit: tel's comment is quite good as-is, but I'd like to add that part of what makes ST nice isn't just having an eliminator, but also so you have a more precise delineation of what "effects" could happen. IO is kind of a big type. Still very valuable.
- asQuirreL 11y agoSo the example I have in mind is that of a Randomised Binary Search Tree (or Treap, if you want). Its interface is pure, however it requires access to an RNG for insertions. Because of this, it cannot be treated as a pure collection: you need to tell the type system, hang on, this RBST carts around some state which changes when you insert to it, despite the fact that the state doesn't change the extensional behaviour of the structure. This is a provable property, but maybe not provable by Haskell's type system. Now I know that you can hide unsafePerformIO in the interface, and in this case I expect it is fine, but this is not easy, it feels like a hack. Another example that I have personally watched play itself out in disbelief was in a fairly large Haskell project: There was need of a lookup table, with some 10000 elements. In such cases, I would have stored the lookup in a database, or a separate file at least, but because they wanted to maintain purity so badly, the solution was to write a 10000 line long Haskell file. But wouldn't it be nice if you could load a file and tell Haskell: don't worry, this won't change, so treat reads to this purely. I never said such things were impossible, merely not easy, and I don't believe this is by accident. I am almost certain that such things are made difficult to prevent abusing such features, but that doesn't stop me from missing them :) .
- bsznjyewgd 11y agoAlong the same line of this post is sigfpe's "You could have invented monads" http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html http://blog.sigfpe.com/2006/08/you-could-have-invented-monad..., which has convinced me at least a few times over the years that I could have invented monads (until I promptly forget all about it the next day). I just need someone to convince me that I could have invented monad transformers too, because while I now think I can use most of the common monads by themselves, trying to do multiple things at once just ends in (metaphorical) tears.
- quchen 11y agoMultiple things at once are still ordinary monads, and you can implement them without using transformers. Transformers are merely the realization that one can put (certain) monads together in a compositional way. An example of a monad in Haskell that is not put together via transformers, yet provides all their functionality, is RWS, being a combination of Reader, Writer and State in one hard-coded type/instance.