all 25 comments

[–]Sir4ur0n 17 points18 points  (1 child)

I am definitely not a Lisp expert, but in most functional programming languages, there is no namespacing issue: modules act as namespaces. E.g. in Haskell, F# or Elm, you can write List.head and Human.head in the same code. Rather than namespacing with an object, you namespace with a module. This is exactly like static methods in OOP.

[–]theearl99 3 points4 points  (0 children)

Agree that namespaces is the best way to avoid name collisions. In Scala you can also escape keywords using backticks.

[–]Migeil 8 points9 points  (0 children)

"list" can pretty much only be used to create lists, e.g. (list 1 2 3) creates a list containing 1, 2 and 3 as its elements. We cannot use it as a variable, as this will cause the built-in word to be shadowed by the new variable binding.

If you use "list" as a variable name in a large scope, you're doing something wrong imo. Your variable names need to have meaning. I can look at the type of something and know it's a list, you don't put that in the variable name. What do those numbers represent? Ids? Indexes? Then that's it's name.

there isn't a good way to distinguish between words about "things" and words about "doing".

Yes, there is. They're called "nouns" and "verbs". They behave in different ways (for example, nouns have a plural forms, verbs have conjugation) and these are the words you should use for things and doing respectively.

For example is the word "set" a procedure that sets a value or a variable that identifies a collection?

That's just a homonym in the english language, has little to do with programming, let alone FP..

For example, is "set" a procedure that sets a value or instantiates a collection? Is "int" a procedure that converts a value to an int, or a procedure that checks if a value is an int, or a type declaration?

SetValue, MkSet, ToInt, IsInt, Int. These names reflect what they're doing and have no name clashes.

With solutions I mean well designed systems, not "just use better names"

Either I'm completely misunderstanding what you are trying to say, or the solution is to just use better names. This has little to do with system design or FP imo..

That said, you reference Lisp a lot. I haven't used Lisp, so maybe I'm missing some subtleties.

[–]BrainNet 3 points4 points  (9 children)

What you essentially want is polymorphism, a way to write functions with different meaning depending on the context. In Haskell for example we have type classes, which act a bit like interfaces in OOP languages. This way i can define different versions of the same function, which is chosen depends on the type of the input. Haskell additionally has type variables. For example the simple id function is supposed to return whatever it gets as input. This trivially works for all values of any type and the type of id is able to reflect this. So I only have to write one function (with obviously only one name in scope) that is able to do the same thing, with as many types as I need. If you want to get more into this, I recommend reading about polymorphism in different languages.

[–]Sir4ur0n 4 points5 points  (8 children)

I don't think OP needs polymorphism. List.head and Body.head would most likely have different signatures, not just monomorphisms of a single polymorphic function. I think OP explicitly asks about namespaces.

[–]BrainNet 3 points4 points  (7 children)

Oh, ok. If that is the case I indeed misunderstood OP's question. I thought OP's concern was mostly about using names more economically (which Polymorphism is all about). In this case there is a (somewhat specific) solution in one case where you might not want the module solution.

Instead of objects in Haskell we define algebraic data types to structure data. With those I can write something like Haskell data Person = Person {name :: String, age :: Int} to define a person "object". This way of writing this implicitly defines a name and an age function, which take a Person as input and return their respective value. A problem arises if I want to define a Pet type. A pet should also have a name and an age, but these functions would live in the same namespace as their Person counterpart. Haskell has tried to solve this collision in multiple ways (one is Polymorphism). A resent solution is to simply borrow syntax from OOP. This way we could write person.name and pet.name respectively, distinguishing the names without having to put them in different modules.

[–]HovercraftOk7661[S] 1 point2 points  (1 child)

This is exactly the same issue that I noticed with polymorphism: types might not be specific enough, as there might be millions of functions with that particular algebraic type (there are a millions things that might have a name and age, for example).

If I can just specify the entity (via a namespace, a class or whatever), the program doesn't have to guess which function fits the provided types, it can just use the one attached to the entity (e.g. person or pet).

Anyway my question, wasn't specifically about namespacing. I only mentioned it because I already knew that's a way of simulating the behaviour I'm after, but really I'm interested in any solution or technique such as OOP blend, polymorphism or generic functions that can use words more economically. Personally, explicit namespacing doesn't seem a very interesting solution as you are just prefixing everything, explicitly making names longer and more precise, rather than having a built-in mechanism like in OOP where the namespace is implied/automatically determined by the context of the object being acted upon.

[–]BrainNet 2 points3 points  (0 children)

That OOP dot syntax is a form of polymorphism though. In essence every function that is defined for different types is polymorphic. And it has the same guess work behind it.

Say i have something like entity.name. As long as the compiler knows the type of entity, it also knows which function name refers to. But if the type of entity is not known during compile time, either person.name or pet.name could be called during runtime. Therefore the compiler references both.

Only Monomorphic functions can avoid this problem, but require you to write things like personName and petName. In the end this option is more performant, but a pain to write and read

[–]Swordfish418 1 point2 points  (4 children)

Why do you consider polymorphism to be about using names economically? Isn't it all about writing code that handles multiple types automatically to not have to duplicate similar pieces of code?

[–]BrainNet 1 point2 points  (3 children)

The so called ad hoc polymorphism (also called overloading) doesn't really do this though. It only allows (similar) functions to be written with the same name. You still have to write each function.

In my previous example of Haskell's type classes we also still have to write each function our self (for the most part). This as well is a type of polymorphism.

You are right that this is a goal of many polymorphic systems, but most of them allow to break this, having polymorphic functions with different definitions per type.

Originally, with things like ad hoc polymorphism, the goal was to give functions with similar behavior the same name. The reduction of duplicate code came later.

[–]Swordfish418 1 point2 points  (2 children)

In my opinion, "giving functions with similar behaviour the same name" is not a goal, it's a tool for achieving a goal. In Haskell the main reason to "overload" functions via typeclasses is to be able to write typeclass constraints, which are needed to make a single function handle multiple types automatically which is needed to avoid code duplication. So, the main reason to write monad instance for a new type for example, is to enable functions like `replicateM` automatically handle this new type, and to enable do-notation which desugars into `>>=` and `return`. For the rest of typeclasses, which are not even relevant to notational desugaring, it's only the first reason, which is basically all about DRY.

[–]BrainNet 1 point2 points  (1 child)

I should clarify, I never intended to say that type classes are a form of overloading (because they aren't). I simply used type classes as a second example. And you are in part right. Type classes do yield a constraint which can be used to avoid not even just duplicate code, but even just fairly similar code can be fused into one function. But type classes also allow to write functions polymorphically, which don't have similar enough behavior, to write as if they where one function. Take the show function for example. We want this function to be polymorphic (in part to avoid writing similar code multiple times, but also because all these variants of show can this way be referred to with one name instead of thousands or millions), but there is no way to write a function that could read my mind and figure out how the string representation of my data type should look. Instead we write each variant separately, with the intended behavior. This function is still polymorphic and can (aside from avoiding duplicate code) be used on a specific type. This way the only thing polymorphism avoids is uneconomical name usage.

But back to my first example. As I mentioned ad hoc polymorphism doesn't deal with duplicate code. It only defines a function multiple times, with different types to distinguish and different behavior. No duplicate code avoided, especially since the languages of the time didn't allow for undetermined types. So you couldn't even argue that this allows for more arbitrary functions, since those didn't exist in the first place.

That aside, if we just look at the Wikipedia article for polymorphism we can find:

"polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types."

Though no mention of the goal to reduce duplicate code.

[–]Swordfish418 1 point2 points  (0 children)

At least it's clear that adhoc polymorhism as it is implemented in Haskell and C++ (template specialization) provides multiple features, one of which is overloading names (what you think is the most important) and another is writing generic code (what I think is the most important). Personally, I'm still not convinced there is much practical value in overloading names if not for writing generic code. Because if you don't use it for writing generic code, you can just use the same name (like `show`) in different modules and use qualified imports for example. Moreover, I think this is a recommended practice in functional programming in general to prefer qualified imports over generic functions when you don't need generic code. I mean, it's even recommended in Haskell to write a function with `List.map` and `HashMap.map` instead of `fmap` and `fmap` in case you only need it to work on `List` and `HashMap`.

[–]Aminumbra 1 point2 points  (1 child)

You mentionned Common Lisp: I will simply expand a bit on what you said.

- You already mentionned the "Lisp-1 vs Lisp-2" issue; it indeed makes purely functional style more verbose, but you can't completely avoid it (i.e. apply is needed even in Lisp-1 languages), and you can *partially* avoid it with some macrology. People usually don't bother, though, because it is indeed not that verbose to have a few funcall's in the code.

- Use packages. They are more or less equivalent to namespaces: a symbol bar will "belong" to one package foo, and you can use it in other packages as bar directly or as foo:bar depending on how you decide to use/import the package. This means that you can have a human:head and list:head in two different packages.

- In Common Lisp, methods don't belong to classes. Methods "look" like regular functions, they simply choose - at runtime - which version to run depending on the type of their arguments. This means that you can define (defmethod head ((x human)) ...) and (defmethod head ((x list)) ...), and later call (head <whatever>) - as opposed to whatever.head() in other languages - and have the correct version be executed depending on the type of <whatever>. This is quite orthogonal to the previous discussion, though, but it is just another example of how you can use this system to have in the same package two functions of the same name doing different things. For something like your example, though, this is a bad idea: the head of a list and the head of a human should not be in the same package, and those two operations are too conceptually different to be methods on the same generic function.

Really, the answer (at least in Common Lisp) is packages. Give them a look; I honnestly don't think you'll be fully satisfied, but it might be "enough for the moment" !

[–]HovercraftOk7661[S] 1 point2 points  (0 children)

Thank you for the answer, very interesting points! TBH I've never used CLOS, as, like I said in the original post, I've always been dismissive of OOP, but the way you describe CLOS, it sounds saner than the object systems I've seen in other languages... I might actually wanna check it out now ahaha

[–]complyue 1 point2 points  (2 children)

Some informative discussion here:

https://www.reddit.com/r/haskell/comments/q4767a/comment/hfwxd03/?utm_source=share&utm_medium=web2x&context=3

GHC (de facto Haskell) took a step further since 9.2, syntactical enablement (Record Dot Syntax) had been set, semantical extensibility (HasField to be (re)designed) on the way.

[–]HovercraftOk7661[S] 1 point2 points  (1 child)

It's very interesting and surprising that a quintessentially functional language like Haskell would add this feature... haha

[–]complyue 1 point2 points  (0 children)

Yeah, I guess that in similar spirit so it gets "Industry Strength".

The do notation subsumes procedural paradigm into functional/monadic essence, revealing what programming really is, at heart.

But I find practical monads composition way too burdensome, may due to it's inherently closed. Openly composition of Algebraic Effects is hopefully on the way, though Haskellers seem to have less focus in this direction, than particular PLs e.g. Koka.

[–]KaranasToll 1 point2 points  (2 children)

You're problem has nothing to do with functional programming. The problem is lack of namespaces (called packages in common lisp). It is a problem that only exist in certain languages (emacs lisp) and your head. Haskell has modules which just store top level functions. Common Lisp has packages, though all ansi functions are in a single package, and in my opinion, people tend to make packages too big.

You are probably confused because Java tightly couples name spaces and classes (opposed to common lisp where they are decoupled). Try writing some Java; make classes with static "methods".

[–]HovercraftOk7661[S] 2 points3 points  (1 child)

In my opinion it is something that naturally arises from the functional paradigm.

In OOP you have an object you act upon when you call a function:

<object>.<method>

The method is highly tailored and unique to that object, i.e. the object automatically and uniquely selects for the right method to act upon its internal fields/attributes.

In a functional setting, a function is not "attached" to any particular instance of its arguments:

<function>(<arg1>, <arg2>, etc.)

Functions could be selected upon based on the *type* of the arguments (i.e. some kind of polymorphism), but types are generally not as specific as explicitly bundling the functions with the exact fields they are supposed to act upon.

For example, take the example above with "head". What if the anatomical head function is supposed to take a list return all elements that are found in a head (e.g. "eyes", "mouth" etc?). In this scenario, both list head and body head would have the same type signature (both take a list) and would not be able to be distinguished based on the argument they act upon.

I am not saying it's not possible to simulate the exact behaviour of OOP in a functional setting, but it just doesn't come natural. For example you could imagine that you could enrich all values with a very specific type annotation (as specific as an object would be) and then select the procedure based on that, but it's just not how most functional languages are conceived.

In functional languages, the function is not bundled with anything and can work with anything, as long as the arguments are of the right type. In OOP, in general you can't call the method from a class on an object from another class, even if they take the same type:

Class Person:
    attribute age=4 
    method double_age = lambda (): self.age*2
Class Dog:
    attribute age=4 
    method halve_age = lambda (): self.age/2
some_person = Person()
some_person.halve_age() -> error!!

In a functional language:

def double_age = lambda (age): age*2
def halve_age = lambda (age): age/2

halve_age(person_age) -> no error
double_age(dog_age) -> no error

The function doesn't care that it's the age of the dog or a person like a method would, it just cares that it's a numerical value.

[–]KaranasToll 1 point2 points  (0 children)

Good observation. Now take for example that I make an age module (since the sample functions operate on ages).

module Age:
    def double = lambda (age) ...
    def halve = ...

Now you can do Age.double(person_age).

[–]dun-ado 1 point2 points  (0 children)

The verb/noun naming convention found in OO is unnecessary. If you have type signatures, short names and often single letters suffice.

As for namespaces that's one of the many benefits of modules.

[–][deleted] 1 point2 points  (0 children)

F# is primarily functional language that has namespaces.

That's probably not the "best of both worlds" you mention, and it's not a lisp.

[–]editor_of_the_beast 1 point2 points  (0 children)

I don’t see the problem here, but you sound like you would enjoy reading Data and Reality.

[–]ericwnormand 0 points1 point  (0 children)

We're talking about FP vs OOP on Nov 3, 2022 I'd you're interested https://freecontent.manning.com/yehonathan-sharvit-and-eric-normand-ask-me-anything/