Fork me on GitHub
#off-topic
<
2024-03-19
>
Jim Newton16:03:33

This might be off-topic, and therefore on-topic for the off-topic thread. I noticed years ago (i've programmed in lisp for about 35 years) that lisp programmers put value in making code "express the intent". And I've noticed that many excellent, intelligent, talented programmers particularly in imperative languages do not value that. is expressing the intent only a feature of functional languages?

Sam Ferrell16:03:48

Declarative languages tend to express their intent more clearly than imperative languages... select city from cities where name like 'san %' is going to look a lot different than walking a btree

Amr Ojjeh16:03:44

O, I just joined here but I was actually thinking about the same thing for a while. I was teaching functions to someone in Python when I realized there are two points to making a function: 1. Don't repeat yourself 2. Express intent We were using Reboorg's World (a bit like turtl) when I was explaining this, so this example would help. We were provided with a turn_left function but no turn_right function, so our code for turning right looked like this:

turn_left()
turn_left()
turn_left()
If we cared about only the first point, we would have refactored this as such:
func123124()
And this is of course what transpilors do, because they don't care about point 2. But of course, DRY is only a facet of why we write functions in imperative programming. That fact that we name things means we must want to express intent as well, which we do this instead:
turn_right()
The problem that you're alluding to is that imperative languages necessarily must adhere to the technicality of how something is done rather than what is done, because intent is not expressed through a procedure, but through functions. So I find that FP is a natural successor to imperative programming, and I think this is why Uncle Bob ultimately switched over to FP. Many imperative programmers in fact care about intent, but many of them either don't know of its importance (sometimes making contradictory stances) or they don't know it could be expressed better, or they know but for their task, they need to prioritize optimization and that sort of thing. Hope my answer helped!

p-himik16:03:18

I'm qualified enough only to share my opinion. In other words, not really qualified, apart from my own experience with a few different languages. The more demanding (in the widest sense of the term) a language is when you develop with it, the more the general focus in the community seems to be about those demands and not about the business side of things. Some lang makes memory management hard - well, now you have shelves of books for that lang full of information on how to manage the memory, online classes, many hours at educational institutions devoted to the topic, etc. Some lang has a somewhat neat but very complex and complicated feature - now you have the same, but for that feature. A fancy type system - another bog. A cauldron of inconsistencies - one more. Nontrivial dependency management, compilation, monitoring, etc. etc. The focus ends up being shifted towards all those things. "This is how you write a CRUD web server in C++" - "That's cool and all, but here the example doesn't use autopointers, here's a missing opportunity to use memory transfer, here's the object is inefficiently passed by value, here's the destructor is not declared to be virtual, here's ..." People end up fighting with the language and its ecosystem, instead of fighting alongside them against domain problems.

2
Jim Newton16:03:19

There are of course times when i don't want my code to express intent. Namely when implementing a standard algorithm. I want the code to look like the standard algorithm, not improve upon it. There is a little essay about that here: https://community.cadence.com/cadence_blogs_8/b/cic/posts/skill-for-the-skilled-visiting-all-permutations

Omar16:03:57

This might be on topic for this off topic thread: http://www.defmacro.org/ramblings/lisp.html The article that gave me the motivation to learn a lisp.

Jim Newton16:03:27

tldr; many knuth algorithms are optimized for speed, not elegance. When I'm using one of his algorithms, I want it to look like the book. E.g.,

(defun map_permutations_E (visit data "ul")
  (letseq ((a (listToVector data))
           (n (length data))
           (b (makeVector n))
           (c (makeVector n+1))
           k
           j)
   (prog ()
     E1 ; Initialize
     (for j 0 n-1
       b[j] = j
       c[j+1] = 0)
    
     E2 ; Visit
     (funcall visit (vectorToList a))
    
     E3 ; Find k
     k = 1
     (when (c[k] == k)
       (do_until (c[k] < k)
         c[k] = 0
         k++))
     (if (k == n)
         (return)
         c[k] = c[k] + 1)
    
     E4 ; Swap
     a[0] = (prog1 a[b[k]]
                   a[b[k]] = a[0])
    
     E5 ; Flip
     j = 1
     k--
     (while (j < k)
       ;; swap b[j] and b[k]
       b[j] = (prog1 b[k]
                     b[k] = b[j])
       j++
       k--)
     (go E2))))

Jim Newton16:03:45

That's coding in a particular lisp dialect called SKILL, the algorithm comes from algorithm E (Ehrlich swaps) from Donald E. Knuth, The Art of Computer Programming Volume 4 Fascicle 2, Generating All Tuples and Permutations, page 57.

lilactown16:03:20

I think that people who are particularly clever and/or interested in certain domains are more interested in "expressing intent [of what I want the computer to do]" and less interested in "expressing intent [of whatever high level task I want to accomplish]"

2
lilactown16:03:46

in that way C, assembly, etc. are actually pretty good tools for expressing what you want the computer to do

3
lilactown16:03:29

with Clojure and other "high level" langs you have to jump through a lot of hoops to program the computer to do a specific thing with its memory, registers, etc.

p-himik16:03:54

That's kinda funny to imagine as a tug-of-war with hardware engineers that design CPUs that execute your code in a way that's different from how it was written. :D

lilactown16:03:13

yes, even assembly is sort of a "high level" language now 😛

Amr Ojjeh16:03:16

@U4YGF4NGM I think it's fair to say though that most imperative programmers don't use C or ASM for their work, rather they use more abstract languages like C#, so I don't think many of them care about what the computer does

Amr Ojjeh16:03:13

Modern CPP has also really abstracted away from the CPU and it utilizes a lot of functional concepts in its std

lilactown16:03:17

a lot of C# devs use LINQ and other "fluent" APIs and only bother with low level things when they need more perf, AFAICT

👍 1
lilactown17:03:24

there's a certain amount of expression you get from imperativeness, too. some algorithms are easily expressed as a mutable variable I change over and over and using recursion or other functional patterns doesn't express it as easily

lilactown17:03:25

so I don't see it so clear as "functional -> expressive, imperative -> inexpressive"

2
Amr Ojjeh17:03:49

Well the problem is that you can't really implement algorithms in FP, because an algorithm means a set of procedures, whereas FP operates as a set of functions invoked

Amr Ojjeh17:03:54

For instance, I saw a someone implement QuickSort in Haskell, but I found it hard to take the claim seriously since the whole point of QuickSort is that it does in-place swaps, and that's something that can't easily be expressed in FP. Would you agree?

lilactown17:03:52

yeah I think that's a good example of what I mean- the quicksort algorithm is more easily expressed in an imperative language than a pure functional one

Jim Newton17:03:00

I never knew quicksort was supposed to do inplace swaps until I saw it implemented in C, and thought WOW how bizzare.

2
lilactown17:03:02

(perhaps, I haven't actually tried)

Jim Newton17:03:19

for me quicksort should make a very efficient sorted copy.

lilactown17:03:07

and that's an example of "I want the computer to do this specific thing" and not "I want a sorted list (and I don't care how it's done)"

Jim Newton17:03:09

I had trouble understanding quicksort.c because of the manipulation of array indices which disappear in lisp

Amr Ojjeh17:03:18

@U010VP3UY9X You can do that but it's technically not as efficient of course, which defeats the point of it being quick

Jim Newton17:03:13

No I think if you make quicksort in place, then you end up always sorting a copy anyway, you just copy at the call-site.

Jim Newton17:03:54

well that's my experience. maybe my perspective is contorted for never having used an imperative language in-force.

Amr Ojjeh17:03:00

I'm not sure I understand what you mean. The only copies which would be made are the values in the array, not the array itself, and if they're all primitive, there shouldn't be any new allocations

Jim Newton17:03:06

if you sort an array in place, then all pointers into that array are now invalide. and how do you know how many data structures reference into your array. So, you copy before sorting. it does not change the complexity of the algorithm, it is still n log n

Amr Ojjeh17:03:19

Now if you can create a copy of the array on the stack then that should still nearly be just as efficient, but arrays are rarely created on the stack and for good reason

Amr Ojjeh17:03:23

I mean if your code relies on an item being indexed at a specific place and that array is ought to be sorted at one point, then that's just bad code 😂

Jim Newton17:03:20

it might not be my code. if my sort is in a library called by code beyond my control, then I dont know how many references there are to the object. I don't assume that my customers write good code.

Amr Ojjeh17:03:48

And yes, complexity doesn't change, but complexity doesn't determine speed. Heap allocations heavily impact speed and that's why QuickSort is done in-place, to avoid heap allocations

Jim Newton17:03:04

like I said, I never saw quicksort done in place until a few years ago when I saw it in c-code. and thought, wow that's bizarre. there's a lot of code there, and there's a lot of fiddling with indices which obscures the beauty of the algorithm.

Amr Ojjeh17:03:54

No library can tolerate code that breaks design assumptions. If an array is to be changed and someone decides to fixate on a specific index, then that simply can't be supported. You can make a copy sure, but that's changing the design decision. The point is that sometimes that decision is made for a reason, and you can't blanket fold say that QuickSort should be made less efficient because client code might break a design assumption, and that a copy is not inevitable as you claimed

p-himik17:03:16

Interesting to see how the discussion has become a showcase of what's being asked in the OP, only on a slightly meta level. :)

😂 2
Vincent19:03:18

The first time I saw "really expressive" code was probably when I saw express.js and the language in that node extension (npm early days) to start up a server. res.render, very nice language around what was happening. I'm sure i had encountered it elsewhere before that, but it was such a nice presentation of how "code can mirror natural language" and of course LISP and things like LISP (javascript is kinda close in terms of feature suite but not restrictive in idioms) are good at congruency with natural lang when you want it

Vincent19:03:34

To expand, you could do a functional approach in javascript for everything. Probably ability to make things functional is symmetric with ability to mimic natural lang.

mauricio.szabo02:03:53

I'm also on the team that's unsure "expressing the intent" is an inherent feature of functional languages... or any language, for that matter. Simple SQL queries express the intent quite easily, and complex ones might not; it's also hard for me to agree that two people will see the same code and say "hey, this expresses exactly what I want things to be/to happen"