Purely functional on the other hand has none of these problems. This is the approach taken by the Unison Language people, which I think makes the right design decisions.
Purely functional on the other hand has none of these problems. This is the approach taken by the Unison Language people, which I think makes the right design decisions.
> Each Unison definition is identified by a hash of its syntax tree. Put another way, Unison code is content-addressed.
In either language class, you need to manage transactions, usually implicitly, if you want to do any meaningful work. This is a gap that either language class can easily solve and in many cases, there are working implementations of these ideas that do just that.
What problem are you actually trying to solve?
I think the idea is that if a function always gives the same result with the same inputs, it is considered “pure enough.”
myMethod :: IO ()
myMethod = putStrLn "Hello World!"It’s like a built in bikeshedding bait for the whole programming language.
Yes, this is a very strange belief
Non-blocking non-erroring send ! and optionally blocking/non-blocking receive, with pattern matching, are built in to the language.
Not a monad in sight.
That’s damning with faint praise.
Monads is a solution for dealing with IO in a strongly typed programming language, where we want all functions to be pure, and we don't have access to linear types. (almost) Every other programming language, when it had the chance at doing this, just threw the towel without a fight, and decided to allow IO everywhere. Haskell decided to stick to its principles instead, and produced something different, guided by a different set of constraints. Different means with different tradeoffs in different places. If Haskell instead decided to allow mutability everywhere, like most programming languages, we wouldn't be talking about it. Because it would be another language with weird syntax, and you can find a lot of them in the list of defunct programming languages at wikipedia.
And the juice is worth the squeeze if you want to see breakthrough things in programming, and not just another Algol with updated syntax.
For reference, other samples of groundbreaking (to some degree) stuff:
* Rust: mutability only when the borrow checker allows it
* Coroutines/continuations: functions that depart away from the entry/exit model
* Prolog: a programming language constructed around functions with 4 connection points: enter/exit/fail/redo
I think assuming that someone doesn't understand something is only rude if there are no indicators for a misunderstanding.
javcasas seemed to assume that TylerE doesn't understand Monads because TylerE stated that there is a Monad insanity in Haskell to replicate a print statement.
I would also assume javcasas' comment wasn't made in good faith, but I still don't see that an insult was voiced.
Not every rudeness can be interpreted as an insult I think
Truly insane.
Now do it inside a vanilla function that isn't implicitly run inside the IO monad.
What do you mean by run implicitly? I explicitly put IO expressions in the main function and they run. That’s the same as any imperative language.
I’m trying to figure out what you’re getting at. Are you saying it’s hard to do IO outside of the IO Monad?
> Look at all the monad insanity Haskell has to do to get the equivalent of a print statement
but it's easy to do that! If you meant something more complex and subtle perhaps you should have made a more complex and subtle claim.
> There is nothing that has to do with monads at all in printing a string. The idea that `putStrLn "hello world"` is monadic is as absurd as saying that `[1,2,3]` is monadic.
You haven't linked any exemplar tutorials, but I feel confident in saying the ones you're referring to teach the general concept of monads, and not how to do IO with monads.
Quite a number of JavaScript developers learned how to use `andThen` with Promises/A+ back in the day, and they didn't need to learn about monads either. (In fact, when it was raised that promises are really monads, there was serious drama and outrage [1] -- an existence proof if there ever was one, that you can use something productively without understanding it as a monad.)
Likewise, nobody would claim that you need to understand monoids before you can concatenate lists. Concatenating lists is as native to lists as sequencing commands is to IO; it's just part of how those types work. The monad interface abstracts over that idea, and learning that abstraction in its full generality is, typically, what people stumble on.
[0]: https://blog.jle.im/entry/io-monad-considered-harmful.html
[1]: https://github.com/promises-aplus/promises-spec/issues/94
Relative to programming in general, monads are easy to work with, easy enough to understand, but I’ll grant you often poorly explained.
Those people writing monad tutorials (those are written about as often as they are read) aren't trying to learn how to do I/O.
(And of course "it's just a monoid in the category of endofunctors".)
myMethod :: IO ()
myMethod = putStrLn "Hello World!"
Yes, so hard... "insane" /s f x y z = do
let
foo = x + y
bar = foo * z
putStrLn $ "Here's bar" <> (show bar)
pure bar
If all you want is to log to the console for debugging from pure code, it would look like f x y z =
let
foo = x + y
bar = foo * z
in
trace ("Here's bar" <> (show bar)) bar
Not recommended, but if you're dead set on commingling your effectful and pure code, you can freely mix them with the function unsafePerformIO.In my experience having to write large applications with significant test coverage, the time savings from not screwing around with stubs and mocks and dealing with the external effects of test runs -- the relative annoyance of using a couple "do"s and "pure"s and separating out data transformation from IO is a small price to pay.
> Look at all the monad insanity Haskell has to do to get the equivalent of a print statement
perhaps you really meant
> Look at all the monad insanity Haskell has to do to get the equivalent of a print statement within pure code
I still don't agree as such, but yes, there is indeed ceremony involved in turning something that was pure into something effectful. That is in fact the whole point!
main = putStrLn “foo” >> putStrLn “bar”
main = putStrLn "Hello, world"If you want to just write IO, you can just define a function with an IO () value and use it in any other function that resolves to IO (), or call other functions that live in IO *, or any pure functions, etc etc.
There is 5x as much ceremony for simple stuff in C or Java...
AFAIU the Haskell community recognized the issue as common enough to grant the existence of Debug.Trace.
That's a feature. It forces you to separate pure functions from I/O. If you spend time thinking about how you (re)factor your work you can end up with most of the interesting logic being in pure functions that are then very easy to write tests for (because you don't have to mock network services, clocks, etc.), and all the interesting I/O logic gets segregated and made [hopefully] small.
Yes, indeed you do. But it's a bit strange that that is a complaint. It's the whole point of fine grained effect tracking. If you change what effects a function does then you have to acknowledge that by changing the code that use that function (directly or indirectly)!
Yes, but that's not a "nightmare" (or whatever the parent called it), it's the whole value proposition.
Declaring a function as non-IO is a contract to your callers that you don't do IO. You don't need to do it. You can write everything in IO if you choose. You can also call into the wonderful ecosystem of libraries, because IO functions can call other IO functions, as well as non-IO functions.
There is only ever friction if you declare your function to be IO-free. If you declare your function to be IO-free, but call IO from inside it, it's a compile error because of course it is.
So why bother declaring anything IO-free? If you do arbitrary IO in a Parser, you can't back-track. If you do arbitrary IO in Parallel code, you invite race conditions. Transactions is my favourite example, though:
.NET [1]
> Disillusionment Part I: the I/O Problem
> It wasn’t long before we realized another sizeable, and more fundamental, challenge with unbounded transactions [...] What do we do with atomic blocks that do not simply consist of pure memory reads and writes? (In other words, the majority of blocks of code written today.) This was not just a pesky question of how to compile a piece of code, but rather struck right at the heart of the TM model.
Scala: [2]
> ScalaSTM does not have the goal of running arbitrary existing code, which is where most of their problems arose.
Java/Akka: [3]
> STM is considered as a failed experiment
Clojure: [4]
> Very simply the side-effects will happen again. In the above case this probably doesn’t matter, the log will be inconsistent but the real source of data (the ref) will be correct.
Sorry for the rant, but I really needed to highlight the fact that nonIO-calling-IO is not some language design flaw created by out-of-touch academics. It's a fundamental problem.[1] https://joeduffyblog.com/2010/01/03/a-brief-retrospective-on...
[2] https://nbronson.github.io/scala-stm/faq.html
[3] https://groups.google.com/g/akka-user/c/3JWz-X5dbe8/m/YiV4WF...
[4] https://sw1nn.com/blog/2012/04/11/clojure-stm-what-why-how/
printGreeting = print "Hello, World!"
main = printGreeting
works every bit as much as main = print "Hello, World"
The only difference for main is that it's run because it's the entry point, but that's true of most languages and almost certainly not what you're complaining about I think?