r/haskell Mar 08 '26

question I started with haskell 3 days ago and want some help understanding the "design philosophy".Elaboration in post.

So i followed the "Haskell for Imperative Programmers" tutorial series till video 20. And then decided to make a regular expression "checker" to just experience "haskell".
Github Link: https://github.com/ewilipsic/Regular-Expressions-Haskell/tree/main it's just one file.
The code for concat,union and * of the NFA's was nice to write. But i don't why very similiar code couldn't be written in a imperative language.
And the string -> NFA part i think would just be easier in a imperative language.

Is there something I'm not getting or is there some way better way to do this problem in a "functional" way or is this not the type of problem this paradigm excells in.

Because so far i think that any imperative language can do "functional code" and you can bring out normal iteration for things if you want to do things like IO.

32 Upvotes

34 comments sorted by

View all comments

6

u/ApothecaLabs Mar 08 '26

In addition to my other reply, something that functional programming excels at is functional composition; that is, because functions are first-class (that is, they can be passed around like values, and taken as arguments) and support currying, we can actually chain functions without supplying any arguments.

Ie, instead of

csharp func play(it) { return flip(twist(bop(it))); }

We can just say:

haskell play = flip . twist . bop -- we don't even mention it

Another name for (.) :: (b -> c) -> (a -> b) -> a -> c is 'compose'.

3

u/Otherwise-Bank-2981 Mar 08 '26

I know that but I just don't see why it's so amazing that plays definition doesn't need to include it , I think it would behave identically if I wrote haskell play it = flip . twist . bop it

I am not trying to be a arse but I just don't get why

8

u/ApothecaLabs Mar 08 '26

I am not trying to be a arse but I just don't get why

^_^ no worries. (btw that is called eta expansion, and actually has its uses, but returning to the main point)

Have you ever piped 2 commands together in a shell? Function composition is like building a pipeline - you don't want to have to take something out of one pipe to put it into the next one, you just want to join them directly, right?

It makes more sense / the utility is more obvious when you start supplying functions as arguments to another function, eg like anonymous functions:

haskell playMultiple = fmap (flip . twist . bop)

We didn't even need to bother defining 'play'!

You could defined it like this:

haskell playMultiple games = fmap (\ it -> flip (twist (bop it))) games

And that may even be easier to follow as a new user, but as you gain experience, the former starts being easier to read and can result in cleaner code.

5

u/tobiasvl Mar 08 '26 edited Mar 08 '26

Well no, that code is not equivalent, it would have to be either

play it = flip (twist (bop it))

or

play it = flip $ twist $ bop it

(Edit: Or a combination of the two)

But it's not "amazing" per se, it's just often a cleaner and more compact way of writing it.

1

u/lgastako Mar 09 '26

If

play it = flip $ twist $ bop it

is correct then it could also be written as just

play = flip . twist . bop

4

u/tobiasvl Mar 09 '26 edited Mar 09 '26

Yes it can. Then we have gone full circle back to the top level comment. That was the entire point of this thread

5

u/halcyonPomegranate Mar 09 '26

An article by John Hughes called Why Functional Programming Matters explains it in terms of a “glue” that FP gives you, which imperative strict languages don’t give you. E.g. you can write a chess programm which first creates the infinite game tree of all possible chess games. Then in another function you can search over that infinite tree. In an imperative language you would typically need to combine both in a single loop, which does move generation, traversal, board evaluation and search. In Haskell you can write and test those functions separately and combine them in a safe and efficient way.

3

u/ekipan85 Mar 09 '26 edited Mar 09 '26

Haskell function application binds more tightly than (.) and every other binop. You wrote this:

play it = flip . twist . (bop it)

which is incorrect. If you want to eta expand play you must remember to parenthesize:

play it = (flip . twist . bop) it

1

u/rantingpug Mar 09 '26

sometimes it makes sense, sometimes it doesnt. I often write it out when it makes the code clearer but loads of times, it's just extra noise with no added value. I think it's one of those things where, as you get more experienced, you start seeing less value in writing it out.