Shorthand Interface and Symbol/Name for POINTFREE

I've been torn on what a more "natural" name for POINTFREE might be, and what possible symbol might be used for it.

This is the "specialization by example" or "tacit programming" construct (rather impressively written in a small bit of usermode code, I might add):

>> apde: pointfree [append _ [d e]]

>> apde [a b c]
== [a b c [d e]]

>> apabc: pointfree [append [a b c]]

>> apabc [d e]
== [a b c [d e]]

>> apabc [f g]
== [a b c [d e] [f g]]

One idea was to use the operator <-, as a sort of complement to lambda.

bump-up: <- [add 1]
bump-down: <- [subtract _ 1]

Though it's kind of dumb, because it winds up looking like information is flowing in the wrong direction:

x: [1 2 3 4 5 6]
all [
    5 = until/predicate [take x] <- [greater? _ 4]
    x = [6]
]

Also it doesn't seem to couple with the code. I had the idea of making it variadic, so you would put the operator into a group:

bump-up: (<- add 1)
bump-down: (<- subtract _ 1)

This coheres slightly better to me, e.g. when using as a predicate:

x: [1 2 3 4 5 6]
all [
    5 = until/predicate [take x] (<- greater? _ 4)
    x = [6]
]

But it's less efficient (has to make a block to pass to POINTFREE*) .

The idea of fusing it as a special mode of the lambda operator when nothing is on the left is possible:

x: [1 2 3 4 5 6]
all [
    5 = until/predicate [take x] (-> greater? _ 4)
    x = [6]
]

But it's a lot of voodoo. I am almost liking the word pointfree better than the symbol here.

x: [1 2 3 4 5 6]
all [
    5 = until/predicate [take x] pointfree [greater? _ 4]
    x = [6]
]

What looks good here? If we're going to use a name like POINTFREE we might as well use TACIT because it's shorter. :face_with_diagonal_mouth:

tacit - adj. - understood or implied without being stated.

x: [1 2 3 4 5 6]
all [
    5 = until/predicate [take x] tacit [greater? _ 4]
    x = [6]
]
2 Likes

What you have here isn’t pointfree or tacit programming at all — it’s partial application. They’re related, but not the same thing. In particular, I consider partial application a great deal more useful than pointfree programming, which often does nothing but making code hard to understand.

So, with this in mind, I suggest the name partial. Short, simple and correct.

EDIT: rereading that Wikipedia article I linked, apparently C++ calls this bind, which is even shorter and still makes sense.

If this is not "pointfree" then by contrast, what would "pointfree" look like?

The term ‘pointfree’ is generally reserved for more complicated cases involving two or more functions, for instance (in Haskell):

palindrome xs = xs == reverse xs        -- pointful
palindrome' = (==) <*> reverse          -- pointfree

average xs = sum xs / length xs         -- pointful
average' = (/) <$> sum <*> length       -- pointfree

dotprod xs ys = sum (zipWith (*) xs ys) -- pointful
dotprod' = (sum .) . zipWith (*)        -- pointfree

Making this work needs a set of combinators which you can use to combine functions in various different ways. It’s generally not worth it. (At least in most languages I’m aware of: the most notable exception is J, which encourages the style for reasons I still don’t understand.)

So would:

>> apspread: pointfree [append _ spread]

>> apspread [a b c] [d e]
== [a b c d e d e]

...be pointfree because it has more functions? It was my intention that would work, it just doesn't yet.


Says ChatGPT:

The feedback you're receiving touches on a nuanced distinction in functional programming terminology. Let's break it down:

Pointfree (Tacit) programming typically refers to a style where functions are defined without explicitly referencing the arguments on which they operate. For example, in Haskell, pointfree style avoids naming arguments:

-- Named argument version
f x = negate (abs x)

-- Pointfree version
f = negate . abs

Here, f is composed of functions (negate and abs) without directly naming the argument x.

In your example, when you define:

emit: pointfree [append block]

It feels like you're defining a function without explicitly mentioning the argument (value), which aligns conceptually with pointfree style. The argument is implied by the structure, not named. However, some might argue that this is closer to partial application, where a function is created by pre-applying some arguments but not all, leaving placeholders for the remaining ones. In your case, block is pre-applied to append, but value is left to be filled in later.

The distinction between pointfree and partial application is subtle. Partial application generally refers to a function where some arguments are pre-bound, leaving others to be supplied. Pointfree refers to not mentioning the arguments at all, focusing on function composition.

I think your usage isn't necessarily wrong. While partial application is occurring in the background, your abstraction hides the argument-naming part, which matches the spirit of pointfree/tacit programming.

Your "placeholder" example (append _ ) feels closer to partial application with anonymous arguments than pointfree programming in the strictest sense. But, in practice, people often blend these concepts.

Ultimately, I’d say you’re capturing the idea of reducing argument verbosity effectively, and whether it's strictly pointfree or not might depend on how much weight you're placing on technicalities.

One thing I should probably clarify here: the ‘points’ in question are function arguments. (The terminology comes from category theory, I believe.) So ‘pointfree’ or ‘pointless’ style involves getting rid of explicit function arguments.

Thus:

This would be pointfree, yes, because it’s getting rid of the function arguments which would otherwise be specified (func [xs ys] [return append xs spread ys]).

If you really do feel the need to go down the rabbit-hole of pointless style, keep in mind that knowing where the arguments go is the important thing. Even in Haskell, with its well-developed type system, pointfree code is often near-impossible to read because of the difficulty in keeping track of all the implicit arguments. In Ren-C, it would become a nightmare. To make it at all manageable, you would need to impose a lot of careful restrictions on what pointfree can do. Partial application is fine, because it’s trivial to convert back into pointful form, but more general pointfreeness could get very difficult to handle. Which is why I advise against it.

(BTW, it’s extremely late here and I need to go now. Will respond to any further replies tomorrow morning.)

Reading another post made me remember that Ren-C already has a form of partial application: namely, adapt! As I understand it, the purpose of adapt is to create a copy of a function where some arguments are ‘pre-set’ — which is exactly what partial application does. A partial function would still be very useful, but if I’m understanding this right, it could be just a small dialected wrapper around adapt.

I'm just trying to make sure that things that feel like rather novel and fun mechanics are within reach of a typical user, and to accomplish them in a readable style. These are good tests and demos.

As for prescribing how people program, that is the opposite of what I aim to do. It's freedom... and freedom to build abstraction with as much or as little syntax at your callsites as you wish.

ADAPT doesn't remove the parameters from the interface. It just gives you a chance to tweak them to your liking before going on to run the function:

>> unless: adapt :if [condition: not condition]
== ~#[frame! {unless} [condition :branch]]~  ; anti

>> unless 1 = 2 [print "opposite of if"]
opposite of if

>> unless 1 = 1 [print "opposite of if"]
== ~void~  ; anti

It is able to be particularly lightweight, though it surrenders control to the implementation after its adjustment. It has no say in what happens after that.

SPECIALIZE is the parameter-remover:

>> aptwo10: specialize :append [value: 10, dup: 2]

>> ap10 [a b c]
== [a b c 10 10]

FYI, ENCLOSE is heavier-weight than ADAPT, giving you the full ability to manipulate the frame which was constructed for the function... decide when to call it (or if to call it at all, or to call it multiple times)... and be in charge of the result.

alternative-append: enclose :append func [f [frame!]] [
    let v: f.value
    if any-list? v [f.value: spread v]  ; spread-by-default semantics
    eval f  ; if no copy made, F will be destructively utilized by the EVAL
    return v  ; ordinary append returns the series, let's twist that up
]

>> data: [a b c]
== [a b c]

>> alternative-append data [d e]   
== [d e]

>> alternative-append data 5    
== 5

>> data
== [a b c d e 5]

The design aims to make all of these compose (you can ADAPT an AUGMENT of an ENCLOSE'd SPECIALIZE, etc.)

So if you SPECIALIZE an ADAPT--for instance--you can remove parameters from the interface -and- do some calculation of what that parameter is on each call vs. fixing it to one value.

Fair enough. I just don’t think it will be very useful in typical code.

Ah, OK. So I got the name wrong, but it does exist as I thought.