4 ms·
I can't speak for everyone but for me the type system is not what made Haskell difficult. I loved it. What made it difficult was laziness. I'd find myself al
by nightski 6y ago
I can't speak for everyone but for me the type system is not what made Haskell difficult. I loved it. What made it difficult was laziness. I'd find myself always trying to reason about execution (even though most of the time you probably shouldn't). Laziness might even be fine in a world built around it, but for a language to be practical it has to interface with systems that are not designed that way (numerous external libraries developed in strict languages). This isn't to say laziness is bad, or even worse - just that for a typical engineer switching from strict languages it's a large mental tax.
The problem is while you can adapt and bolt on some functional programming techniques to existing languages like C# - to bring the power of the type system to an existing language is all but impossible. There are a few areas it has started to sneak in, like nullable types. But from my naive perspective you just can't bolt on an expessive type system like that, let alone a dependently typed one.
- tunesmith 6y agoIs laziness at all related to IO/effects libraries in Scala like cats-effect or zio? Like where you have to switch your mental model to view a program as something that just describes an eventual outcome but doesn't actually "do" it until some last-minute side effect thing that is bolted on?
- lmm 6y agoIn a lazy-by-default language (Haskell) you absolutely have to use those kind of approaches, because you can't write code where effects are important any other way. You have no control over evaluation order (other than dirty hacks), so you can't rely on that to control effect sequencing. Of course in a strict language with a decent type system like Scala you eventually find you want to use that style anyway. But you can come to it in your own time and see the benefits for yourself rather than having it forced on you.
- bcatanzaro 6y agoI felt that way too. I remember the first time I tried to make a lazy list of random numbers in Haskell. It didn't work because of course the random number generator is stateful which conflicts with laziness. It was a disappointing moment for sure.
- smegma2 6y agoWhat was the problem exactly? I don't see any issues with generating e.g. an infinite list of random numbers in Haskell.
- dllthomas 6y agoOn the one hand, of course you can. But I do understand how it might seem tricky if you're too focused on trying to glue together `Gen a`s (or even `MonadRandom m => m a`s), principally with Monad. To the grandparent, assuming the trouble was as I understand it, the idea is to split your state and advance one new state down the lazy list while the rest of the computation uses the other new state. Eliding constraints for brevity, if you have: split :: g -> (g, g) random :: g -> (a, g) then you can say lazyRandomList :: g -> ([a], g) lazyRandomList oldState = let (listState, newState) = split oldState in (makeList listState, newState) where makeList inState = let (value, nextState) = random inState in value : makeList nextState When you match on the outer tuple, the split gets forced, but `makeList listState` is still a thunk. You can go ahead and use `newState` all you want without forcing it. Once you force it, you have a cons, with two thunks that can be forced independently. The tail evaluates to a similar cons produced from a different state. A lazy list. It's true that it's infinite, but you can fix that (if you want to) by taking the head (perhaps a random number of elements); it's easier to control the distribution of length that way than with some fixed chance you produce nil at each step, although that approach works too.
- smabie 6y agoThere's no reason that you can't do this. Of course, you're not going to be able to just simply return a list of random numbers. But you can get a Rand [Int] or whatever and then evaluate it in an IO monad using evalRandIO.
- nybble41 6y ago> the random number generator is stateful which conflicts with laziness It's not difficult to create a stateful computation in a non-strict runtime environment. Laziness is compatible with (local) state. Haskell's random package is designed around explicitly threading the PRNG state through the computation: import System.Random (Random, RandomGen, random) import Data.List (unfoldr) randomList ∷ (Random a, RandomGen g) ⇒ g → [a] randomList = unfoldr (Just ∘ random) Since the function passed to `unfoldr` always returns a `Just` value, the resulting lazy list never ends. If you're not familiar with `unfoldr`, it's essentially the opposite of `foldr`: instead of applying a reduction function to a list it produces a list by iteratively applying a generator function to a seed—in this case the PRNG state. As it happens, this function already exists as `System.Random.randoms`. You use it like this: main = do (xs :: [Int]) ← randoms <$> newStdGen mapM_ print xs There are related functions `randomR` and `randomRs` which take a lower and upper bound in addition to the state. If you were trying to achieve this effect with `randomIO` or `randomRIO`—perhaps to avoid explicitly threading the state—then you will run into difficulties. Not because of laziness; rather the contrary. The problem is that IO actions are strict by default, so you can't easily combine them to create a lazy list. You would need to use something like `System.IO.Unsafe.unsafeInterleaveIO` to defer the generation of the random values until they were actually demanded, but then you're mutating the shared, global PRNG state each time an item is evaluated from the list and the result would not be deterministic or repeatable even if you started with a known seed.
- quickthrower2 6y agoI find the type system and the errors difficult. The basics are easy (like you would use in Elm for example) but once you are talking about types that are related to other types, GHC extensions and all that, I quickly run into inscrutable errors that might take 1-2 hours of Googling to resolve. Basic Haskell is lovely though. It's why I liked Elm initially although it does take it a bit too far in the basic direction. The problem with Haskell is it's purity and thirst for abstraction means more and more complex type definitions and ideas - monad transformers (yes I know they are supposed to be 'easy') - and GADTs for example. And if you want to use libraries, you need to get these concepts to merely be able to read the example code. It's table stakes like known what "npm i" means when looking at a node module doco. Javascript on the other hand, for all it's warts, allows you to cheat by wrangling directly with the data. This has a lot of issues but it is a lot easier to grok - to the point where non programmers use it as a scripting language. Sometimes people do some weird shit with proxies/promises - and you'll get that type of code in any Turing-Complete and useful language, but on the whole most libraries do keep it simple and scrutable.
- sfvisser 6y agoGHC's type errors can be daunting, but the complexity of the errors usually scales quite linearly with the complexity of your code. That's good, it keeps you in line. > The problem with Haskell is it's purity and thirst for abstraction means more and more complex type definitions and ideas [...] If that's the case something goed wrong. Proper abstraction means having complex definition with simple types!
- hopia 6y agoPersonally, I found precisely the type system the most challenging part. It still is. The whole type level programming is something you just don't have in almost any other language so that's completely novel to me.
- brewmarche 6y agoPersonally I don't like the C# nullable types as they are right now. I would prefer them to be just a library type instead of a language construct. With the language construct there seems to be no way to use fmap/bind (Select/SelectMany in LINQ) on nullable types while that would be trivial if they had used an Option<T> type. Maybe I'm missing something?