If you like Haskell but want something else, you really should consider Scala. It's not the same. But it has many of the same niceties around the rich type system, but with generally good tooling, the amazingly rich JVM ecosystem (tooling, libraries, learning materials), and a somewhat more pragmatic bent to it. Scala has a bad rep, justifiably so, due to a lot of its problems in the past: community, libraries, tools…
I find the issue is most of the niceties are also built into Rust. Scala's perfectly good though, there's very little to complain about.
Leaving Haskell behind
121–130 of 402 posts
Re: Leaving Haskell behind
#122Earlier quoted context omitted.
You should generally be writing code against typeclasses, not a particular monad transformer stack. For example: fibonacci :: MonadState (Int, Int, Int) m => m Int fibonacci = do (prev, prev2, n) 0 then put (prev + prev2, prev, n - 1) >> fibonacci else return prev2 concreteFib :: ReaderT String (StateT (Int, Int, Int) (ExceptT String Identity)) Int concreteFib = fibonacci
You misunderstand my problem. Add a logger to that fibonacci function. Potentially EVERY usage site now has to change, maybe even multiple layers. Adding a log in most languages is a local transformation. In Haskell it isn't, it can have codebase wide consequences.
newtype LoggedStateT s m a = LoggedStateT (WriterT s (StateT s m) a)
instance (Monoid s, Monad m) => MonadState s (LoggedStateT s m) where
get = LoggedStateT $ do
val > lift (put s)
(which is basically an ad hoc effect system)If on the other hand you want to reproduce the behavior of other languages, throw everything in `MyAppMonad` give it whatever capabilities you need.
Re: Leaving Haskell behind
#123Earlier quoted context omitted.
Cargo is an evolution of older ideas from Ruby's bundler, so in a sense it does have a longer pedigree than most other tools of that ilk. Bundler mostly doesn't suck. There are design choices I disagree with but there's a happy path to using it that gets a lot right. There are ways to do language tooling that don't suck. Without much direct experience of cargo I can't say whether that carries across, but I wouldn't b…
FWIW, the thing which really sucks about cargo is how it handles cross-compilation. Most "professional" workflows are always cross-compilation: even if you are technically on Linux already you don't want to build for the exact version you have so you create a sysroot and cross compile towards it... and cargo is somehow so bad at this that increasingly large numbers of projects are being forced to turn on a environmen…
Re: Leaving Haskell behind
#124Earlier quoted context omitted.
Fair enough, though I’ll note that only I’ve given an up-to-date reference.
Stack vs Cabal was a huge argument where I worked with multiple teams using different build tools. Add Nix and nix2whatever, and it was more fun. Spent half my time debugging build instructions
Re: Leaving Haskell behind
#125Earlier quoted context omitted.
Cargo is an evolution of older ideas from Ruby's bundler, so in a sense it does have a longer pedigree than most other tools of that ilk. Bundler mostly doesn't suck. There are design choices I disagree with but there's a happy path to using it that gets a lot right. There are ways to do language tooling that don't suck. Without much direct experience of cargo I can't say whether that carries across, but I wouldn't b…
FWIW, the thing which really sucks about cargo is how it handles cross-compilation. Most "professional" workflows are always cross-compilation: even if you are technically on Linux already you don't want to build for the exact version you have so you create a sysroot and cross compile towards it... and cargo is somehow so bad at this that increasingly large numbers of projects are being forced to turn on a environmen…
Can you clarify what this is referring to?
> the goal should be to make extremely difficult things easier to pull off, even if it means the simple things have to be a bit harder
There are dozens of existing build systems that have this philosophy, and personally I appreciate the default system being optimized for the average use case. The fact that the happy path is so easy is the reason that people overwhelmingly choose to use Cargo; we can lie to ourselves all we like about the appeal of things like memory safety and type safety, but at the end of the day Cargo is the reason that Rust is popular. (And to be clear, I'm not trying to say Cargo is perfect; I have a slew of my own bugs and feature requests filed against it.)
Re: Leaving Haskell behind
#126Earlier quoted context omitted.
> [python] backwards compatibility issues What issues? A lot of problems with Python is due to keeping compatibility with Python 2.0. Implicit string concat bites me fairly often for example, and it has never been useful.
2->3 has been a complete disaster, anything older than a few weeks tends to randomly break with some kind of dependency issue, sometimes requiring multiple installations of python on the same machine which will bite each other in hard to predict ways. Python is a wonderful idea but I've yet to be able to write something in python and call it 'finished' because it never ever continues to work in the longer term. Highl…
It WAS a long slog yes. But now it's pretty much done. And there really was no way to fix the unicode issue without a big painful transition.
> anything older than a few weeks tends to randomly break with some kind of dependency issue
I absolutely do not have this issue. Maybe you're using libraries very different from what I do? But I do think I have pretty wide interests/projects...
> sometimes requiring multiple installations of python on the same machine which will bite each other in hard to predict ways
I don't know what you're talking about here. Do you have an example?
> I've yet to be able to write something in python and call it 'finished' because it never ever continues to work in the longer term.
I don't have this experience.
Re: Leaving Haskell behind
#127Earlier quoted context omitted.
You should generally be writing code against typeclasses, not a particular monad transformer stack. For example: fibonacci :: MonadState (Int, Int, Int) m => m Int fibonacci = do (prev, prev2, n) 0 then put (prev + prev2, prev, n - 1) >> fibonacci else return prev2 concreteFib :: ReaderT String (StateT (Int, Int, Int) (ExceptT String Identity)) Int concreteFib = fibonacci
You misunderstand my problem. Add a logger to that fibonacci function. Potentially EVERY usage site now has to change, maybe even multiple layers. Adding a log in most languages is a local transformation. In Haskell it isn't, it can have codebase wide consequences.
fibonacci :: (MonadLogger m, MonadState (Int, Int, Int) m) => m Int
fibonacci ...
and now of course all callers must support MonadLogger. But instead of using the MonadLogger (or any mtl constraint directly) you should just be constructing an abstraction boundary with a type class synonym: class (MonadLogger m, MonadState s m) => MyMonads s m
and now you change fibonacci: fibonacci :: MyMonads (Int, Int, Int) m => m Int
fibonacci ...
And now if you need to add a monad or add Eq or whatever you just have to change your type class synonym rather than every function. Its not a problem with the language its just programing with modularity in mind, even in the type system.Re: Leaving Haskell behind
#128Earlier quoted context omitted.
You misunderstand my problem. Add a logger to that fibonacci function. Potentially EVERY usage site now has to change, maybe even multiple layers. Adding a log in most languages is a local transformation. In Haskell it isn't, it can have codebase wide consequences.
If you just want to add logging to existing operations, reinterpret them at the call site. Something like this newtype LoggedStateT s m a = LoggedStateT (WriterT s (StateT s m) a) instance (Monoid s, Monad m) => MonadState s (LoggedStateT s m) where get = LoggedStateT $ do val > lift (put s) (which is basically an ad hoc effect system) If on the other hand you want to reproduce the behavior of other languages, throw…
Re: Leaving Haskell behind
#129Earlier quoted context omitted.
The point is that Haskell's do notation works for every monad, whereas "product" in Ruby works for just a cartesian product of sets, and not any other use of monads (e.g. generating random values, async code, operations that might return errors, IO operations, and so on).
The point is more specific than that: the claim here against this example is that Haskell's `do` notation uniquely lets you avoid indentation depth. The thing is, none of those other examples cause indentation depth problems in other languages because they don't force you through type system hoops to get useful work done. Besides, you can (ab)use Enumerator for all sorts of monadic things if needs be and end up with…
They do, I've seen it happen before. Callback hell was a thing in JavaScript, before Promises happened (and later async/await). And if you've ever written in a functional programming style in a language that doesn't have a first class abstraction for monadic code, then you might have seen cascades of flatMap and map.
FWIW, do notation is not the only option, Scala has for comprehensions that accomplish the same goal.
Re: Leaving Haskell behind
#130Earlier quoted context omitted.
Stack vs Cabal was a huge argument where I worked with multiple teams using different build tools. Add Nix and nix2whatever, and it was more fun. Spent half my time debugging build instructions
It is not that much better in Java land. Getting builds right takes lots of resources, keeping new and old things running is not trivial.
Yes, there is a disagreement about whether to use maven or gradle, but IMHO they both work reasonably well out of the box.