I think my answer is monads. Monads are basically contexts that wrap around a value and give meaning - whether a value exists for instance - to the numbers or strings that are encapsulated within them. They affect the values you get when you operate on them (depending on what the context is) and are stackable. That means if you have one monad called Maybe which contains information on whether the value underneath exists, you can stack another monad called IO on top of it that asserts that you are performing I/O operations on the value underneath.
Figure 1: One single monad
Figure 2: Monads in a conventional Haskell project
The reason why I am emphasising monads is because MOST MEANINGFUL THINGS you want to do in Haskell involves monads but I think they are not emphasised enough in introductory courses and students like myself will not pay extra attention to them because they sound so strange. But monads are the core of functional programming!
Another thing that makes Haskell special is the soundness of its type checks. Wait, you go. Don’t all strongly-typed languages enforce types by definition? Well, not exactly. Most people who start learning Haskell usually have some experience with strongly-typed languages like Java. But because of the need for backward-compatibility, generic types – basically types that can change depending on the use-case – are “erased” during compilation. That means that you can assign wrongly-typed variables on purpose and the Java compiler will not catch it until the variable hits some function expecting something else during runtime and explodes! 2
Figure 3: Example of ClassCastException (explosion) in Java
Haskell is able to avoid this by staying true to the meaning of types and not budging on type checks. This means a String is a String. It is not “similar” to another type like Text or a subclass because Haskell’s type system doesn’t support subtyping!
Of course, it is a little bit more complicated than that.↩︎