Hacker News new | ask | show | jobs
by sparkie 81 days ago
S-expressions are more like the tupled argument form, but better.

    (f x y z)
Is equivalent to:

    (f . (x . (y . (z . ())))
Every function takes one argument - a list.

Lists make partial application simpler than with tuples (at least Haskell style tuples), because we don't need to define a new form for each N-sized tuple. Eg, in Haskell you'd need:

    partial2 : (((a, b) -> z), a) -> (b -> z)
    partial3 : (((a, b, c) -> z), a) -> ((b, c) -> z)
    partial4 : (((a, b, c, d) -> z), a) -> ((b, c, d) -> z)
    ...
With S-expressions, we can just define a partial application which takes the first argument (the car of the original parameter list) and returns a new function taking a variadic number of arguments (the cdr of the original parameter list). Eg, using a Kernel operative:

    ($define! $partial
        ($vau (f first) env
            ($lambda rest
                (eval (list* f first rest) env))))

    ($define! f ($lambda (x y z) (+ x y z)))
    (f 3 2 1)
    => 6
    
    ($define! g ($partial f 3))
    (g 2 1)
    => 6
    
    ($define! h ($partial g 2))
    (h 1)
    => 6

    ($define! i ($partial h 1))
    (i)
    => 6
We could perhaps achieve the equivalent in Haskell explicitly with a multi-parameter typeclass and a functional dependency. Something like:

    class Partial full first rest | full first -> rest where
        partial :: (full -> z, first) -> (rest -> z)
        
    instance Partial ((a,b)) a b where
        partial (f, a) = \b -> f (a, b)
        
    instance Partial ((a, b, c)) a ((b, c)) where
        partial (f, a) = \(b, c) -> f (a, b, c)
        
    instance Partial ((a, b, c, d)) a ((b, c, d)) where
        partial (f, a) = \(b, c, d) -> f (a, b, c, d)

    ...
1 comments

> Every function takes one argument - a list.

That's one way to look at it, but the major difference is that one can also pass a a list as one argument, as in `(f x y z)` and `(f (list x y z)` are not the same. The thing with tuples is that a tuple of one datum is the very same as that datum itself and the same is true with the currying situation. `(f x) y` and `f x y` are truly one and the same in Haskell and Ocaml, just as `f(x, y)` and `let val a = (x,y) in f x` are one and the same in SML. This is not the case in Rust where `f(x,y)`, a function called with two arguments, and `f((x,y))`, a function called with one argument that is a tuple of two arguments are two different things.

There is also a difference in Scheme between returning a single value that is a list containing multiple values, and actually returning multiple values, In Rust however there is no difference between returning two values and returning a pair of two values. So Rust functions actually do properly take multiple arguments but always return a single one which may or may not be a tuple. In SML, Haskell and OCaml all functions technically take only one argument and return one value.