Translating Haskell to C++ metaprogramming
vandenoever.info
vandenoever.info
C++'s template system has become the ugliest part of the language, both the syntax and the perverse ways people twist the language with it. I understand most of the metaprogramming tricks are in the same vein as "Look what I made in Brainfuck!", and are more about showing off than for serious consideration, but for getting real work done it's a huge headache to work with, and the effort would be better spent doing more interesting things in languages that weren't so hard to use. And it's not every day that I suggest Haskell is easier than something else :-)
Because perception is everything in this business. Haskell is perceived to be an impractical, academic language. Therefore, people refuse to use it and instead try to translate lessons they learned from it to "more practical" languages such as C++.
If everyone was working solo and didn't need to justify their decisions to others, maybe Haskell would catch on faster. As it is, people seem to find it harder to convince others in their groups/teams/companies to use Haskell than to simply go with what everybody else is using.
†Edit: Well, it's practical for some stuff, but it's not particularly more practical than other languages, and there's a big spooky bit of danger that comes with it.
Theoretically maybe ;)
The real world disagrees with you however:
https://www.fpcomplete.com/business/haskell-industry/
https://wiki.haskell.org/Haskell_in_industry
> At the line-by-line level, purely functional code also takes more effort to edit, once written, than imperative code.
How? You can locally reason about purely functional code which makes it much easier to edit then it's imperative counterpart which likely depends on global mutable state.
> Lazy evaluation is a big risk, if you aren't writing programs that start and finish.
Have you written much lazy evaluated code? Have you written much Haskell code?
> and there's a big spooky bit of danger that comes with it.
Can you elaborate on this? It sounds like "Haskell is bad because reasons that are spooky".
Good grief.
My response was purely a rebuttal to:
"Haskell's perceived as an impractical language because it is one"
The "cherry picked pro-Haskell links" were picked because they link to examples of Haskell's practical uses in industry.
Should I have created a faulty rebuttal that didn't list real world counter-examples of "Haskell's perceived as an impractical language because it is one"?
I don't think I could find a link to practical uses of any language in industry that wasn't "cherry picked".
The "line-by-line" level has nothing to do with global mutable state. It has to do with locally mutable state. It's a lot simpler to change code that is written in terms of for loops and accumulator variables, than it is to change code written in terms of maps, folds, intercalates, whatever.
In any reasonably architected software, you can see how a function depends on global state by looking at its argument list. Those that use database connections take the database connection. Those that create database connections receive a parameter that tells them what to connect to. Pretty much everything writes log messages. If you're in a position to choose Haskell, then you're also in a position to make your software this way.
As far as "you can see how a function depends on global state by looking at its argument list" you are actually referencing systems that avoid global state. Dependency Injection is the process of taking global state and making it local state.
If you want some convincing about the for loops claim, well, first I recommend considering how keyboard users often overlook cases where using a mouse would be more efficient, and then I recommend trying some programming contest like the Google Code Jam using Haskell. Then try using C++, or D or something. Once upon a time, I didn't do programming contests, but some people from IRC got me to try the Code Jam. I thought I'd use Haskell, being very familiar with it, and the experience was a most illuminating disaster. On the other hand, C++ worked out pretty well, even though I was a total cargo cult functional programming weenie at the time and had a real bad attitude about C++, while having only used it for a couple of college classes. You'll find yourself much more aware of how much time you spend stumbling over functional code after that.
Were you familiar with pure functional data structures and algorithms or at least simulating imperative ones with ST when needed? If not, I don't think you can fairly say that Haskell is bad for programming contests whereas C++ worked out pretty well.
You at least need to compare the two with an equivalent knowledge of implementing algorithms with each, not that figuring out or even approximating that equivalence would be easy .
> You'll find yourself much more aware of how much time you spend stumbling over functional code after that.
I'm well aware of when/how much I stumble over functional code since I still have vastly more experience with the imperative paradigm.
All I can do is try and keep an open mind to logical arguments and keep writing code in multiple languages and multiple paradigms to know what the best answer is for a given scenario or set of scenarios.
> how keyboard users often overlook cases where using a mouse would be more efficient
Don't see how this is relevant, but I'll bite... what cases would this be? I of course agree that gaming or navigating a 3d world is better with a mouse, but I struggle to see where programming related tasks would be better suited to using mouse.
for loops are less structured than maps or folds meaning that divining the meaning of one is more difficult. I think that for loops seem easier because many developers have a lot of previous experience with imperative constructs.
> In any reasonably architected software, you can see how a function depends on global state by looking at its argument list.
Most code isn't reasonably architected.
> If you're in a position to choose Haskell, then you're also in a position to make your software this way.
But then you lose out on all of the other advantages of Haskell. I'd love to hear your responses to the reasons why the Haxl team at facebook chose Haskell. Here's a link:
It may not be as fast as hand optimized C. It can be within a factor of 2. Though it can be cross compiled to many platforms without any changes.
But I digress - it's my secret weapon and I'd like to keep it that way ;)
No it is not. I've been writing Haskell full-time for five and a half years and I can count on one hand the number of times that lazy evaluation has been a significant problem.
> At the line-by-line level, purely functional code also takes more effort to edit, once written, than imperative code.
This couldn't be further from the truth. Haskell's ease of maintenance is vastly better than every other language I've used because of purity and its much more advanced type system. I've had a colleague make a highly non-trivial sweeping change to a 2500 line application I had written that they had not worked with before and their change worked literally the first time they got it to compile. This is not one of those statements of "if it compiles, it works", which are obvious hyperbole to make a point. It's an actual experience I've had that happened exactly that way. My coworker and I were rather surprised and we both agreed that there's no way that could have happened in any other language in mainstream use today. It's actually still in our commit log with the description "Godly refactoring".
Haskell's type system (assuming you mean with GHC extensions because you called it advanced) only gives you an advantage in cases where other languages would have to resort to reflection or dynamic casts, i.e. specifically where they give up type safety and you'd miss a recompilation. In reasonable languages with generics, like C#, this is quite rare. For example, once in a while, in C#, you might wish you had associated types (or functional dependencies).
Blasien is meant as a way to improve software that works with XML. Big packages like LibreOffice and Calligra could benefit from it without switching to Haskell.
In fact, I've not figured out yet how to bring the features of Blasien (literal XML and compile time schema validation) to Haskell.
The problem with Haskell is that while it is very effective at expressing ideas and logic, it is just as ineffective at expressing runtime behavior. In world where everything is lazily evaluated by default and garbage collected, it is very difficult to reason about how the code will actually execute. Are you trashing the cache? Fragmenting your heap? Are you misusing the instruction cache? For performance reasons, Haskell programmers often need to forcibly make operations strict and turn off the thunk essentially. This is a layer of complexity you don't often see in blogposts and Haskell tutorials. In contrast, I can look at C or C++ code and tell you more or less exactly how it will map to assembly (with some exceptions like when compilers decide to inline).
So in short, that Haskell you know is not the full story. I've written some Haskell programs and tried very, very hard to make them fast (they were parallelized too). At the end of the day, it's hard for a language to completely outclass another one because there are tradeoffs and life isn't so simple.
Haskell is no bed of roses and C++ is not all that bad.
Some of us write real time signal processing code and C++ is a viable choice in this context.
Feel free to read it as "Haskell isn't all sunshine and rainbows."
To be clear (before you say that "sunshine and rainbows" seems like a weird idiom because sunshine causes cancer), I meant that I think Haskell also has disadvantages.
https://en.wiktionary.org/wiki/bed#Etymology
from Old English bedd (“bed, couch, resting-place; garden-bed, plot”)
"you usually want your data structures to be as strict as possible, but your control flow to be as lazy as possible"
Here's something else that might be of interest to you:
http://johantibell.com/files/haskell-performance-patterns.ht...
> I've written some Haskell programs and tried very, very hard to make them fast (they were parallelized too).
Can you share these? I'm very interested in a Haskell which is both effective at expressing ideas and logic and is very performant (I also think it's farther along at doing so than you give it credit for).
I read all the books, all the blogposts and papers; trust me, I did the obvious things. Even if you make your data structures tight, the problem isn't so much that the program runs slow on average. The problem is that it is hard to predict the runtime performance characteristics, let alone guarantee them.
I'm not some kind of Haskell zealot, and I haven't even used it in several years. That said, I'm well aware of the downsides of Haskell, and I've pointed out on numerous occasions that "real life" Haskell doesn't match up with the pure, lazy Haskell people often talk about.
However, the reality is that people who really care about performance and predictability in C++ aren't doing crazy template metaprogramming because, although not as bad as Haskell, it's also difficult to reason about and understand what's going on under the hood.
My guess is that every time somebody has said, "I translated this Haskell code to C++ metaprogramming," they could have just used Haskell with no negative repercussions.
Yes, but C++ enjoys profilers like Instruments, Visual Studio Profiler, Intel Amplifier among many others that allow to check how all that meta-programming is affecting the application, while Haskell tooling is much more limited.
That said, I do use templated code in my inner loops even. After all, the benefit of generics is that you do the work at compile time instead of runtime (no casting, data marshalling is tight, etc).
Even with code that needed to be significantly templatized (as is the case with many core libraries), I have to give C++ a clear edge as far as runtime debuggability and performance is concerned.
Because sadly it isn't a first class language in OS vendor's SDKs.
Even Swift and F# have more possibilities to succeed in the industry, exactly because of that.
The claims about being able to formally prove the code implements the spec would be pretty damn compelling in that space too.
Once upon a time I asked on HN if there are any useful and usable haskell programs you can just install and run for a purpose that isn't writing haskell code. Shellcheck & pandoc seemed to be the suggested examples. Is that still the case or are there now a few more?
Secondly, if you know where someone has claimed that Haskell lets you "formally prove the code implements the spec" then please point it out to me so I can challenge them for being misleading at best.
SeL4. It comes up regularly that C, or java or python or some other well used language doesn't work well with isabelle, coq because of pointers, destructive assignment or whatever but haskell is pure and this is one of the many advantage of pure. I don't have this opinion myself nor have I formed the opinion that it's false.
cryptol (https://en.wikipedia.org/wiki/Cryptol)
seL4 (https://en.wikipedia.org/wiki/L4_microkernel_family#High_ass...)
Probably because of the broad agreement among crypto people that reinventing core crypto wheels is almost always a bad idea. On top of that, the intersection between the set of people who are qualified to write that crypto code and the set of people who know Haskell well enough to do a good job of it is probably the empty set.
"This library provides native Haskell TLS and SSL protocol implementation for server and client."
0: http://hackage.haskell.org/package/tls
EDIT: This Github issue provides some great discussion about the security of this library and links to some good discussion as well:
TemplateHaskell --DSLs that are transformed to (mostly rather repetitive) Haskell at compile time-- are MP in my idea, also: macros in LISP and open classes modified my the program at runtime in Ruby.
Non the less a very interesting article! Compared to C++ Haskell's syntax is soo clean!
Wikipedia says: "Metaprogramming is the writing of computer programs with the ability to treat programs as their data." Writing C++ templates to treat Relax NG specifications and literal XML in C++ as input for schema validation would fit that definition.
The following syntax in Haskell causes a parse error in my brain:
contains :: NameClass -> QName -> Bool
I really want it to be contains :: (NameClass, QName) -> Bool
Or even just contains :: NameClass, QName -> Bool
Is it just my problem to get over or do other people have it, or is there syntactic sugar coming to my rescue? contains :: NameClass -> QName -> Bool
two ways? Like, contains :: (NameClass, QName) -> Bool
And contains :: NameClass -> (QName, Bool)
? What about four types? contains :: Urk -> NameClass -> QName -> Bool contains :: (NameClass, QName) -> Bool
or contains :: NameClass -> (QName -> Bool)
In other words, you can treat it as a function taking two arguments, or one function taking one argument and returning another function.a -> b -> c -> d always means a -> (b -> (c -> d)).
"->" is right-associative.
f :: A -> B -> C -> D
f a :: B -> C -> D
f a b :: C -> D
f a b c :: D
This is why Haskell's function call syntax seems a bit funny, and the typing syntax (->) seems funny. Every time you apply an argument you get a new object back which represents the result of applying the function. Once you've applied everything to the left of the rightmost type, you have the result.
It's a statement of implication. If I have an a and an f, the most I can have is a function which takes arguments of type B, C, and D, and so forth.
contains :: (NameClass, QName) -> Bool
is valid Haskel, and denotes that "contains" is a function that takes a tuple and returns a bool. As a matter of style, Haskellers tend to avoid writing function like this, and prefer to write functions like contains :: NameClass -> (QName -> Bool)
which indicates that "contains" is a function that takes a "NameClass" and returns another function. Because this pattern is so common in Haskell, -> is defined to be right-associative, so the paranthese can be omited.In it kinda the case in C++ too. C++ template system is a turing-complete machine at compiling time.