Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

So what you're saying is that these purely functional languages are so restricted that if you want to do anything useful in them, you _need_ this concept that nobody on the internet is able to actually explain.

Makes me want to use purely functional languages even less.




While this statement seems like common sense, there are plenty of examples of limitations creating a better language. I am quite happy that nobody can write goto in code that I have to work with.


> So what you're saying is that these purely functional languages are so restricted that if you want to do anything useful in them, you _need_ this concept that nobody on the internet is able to actually explain.

What do you define as useful? Reading a file and taking the first line maybe? Here's what you need to know:

Start ghci (I'm using stack[0], if you don't have stack you can also just use `ghci` here).

    cody@cody-G46VW:~$ stack ghci
    Using resolver: lts-2.16 from global config file: /home/cody/.stack/global/stack.yaml
    Configuring GHCi with the following packages: 
    GHCi, version 7.8.4: http://www.haskell.org/ghc/  :? for help
    Loading package ghc-prim ... linking ... done.
    Loading package integer-gmp ... linking ... done.
    Loading package base ... linking ... done.
    Prelude> import Control.Applicative ((<$>))
    Prelude Control.Applicative> readFile "/etc/issue"
    "Ubuntu 15.04 \\n \\l\n\n"
    Prelude Control.Applicative> 
Now we are going to use the $ sign which allows you to avoid extra parenthesis.

    Prelude Control.Applicative> let add x y = x + y
    Prelude Control.Applicative> let subtract x y = x - y
    Prelude Control.Applicative> add 5 (subtract 3 5)
    3
    Prelude Control.Applicative> -- now using $
    Prelude Control.Applicative> add 5 $ subtract 3 5
    3
    Prelude Control.Applicative> -- what's the point of me teaching you the $ operator?
    Prelude Control.Applicative> -- it's similar to one we'll use to apply our lines function to what readFile returns

How can we split the file by newline?

    Prelude Control.Applicative> (fmap lines (readFile "/etc/issue"))
    ["Ubuntu 15.04 \\n \\l",""]

From there, how can we get the first item?

    Prelude Control.Applicative> (fmap head (fmap lines (readFile "/etc/issue")))
    "Ubuntu 15.04 \\n \\l"
The duplication of fmap is getting kind of tedious to me. We can use function composition to avoid it. I'll use the add and subtract function definitions from above, but I'm sure it's obvious what those do.

    Prelude Control.Applicative> let addFiveSubtract3 x = (add 5 . subtract 3) x
    Prelude Control.Applicative> addFiveSubtract3 0
Now you know (hopefully) how function composition works, how can you use it for real stuff?

    Prelude Control.Applicative> -- back to reading files and stuff
    Prelude Control.Applicative> fmap (head . lines) (readFile "/etc/issue")
    "Ubuntu 15.04 \\n \\l"
Remeber that seemingly pointless detour to teach you what $ does? Let's try using that.

    Prelude Control.Applicative> fmap (head . lines) $ readFile "/etc/issue"
    "Ubuntu 15.04 \\n \\l"
There is an infix version of fmap called <$> which you might notice resembles $ with the addition of square brackets, kind of like it's in a box or something. Try removing `fmap` and replacing `$` with `<$>`. I'll wait here.

....

....

....

....

....

Done? Alright, here is the solution just in case:

    Prelude Control.Applicative> (head . lines) <$> readFile "/etc/issue"
    "Ubuntu 15.04 \\n \\l"
We can actually get rid of the parenthesis on the left hand side of <$> like so:

    Prelude Control.Applicative> head . lines <$> readFile "/etc/issue"
    "Ubuntu 15.04 \\n \\l"
Now how can we print it out? Well, let's see what our type is:

    Prelude Control.Applicative> :t head . lines <$> readFile "/etc/issue"
    head . lines <$> readFile "/etc/issue" :: IO String
Something to know about Haskell is that it's okay to be naive and pick functions whose type signatures look like they do what you want. There are even search engines[1][2] that take type signatures and give you functions.

So what is the type of putStrLn?

    Prelude Control.Applicative> :t putStrLn
    putStrLn :: String -> IO ()
How could we go from `IO String -> IO ()`? Well, IO is a Monad (thing which follows some rules as defined by typeclasses). A monad defines a few things as you can see here:

http://hackage.haskell.org/package/base-4.8.0.0/docs/Prelude...

But remember, we aren't interested in intimately understanding all that stuff... just in getting our functionality working. Maybe later we can circle back and figure stuff out at a deeper level.

The first function defined is >>=, whose type is:

    (>>=) :: Monad m => m a -> (a -> m b) -> m b
What was that type we needed again? Let's compare these two:

    (>>=) :: Monad m => m a -> (a -> m b) -> m b
If we look at the type of our function getting the first line of a file:

    head . lines <$> readFile "/etc/issue" :: IO String
We see that it can fit into the m a part of this:

    (>>=) :: Monad m =>  m      a    -> (a -> m b) -> m b
                         IO   String
Keep in mind that m means Monad, as is specified in the typeclass constraint to the left of `=>`.

Now we can specialize our type signature to:

    (>>=) :: Monad m => IO String -> (String -> IO b) -> IO b
What's an `IO b`? `a` meant anything so what does `b` mean? `b` actually means anything too, but a was already taken in the earlier part of the type signature.

If we look at the type of our putStrLn function:

    putStrLn :: String -> IO ()
You might notice it fits into the second part of our new specialized type signature:

    (>>=) :: Monad m => IO String -> (String -> IO b) -> IO b
                                      String -> IO ()
aside: () is kind of like void, at least that's how I think of it. It's actual name is unit.

So our full function is now:

    (>>=) (head . lines <$> readFile "/etc/issue") putStrLn
Let's try it out:

    Prelude Control.Applicative> (>>=) (head . lines <$> readFile "/etc/issue") putStrLn
    Ubuntu 15.04 \n \l
Cool, now notice that I put parenthesis around the infix function to make it a prefix function. Let's take those parenthesis off and use it as a proper infix function:

    Prelude Control.Applicative> (head . lines <$> readFile "/etc/issue") >>= putStrLn
    Ubuntu 15.04 \n \l
Take out superfluos parens:

    Prelude Control.Applicative> head . lines <$> readFile "/etc/issue" >>= putStrLn
    Ubuntu 15.04 \n \l
There is a flipped version of >>= called =<< that is easier to read in this case to me:

    Prelude Control.Applicative> putStrLn =<< head . lines <$> readFile "/etc/issue" 
    Ubuntu 15.04 \n \l

We could have also used do notation and the `<-` or "draw from IO to variable on the left, taking appropriate actions as defined by the Monad context you are in in the case of a failure". So if you are in the Maybe monad's context, a failure will cause a break (using imperative terms) and return Nothing.

The example using do notation:

    Prelude Control.Applicative> putStrLn myLine
    Ubuntu 15.04 \n \l
There, now you know how to use monads and functors to some degree for a real problem and know the minimum knowledge (as far as I can tell) necessary. Why do all of this just to read the first line of a file?

Now that you know how the machinery works it's pretty simple and gets you composability and referential transparency.

Want to know more about Functors and Monads (maybe even applicatives O_o)? Check out Functors, Applicatives, And Monads In Pictures[3].

0: https://github.com/commercialhaskell/stack

1: https://www.haskell.org/hoogle/

2: http://hayoo.fh-wedel.de/

3: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_...


Dude, if you wanted to open a file and read the first line, then just do so. What you're doing is try to derive this simple task in a cery complicated matter full of weird symbols that, even if you don't believe it, are not entirely obvious. People who want the first line of a file want something that expressed that in an obvious matter, not with a baggage of functional programming theory.


> Dude, if you wanted to open a file and read the first line, then just do so.

After knowing how the Monads and Functors work, I can:

    putStrLn =<< head . lines <$> readFile "/etc/issue"
There are advantages to separating pure and impure functions. Pure functions can be tested more aggressively with property testing for instance. They can also be optimized more agressively.

Most of the errors in software in my experience come from impure functions, so handling it in a principled way seems to reduce bugs.

> People who want the first line of a file want something that expressed that in an obvious matter

I feel like you mean to say "want something that expressed that in a familiar matter". The Haskell definition I used at the beginning of this comment is obvious to someone familiar with the basics of the programming language.

> not with a baggage of functional programming theory.

What functional programming theory? To put this into OOP terms this is just working with well defined Objects that adhere to an interface and whose failures are encapsulated in a sensible way.

edit: I agree it's harder to see or explain these advantages. Please give the tutorial I posted a quick once-over and let me know what you think. If you have specific criticisms about smaller and more specific pieces I think we could have good discussion.

To be honest I'm constantly re-assessing whether it's worth having to deal with the "baggage" of Haskell as you refer to it. However I go back to using other less principled languages and things simply don't ever seem to work out as well.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: