3 ms·
> [tacit programming] makes reasoning about programs much harder With sufficient type information reasoning is easy enough that even the compiler can verify co
by jlg23 10y ago
> [tacit programming] makes reasoning about programs much harder
With sufficient type information reasoning is easy enough that even the compiler can verify correctness.
> The opinion that a point-free style is "beautiful code" seems pretty esotheric to me.
In some way one could say that tacit programming is the interface-oriented programming of the FP-world.
I just had an example where I used tacit programming and had much cleaner code: I had to talk to PostgreSQL through "some" API. For that I need to connect, authenticate and potentially configure the connection. The toplevel function looks like this (in CL):
(defun connect (open authenticate configure)
;;; omitting the (check-type ..)s on the functions here for brevity
(funcall (compose configure authenticate open)))
There is of course a wrapper that creates the 3 functions for you for the regular use cases, but if you want to implement a connection through IP-over-carrier-pigeon or authentication based on DNA samples, you can do so without having to touch the library - just pass your own open/authenticate/configure functions.
And the benefits are just the same you have in OOP when using interfaces properly: Decoupling of logic and implementation.
- davnn 10y ago> With sufficient type information reasoning is easy enough that even the compiler can verify correctness. I think we have a different opinion on "easy to reason about". With easy to reason about I mean the time it takes me to understand a function or part of a program. I can write the most cryptic function with cryptic types that is 100% correct but nobody understands. > And the benefits are just the same you have in OOP when using interfaces properly: Decoupling of logic and implementation. No, there are no real benefits as it's only a syntactic difference. Generally, a point-free style should only be "easier to understand" when the point you decide not to mention is completely irrelevant and non-meaningful to understanding the function, but that's not always the case in my opinion.
- jlg23 10y ago> I think we have a different opinion on "easy to reason about". > No, there are no real benefits as it's only a syntactic difference. Yes, but an implementation in brainfuck can be derived by pure syntax transformation as well and thus too is "only a syntactic difference". I personally find it much easier to reason about 6 consecutive lines of code that enforce 4 interfaces by type annotations (the 2 lines quoted plus the 4 lines of type declarations I omitted) than to read through the equivalent in java, which is at least 12 lines in 4 files and requires an abstraction very far away from underlying mathematical principles (the equivalent of my code in Java would require factories of factories and I'd argue that if one considers that easier to read, one has spent too much time in OOP-land). As others have pointed out, one should definitely not get carried away with it and always evaluate carefully whether it helps clarity. But when the protocol is as simple as "connect, authenticate, configure" then I don't see any reason why I should not write it exactly like this: (compose configure authenticate connect)
- evincarofautumn 10y agoAnd in a concatenative language, that would be quite literally: connect authenticate configure
- kazagistar 10y agoHow is this better then implicit state? I have no idea what data was passed around here.
- evincarofautumn 10y agoIn typical concatenative code, a function usually has only 1–2 inputs and outputs, so it only ever uses a tiny part of the “implicit state” (typically a stack) being passed between functions. It’s not much different than configure(authenticate(connect(open()))); in a C-style language. Those functions might be returning and accepting all kinds of complex objects or tuples, but you can use purely local reasoning and just treat that expression as a pipeline of black boxes, as long as the inputs and outputs match up. Static types help a lot with this (and I’ve actually been working on a statically typed concatenative language for a while) but even basic arity checking (as in Factor) is enough to make it a very minor issue. To me, the point is not that you should avoid local variables entirely. Rather, it’s that most code doesn’t actually need local variables to be perfectly readable, because the most common dataflow patterns in regular business logic can be captured by simple composition of functions.