
Rust as a gateway drug to Haskell - miqkt
http://xion.io/post/programming/rust-into-haskell.html
======
slaymaker1907
Something that I found about going from Haskell to Rust was that Rust provides
many powerful abstractions without compromising on performance. It was very
frustrating to try reasoning about performance in Haskell due to its laziness.
Every language has quirks about how to write performant code, but Haskell is
notorious in my mind for being quite easy to write obscenely slow code in.

~~~
oconnore
Comparing it against Rust doesn't really seem fair. If I wasn't using Haskell,
I'd be writing Erlang, and last I checked GHC is much faster than Erlang/OTP.
Certainly faster than CPython, Ruby, and most Node.js.

Trying to compete with Rust or C++ performance in Haskell is certainly
frustrating to reason about :)

~~~
pletnes
While "GHC is faster than CPython" is a fairly accurate generalization, I
still think python performance is easier to reason about. Haskell can give you
fast code, but sometimes it will be super slow for no obvious reason.

~~~
chongli
_super slow for no obvious reason_

Obviousness is hard to pin down. What may be completely obscure to a Haskell
beginner is obvious to a veteran. Is it really fair to count people's strict-
language preconceptions against Haskell? I don't know.

GHC provides a wealth of profiling tools that an expert Haskell user can
employ to find space leaks. Heck, experts generally know where to look
(excessive use of lists, spine-nonstrict data structures) so that the
profiling tools become a last resort.

~~~
whatshisface
A sufficiently smart [compiler | developer] can [make it so that | think]
anything is easy.

Since performance is exactly a measure of the number of imperative steps that
need to happen in a program, low level imperative langauages are going to win
in performance-obviousness every time. (Rust probably beating C in terms of
avoiding memory problems, C probably beating rust in terms of not supporting
language constructs that can make a single line worse than O(1).)

"Accidentally" asking the language to do something time-expensive is as
impossible in C as forgetting a free() in Haskell.

~~~
xwvvvvwx
_Since performance is exactly a measure of the number of imperative steps that
need to happen in a program_

This is an oversimplification. Writing fast code for modern processors is all
about how much time you spend waiting for memory.

~~~
saosebastiao
Cache locality will never beat algorithmic complexity as a performance factor.
Don't exaggerate its importance.

~~~
falcolas
It can, when the constant portion of the algorithm chosen is high and the
number of items being pushed through the algorithm is low.

e.g. bubblesort will beat quicksort for tiny lists or large lists which are
already sorted.

Or, put another way, 1000 operations against 1ns latency memory (L1) has the
same performance as 10 operations against 100ns latency memory (RAM).

------
le0n
Haskell/GHC will soon have an extension for linear types, which will bring the
languages much closer: [http://blog.tweag.io/posts/2017-03-13-linear-
types.html](http://blog.tweag.io/posts/2017-03-13-linear-types.html)

~~~
kccqzy
Yeah right. If only the standard library (the base package) could immediately
be converted to use linear types and we can all benefit from that! (Hint: it
won't. Even the fairly no-brainer AMP and BBP took an absurdly long time. This
is extremely slow by Rust standards. The Haskell community might, but the
stewards of the standard libraries don't have the move-fast-and-break-things
attitude.)

~~~
Tarean
Did rust ever have a large scale breaking change? Anyway, the linear type
proposal is designed to be completely backwards compatible so that shouldn't
be as big of an issue.

~~~
kccqzy
Yes I know it's completely backwards compatible. But in practice if the
standard libraries don't change, (a) there won't be a ready source of
inspiration and examples to copy from; (b) actually the feature is really
geared more towards libraries than applications, so it's less useful if the
base library doesn't adopt it wholeheartedly.

~~~
Tarean
Fair enough, although it would be easy to have an alternative prelude with
linear types that libraries could depend on. Just that the extra dependency
would really suck.

Haven't sunk a lot of time into it yet to be honest, I think the biggest
beneficiaries will be streaming libraries and non-gced data structures like
long lived queues?

------
toprerules
The similarities are striking. Both have a problem with undocumented or
experimental modules for everyday tasks, both use compiler plugins to make
sure that every package is using its own superset of the language (although in
Rust this only applies to nightly), both tout lofty goals while rarely
producing production grade systems. Rust is the first toe dip into the world
of blog posts heralding the"coming of the age of reason" when we all see the
error of our ways for writing dangling pointer references and impure
functions. To be a complete cynic, I hope Rust runs right away from over
generalized abstractions and the Haskell indifference to achieving real
success by overcomplicting even the most mundane of problems.

~~~
motet_a
Yes. Complex languages are not required to solve complex problems. This is why
Go and JavaScript are so popular.

~~~
Sharlin
I'd say neither Go nor JavaScript are predominantly used to solve complex
problems. The main use cases for both are solving mundane problems by mundane
programmers. In case of Go that's an explicit design philosophy.

------
vesak
I wonder if Idris could some day take Haskell's place. It sports default eager
evaluation. Seems like the tooling (package managers etc.) isn't quite there
yet, though.

~~~
runeks
Is a difference in a default option really enough for a language to take the
place of another? And if it's not just a change in a default option, but the
removal of laziness, we lose something, which will make the choice less
obvious.

I think it's more likely that, if dependent types prove really useful, Haskell
will adopt these, and people will adapt their code, rather than port
everything to a new language.

As far as I can see, Idris is too much like Haskell to take its place. Porting
thousands of libraries to a new language is a huge effort, so a huge advantage
is required, which I don't see Idris offering.

~~~
firethief
A System F type system can't be replaced by a dependent type system without
affecting existing code. There is no way to turn the libraries in Haskell into
libraries in dependently-typed Haskell without "porting them to a new
language" and all the work that entails.

~~~
taktoa
This is 100% false. The Calculus of Constructions is a superset of System F.
You may be thinking of the fact that if you want your type system to be sound
when interpreted as a logic, then termination must be guaranteed (which would
break backwards compatibility). Dependent types and totality are often found
together, but are in actuality orthogonal features.

------
kbutler
Isn't the most significant line: "For work-related reasons, I had to recently
get up to speed on programming in Haskell"?

I was somewhat disappointed not to have a followup to what work-related
reasons there might be. Note also that the About page says "work at Facebook".

~~~
dllthomas
If you're puzzling at the connection between Facebook and Haskell,
[https://github.com/facebook/Haxl](https://github.com/facebook/Haxl) is the
most obvious one.

------
sandGorgon
I have heard the same thing about Kotlin now - [https://hackernoon.com/kotlin-
functors-applicatives-and-mona...](https://hackernoon.com/kotlin-functors-
applicatives-and-monads-in-pictures-part-1-3-c47a1b1ce251)

~~~
throwaway91111
Err kotlin as a gateway drug to haskell? They might wanna fix type erasure
first....

~~~
corn_dog
maybe kotlin as a gateway drug to scala as a gateway drug to haskell

------
leshow
I have found them to be very similar too. I started learning Haskell in
earnest last September, following along with the exceptional "Haskell: First
Principles" which I highly recommend. I had been toying with Rust around this
time and I found a confluence of ideas between the two languages. Most things
I learned in Haskell I was able to carry over to Rust.

The inverse was not quite as true. But I still love Rust. Any time I need
something performant, or I just want to play around, it's my first choice.

------
asdfaoeu
"the legacy C++"

Interesting reading but the author seems intent on pushing some buttons.

~~~
teamhappy
Maybe that's just his way of saying "C++ without concepts."

------
spullara
Maybe the other way around.

------
allengeorge
The article references using trait objects for dynamic dispatch. FWIW, I found
them extremely limited: you can't use generics, can't return Self... I wonder
how often people use them.

~~~
cbcoutinho
What do you mean you can't return self? I'm just starting out with learning
rust, but from what I've gathered you can use objects in a myriad of ways that
will allow you do what you need.

Instead of returning self, you could just pass a reference - you need to
mutate self then just pass a mutable reference. Did I miss something here?

~~~
steveklabnik
Returning Self in a trait object isn't possible; it's not object safe. In
order to return Self, you'd need to know the concrete type of the thing, and
when you have a trait object, that's erased.

Returning a trait object instead of plain Self would work though.

------
pjmlp
> More advanced type systems, however, allow to specify the generic
> constraints explicitly.

Well, Java allows it, even if it looks a bit unglier than the Rust example.

    
    
        <T extends Comparable<T>> T min (T a, T b) {
            if (a.compareTo(b) > 0) { return b; } else { return a; }
        }
    

And even without general availability of concepts (already in gcc 6.x), one
can achieve it in C++ via _if constrexpr_.

~~~
DaiPlusPlus
The difference with Rust is that you can apply a trait to an extant type you
don't "own" \- you can't do that in Java or C#: you would need to use the
decorator-pattern (boilerplate ahoy!). C++'s checks are closer to duck-typing
in practice.

~~~
louthy
Actually you can do ad-hoc polymorphism in C# (and I assume Java too, although
I've not used it). Here's an example of implementing an Eq and Ord 'type
class' in C#, with 'class instances' of OrdInt and OrdString. Then a simple
generic bubble-sort function which works on any type that has an Ord instance:

    
    
        public interface Eq<A>
        {
            bool Equals(A x, A y);
        }
    
        public interface Ord<A> : Eq<A>
        {
            bool GreaterThan(A x, A y);
            bool GreaterThanOrEq(A x, A y);
            bool LessThan(A x, A y);
            bool LessThanOrEq(A x, A y);
        }
    
        public struct OrdInt : Ord<int>
        {
            public bool Equals(int x, int y) => x == y;
            public bool GreaterThan(int x, int y) => x > y;
            public bool GreaterThanOrEq(int x, int y) => x >= y;
            public bool LessThan(int x, int y) => x < y;
            public bool LessThanOrEq(int x, int y) => x <= y;
        }
    
        public struct OrdString : Ord<string>
        {
            public bool Equals(string x, string y) => x == y;
            public bool GreaterThan(string x, string y) => x.CompareTo(y) > 0;
            public bool GreaterThanOrEq(string x, string y) => x.CompareTo(y) >= 0;
            public bool LessThan(string x, string y) => x.CompareTo(y) < 0;
            public bool LessThanOrEq(string x, string y) => x.CompareTo(y) <= 0;
        }
    
        public static class Testing
        {
            public static void Test()
            {
                var x = new[] { 3, 8, 1, 2, 10 };
                var y = new[] { "mary", "had", "a", "little", "lamb" };
    
                BubbleSort<OrdInt, int>(x);
                BubbleSort<OrdString, string>(y);
            }
    
            public static void BubbleSort<OrdA, A>(A[] values) where OrdA : struct, Ord<A>
            {
                bool swap;
                do
                {
                    swap = false;
    
                    for (var i = 0; i < values.Length - 1; i++)
                    {
                        var x = values[i];
                        var y = values[i + 1];
    
                        if (default(OrdA).GreaterThan(x, y)) // Ad-hoc polymorphic call to GreaterThan
                        {
                            swap = true;
                            values[i] = y;
                            values[i + 1] = x;
                        }
                    }
                }
                while (swap);
            }
        }

~~~
DaiPlusPlus
But you just proved my point: your OrdString and OrdInt types there are
implementations of the Decorator pattern (just using interfaces instead of
superclasses).

~~~
louthy
No they're not, they are not wrapping an object instance, they are providing
ad-hoc functionality only. The generic parameters provided to BubbleSort are
constraining the arguments and allowing access to the ad-hoc functionality,
something that can't be done with inheritance unless you own the type.

This is exactly how traits, implicits, type-classes, class-instances, and
concepts work in other languages like Scala, Haskell, etc.

------
v0lta
That's the most HN post title in a while

------
timwaagh
i imagine they both provide a similar hipster code high. while being fairly
hard to do something useful in or make money with.

------
decafbad
Rust and Kotlin destroy any previous claims about practical usages of Haskell.

~~~
lmm
Languages without HKT are horribly impractical for large codebases.

~~~
pathsjs
Yet there are not many large codebases in languages with HKT, and even those
which are there (mainly big Scala projects) do not use HKT

~~~
lmm
Most large codebases are terrible, and these facts are not unrelated.

~~~
AnimalMuppet
I could be wrong, but I don't think that there are enough large codebases that
use HKT for us to be able to know whether most large codebases that use HKT
are _not_ terrible.

I suspect that it is closer to the truth to say that most large codebases are
terrible, regardless of language, and that HKT won't save you. (Using HKT may
help, some. Using HTK well may help more. But the problem with large codebases
is that enough programmers work on it that the talent level tends toward the
average of the universe of programmers. That's... not good. It means that
whatever tool or technique you choose won't be used well.)

------
partycoder
Rust still allows imperative programming whereas Haskell doesn't. This makes a
big difference.

Rust is closer to Alan Turing than it is to Alonzo Church.

~~~
xedrac
Haskell definitely allows imperative programming, it's just not particularly
good at it.

~~~
eklavya
What is it lacking for imperative programming? I find it to be immensely good
at it!

~~~
cousin_it
I tried implementing some simple imperative algorithms (like union-find) and
found the Haskell code very noisy. For example, "var x = f(y)" in an
imperative language might translate to "let x = f y" or "do x <\- f y" in a
Haskell do block, and the wrong variant won't compile. There's no general
purpose idiom you can use to replace all loops, only tons of special purpose
HOFs you must memorize. Writing code that mixes different types of side-
effecting calls is combinator hell. And so on. Usually an imperative algorithm
will be much clearer in an imperative language than in Haskell, and easier to
get right on the first try.

~~~
kqr
> There's no general purpose idiom you can use to replace all loops, only tons
> of special purpose HOFs you must memorize.

Well, yes, there is: general recursion. You can recreate any loop with plain
old recursion.

But stating that as a problem is a little like coming to Java and saying that
"there's no general purpose idiom you can use to replace goto, only tons of
special semantic branching constructs you must memorize." Sure, a valid
complaint if you've been forced to only use goto in your life, but not a
reflection on good programming practise.

~~~
cousin_it
I don't think that's true. Say you have an imperative algorithm that modifies
an array in a loop. Then the Haskell encoding of that algorithm will use
writeArray. But since it's monadic, it won't look like general recursion,
it'll need to use a combinator like forM. You could say it's general recursion
plus bind and return, but it gets worse. If the loop body needs to use both
writeArray and randomness, you need more combinators. I'm not sure anyone can
reliably write that stuff on a napkin, the way I can write a loop. That's not
like the situation with gotos, because code with gotos is harder to write on a
napkin.

~~~
tome
What's wrong with this?

    
    
        import qualified Data.Vector.Mutable as V
        import           Control.Monad       (when)
        import qualified System.Random       as R
        
        main = do
            v <- V.replicate 10 0
            putStrLn "Initialised to zero"
            printThemAll v
        
            setIncreasing v
            putStrLn "Set with values increasing by 2"
            printThemAll v
        
            addRandomness v
            putStrLn "Added a random number to each"
            printThemAll v
        
        printThemAll v = loop 0 (V.length v)
            where loop i n = do
                    when (i < n) $ do
                       e <- V.read v i
                       print e
                       loop (i + 1) n
        
        setIncreasing v = loop 0 (V.length v)
            where loop i n = do
                    when (i < n) $ do
                      V.write v i (i * 2)
                      loop (i + 1) n
        
        addRandomness v = loop 0 (V.length v)
            where loop i n = do
                    when (i < n) $ do
                      r <- R.randomIO
                      e <- V.read v i
                      V.write v i (e + r)
                      loop (i + 1) n

~~~
cousin_it
That's indeed nice, and made me change my mind somewhat. I wonder if you can
write a napkin example that mixes ST with randomness, instead of using IO for
everything?

~~~
tome
It's not too bad to manually thread a seed around.

    
    
        import qualified Data.Vector.Mutable as V
        import           Control.Monad       (when)
        import qualified System.Random       as R
        import           Control.Monad.ST
        
        st = runST $ do
            v <- V.replicate 10 0
        
            setIncreasing v
        
            addRandomness v
        
        printThemAll v = loop 0 (V.length v)
            where loop i n = do
                    when (i < n) $ do
                       e <- V.read v i
                       print e
                       loop (i + 1) n
        
        setIncreasing v = loop 0 (V.length v)
            where loop i n = do
                    when (i < n) $ do
                      V.write v i (i * 2)
                      loop (i + 1) n
        
        addRandomness v = loop initial_g 0 (V.length v)
            where seed       = 1234
                  initial_g  = R.mkStdGen 1234 -- Seed
                  loop g i n = do
                    when (i < n) $ do
                      let (r, next_g) = R.random g
                      e <- V.read v i
                      V.write v i (e + r)
                      loop next_g (i + 1) n

~~~
cousin_it
I see, thanks! The imperative version would still look more direct to me, but
your versions are very nice.

