Call by need compared to pass by function SML uses pass‑by‑value, Haskell uses call‑by‑need. Unless I'm wrong (the purpose of this question), one can do call‑by‑need with SML, passing a function instead of a value; a function to be later evaluated when needed.

Adapting a classical example:

(* This will print `0` *)

fun pass_by_function (cond, a1, a2) =
   if cond then a1
   else a2

val e1 = fn () =&gt; 1 * 0
val e2 = fn () =&gt; 1 div 0

val _ = print (Int.toString (pass_by_function (true, e1, e2) ()))

(* This will fail with a divide error *)

fun pass_by_value (cond, a1, a2) =
   if cond then a1
   else a2

val _ = print (Int.toString (pass_by_value (true, 1 * 0, 1 div 0)))


It's a big matter for Haskell to be call‑by‑need. I'm just wondering if there is something else or if it's just, say “syntactic sugar”, compared to the way it needs to be done with SML.

Is this just a different way to write the same or is there a semantic difference I'm not aware of? Passing a function, is it indeed always the same as call‑by‑need? If not, when and how does it differs?
Call-by-need is more than call-by-name.  Call-by-name is syntactic sugar for wrapping a closure (thunk) around each argument and then passing the pointer to the closure.  Your example shows what call-by-name turns into under the covers.

Call-by-need goes one step further by memoizing.  If an argument is used twice in a function then the thunk corresponding to that argument is only evaluated once, the result is stored, and then on the second (and after) use of that argument the stored value is returned instead of paying the cost of reevaluating the function.

This only works because Haskell is pure functional.  That means that every function call always returns the same result for the same inputs.  With a language with any mutation you can't make this guarantee, so it is much harder to do call-by-need automatically.  SML allows mutation.