Here is my list of bad language features:
1. It doesn't support dot notation for records.
2. Record names can't be overloaded.
3. Monads might lead to messy code due to verbose syntax. For example, instead of writing c <- openDbConnection (readConfigData), I have to write ugly code
do
conf <- readConfigData
c <- openDbConnection
The situation becomes worse, the more side effecting parameters you need for a function call.5. Many extensions, essential for productive development are out of the language standard. For example, multi param type classes, existential types. Haskell' standard is in progress for a very long time, and still isn't finished.
> Monads might lead to messy code due to verbose syntax.
Your example could be written `do c <- openDbConnection =<< readConfigData` (assuming there should be a `conf` in the third line). If you need multiple parameters passed like this you're right, you probably need to execute them separately and give their results names. That however can also lead to more readable code if getting the parameters isn't just a single statement.
> Many extensions, essential for productive development are out of the language standard. There is always this discussion what "valid Haskell" is, on the one side the "Haskell Report" guys, on the other the "whatever GHC accepts for the next 10 years" section. A few remarks:
- GHC is a giant project that uses neither multi-parameter typeclasses nor existentials.
- I don't think language extensions are ever removed from GHC, just pronounced deprecated. (I don't know about such a case at least, correct me if I'm wrong)
- Heavy use of language extensions does mean GHC lock-in. It's free software, but I can still see how that could be a concern.
#5 is a fair to a point. I don't find MPTC or existential types to be useful in almost any case: I actively try to avoid both. ScopedTypeVariables, FlexibleContexts, FlexibleInstances and potentially RankNTypes and the poorly named UndecidableInstances should be standard though (imo). Some people also find OverlappingInstances and the like to be generally useful but you probably don't want to hear what I'd say about the subject.
There are plenty of other (also subjectively ugly) ways to write #3. SHE (a Haskell preprocessor) deals with it in one way, see the pigworker's idiom brackets: https://personal.cis.strath.ac.uk/conor.mcbride/pub/she/idio.... Without a preprocessor you can certainly use applicatives or some regular monad combinators to git'r'done but a little sugar could go a long ways. I'm not attached to any of these.
Lucky for us there are many languages to choose from, personal taste has proven a fickle mistress.
import Control.Applicative
import Control.Monad
data ConfigData
data DbConnection
readConfigData :: IO ConfigData
readConfigData = undefined
openDbConnection :: ConfigData -> IO DbConnection
openDbConnection = undefined
openDbConnection2 :: ConfigData -> ConfigData -> IO DbConnection
openDbConnection2 = undefined
openDbConnection3 :: ConfigData -> ConfigData -> ConfigData -> IO DbConnection
openDbConnection3 = undefined
main :: IO ()
main = do
-- it does work...
conf <- readConfigData
_c0 <- openDbConnection conf
-- how about a flipped bind?
_c1 <- openDbConnection =<< readConfigData
-- or join/fmap...
_c2 <- join $ openDbConnection <$> readConfigData
-- need two parameters? liftA2/liftM2 has been around for a while
_c3 <- join $ liftA2 openDbConnection2 readConfigData readConfigData
-- or use Functor/Applicative to deal with arbitrary numbers of side effecting parameters, longhand idiom brackets...
_c4 <- join $ openDbConnection3 <$> readConfigData <*> readConfigData <*> readConfigData
return ()2. Definitely agreed.
3. "readConfigData >>= openDBConnection" can be used in a simple case like your example. Cases with more arguments can use "ap" or "<*>", which are perhaps a bit ugly, but most of the time you don't need them.
5. The fact that Haskell is a living language that is improving all the time is a point in its favor, I think.
http://ghc.haskell.org/trac/haskell-prime/wiki/TypeDirectedN...
3. Don't modify parameters. It makes code easier to test in any language.
5. GHC may as well be the standard. Are you really going to use a different compiler.
It's not about modifying parameters. It's about verbosity of the code. The type of the parameter is IO String.
>5. GHC may as well be the standard. Are you really going to use a different compiler.
It's a real issue. Different language extensions might modify how type inference works. If you add one more extension, you program might suddenly stop to compile. We need a fixed set of language extensions which are always available.
Do you have some real examples of this happening with GHC extensions? I haven't run across one in the field.
> We need a fixed set of language extensions which are always available.
This sounds very much like a language standard. Haskell isn't much different than other languages here, though the language committee is slow to ratify generally accepted extensions into the standard. GHC at least makes them available to people who want to kick the tires. Then read about closures in Java. Look at C++11 revisions and notice that sometimes meant cutting features, even if they were previously implemented by some compilers (template exports) or prototyped (concepts).
5. What on earth are you talking about? Conjecturing about type inference is not useful. While I may prefer some extensions to be on be default, in hundreds of thousands of lines of haskell code I am yet to see adding an extension break existing code. The worst I have seen is adding an extension slow down compilation.
In haskell you have to forget most of what you have learned in order to be successful. Otherwise you will fight the language.
No, the type of the parameter is String. IO String is the type of the expression that gets that parameter when executed. There's a popular quote by shachaf, "getLine :: IO String contains a String in the same way that /bin/ls contains a list of files" that illustrates this nicely.
do
conf <- readConfigData
c <- openDbConnection
Or just do
c <- openDbConnection =<< readConfigData
-- do something with c
Where `=<<` can be thought of an infix apply for monads.As for 5., GHC is de-facto standard.
If you're trying to sell me on Haskell, right there you just made me think "uh, ok, guys, but my code does not live in an ivory tower, and very much needs to deal with the real world, messy data, users, and so on".
I realize that Haskell can handle that, too, but in terms of copy writing, it leaves something to be desired.
They subsequently go on to write much more interesting reasons why you might consider Haskell, including examples of companies using it in the real world, but they should not lead with talk of 'purity'.
Haskell advocacy is high in the running for the least effective in the business. Boosters can't seem to help but pitch the things they care about rather than the things the listener does. Maybe it feels satisfying, but it's no way to attract converts.
A more effective approach would be to choose common tasks people perform in other languages and demonstrate how they can be less (error-prone, time-consuming, verbose) in Haskell.
For instance, I recall reading about a Haskell Web framework where the compiler guarantees user-generated content never makes it into any served HTML unsanitized. What a boon for security! However, this is not shouted from the rooftops; instead, it's taking me so long to track down I've given up in the hopes that someone will post it here instead.
I've used Yesod in actual real-world projects and it was always a pleasure, albeit sometimes slightly complicated - the new version (which I haven't used yet) promises to simplify this a lot though.
It's not really that Haskell removes impurity, more that it adds purity. The "main" function of any Haskell application is an IO action, but you can define pure functions which relinquish IO and global state. And the best practices include doing that as much as possible, which is a terrific way of making your program more comprehensible and correct.
Speculative guesses as to what might stand in the way:
1. The lack of a purity-tracking mechanism like Haskell's may make programming in this style in C more complex and error-prone.
2. The fact that C compilers don't expect you to use this programming style may mean they don't optimize common operations in the way Haskell does; for example, if you do manipulations on large arrays in a functional rather than imperative style, Haskell will optimize away a lot of the temporary arrays, but C compilers may not.
The way I've done it so far is seems to be working, and I've implemented standard types (their introduction and elimination rules) and few functions on them. The two main problems I've run into is memory leaking and portability.
Since the constructors of an algebraic data type should return const data, it's a bit difficult to free them without violating the type system. I haven't decided yet if I'll end up violating the type system or just drop that const. I think the latter makes more sense, since we're trusting C as little as possible in the first palce. Everything actually might as well be done in void pointers but I think there's some value in using types where possible for documentation's sake.
About portability. The main thing here that I ran into was that there's no real standard and portable way of doing closures or anonymous functions in C yet. Right now, what I'm doing is using nested functions, which works in GCC and clang, but I have no idea if they'd work in MSVC or any of the other compilers out there like pcc and lcc. OS X has blocks, but I figure if they're using gcc too, why not stick with nested functions. As far as I can tell, I won't have the problem of having nested functions returning before their parent does because of the eliminator model.
The libCello (http://libcello.org/) author has been able to successfully model typeclasses in C, so I will be picking that up as soon as I get around to it.
I pushed some of the code to GitHub just now for this post: https://github.com/guerrilla/libalgae
I'm glad to hear other people had the same idea. If anyone has any suggestions, please let me know, as I wouldn't mind some collaboration on this.
It's not only that purity is safe and beautiful. It's also in many cases much easier to understand, because it's about writing functions that express transformations. These transformations should make sense in a conceptual way.
When you distill your domain concepts (see "Domain-Driven Design") and write pure functions on domain data, the result is remarkably clear, testable, and debuggable, no matter which language.
Previously I would have been (prematurely) concerned with the efficiency of in-place updates, but since I'm not writing high-performance CPU-bound software, I don't care about that anymore until the profiler tells me to.
You could turn it around and say that non-haskell programs live in an ivory tower and ignore the messy world below :)
So someone has a tough problem dealing with the messy real world, and then you tell him to use Monads. Now he has two problems :-)
Ok, I'm kidding, but mbrock's answer is a lot better in terms of selling the language in that it mentions something that anyone can figure out is a good idea, without getting into the details of it.
> encourage a clean "separation of concerns" between logic and interaction.
I'd recommend taking a look at the Real World OCaml book: https://realworldocaml.org
1. Tail call elimination 2. A full-featured module system 3. Functors (functions at the module level) 4. First-class modules 5. GADTs 6. Polymorphic variants 7. A decidable type system 8. ... more?
Things OCaml lacks which Scala has:
1. Traits 2. Implicits 3. Trivial Java FFI 4. Seamless syntactic macros (c.f. camlp4) 5. ... more?
It's the most beautiful invention of computer science, Programming in prolog, especially, is the closest a programmer can come to achieving a state of nirvana. A place where the mind is at one with the problem at hand.
Having programmed more than I care to remember (since we're establishing credibility by tossing unverifiable facts in there) I think it takes the right tool for the right job. Sometimes that's prolog, sometimes that's assembly and sometimes it's something else entirely.
Every language that is in use today has a niche, no single language manages to span more than a few such niches and the larger the impedance mismatch between the problem and the language the more work you have to do.
I don't want to sound insulting, but 10 KLOC is not nearly enough to get a reasonable understanding of even very concise languages. 10 KLOCs of APL, maybe, but certainly not with C, Java (certainly not with Java) or even Ruby or Python.
Beware of Maslow's hammer.
Prolog should be more popular, it should be at least embedded in every program, even in the browser.
If there are more than a few for loops or if else statements than the majority of the program should be encoded in sql/prolog.
If you look at all these javascript frameworks, the core concept they're missing is relational programming. The program should be a loop that takes all input events like mouse clicks, etc. and then queries a prolog engine about to what actions to take.
It's getting more popular steadily. Map/reduce is being replaced/complemented with distributed sql, and the frontend will also come around as people starting embedding prolog into their guis (LAMP stack in the browser).
http://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pd...
Everything else is either interesting or not. I think Haskell is interesting from an academic research perspective, but I'm not interested in using it to write programs.