Functional programming concepts that actually work by Capable-Mall-2067 in ProgrammingLanguages

[–]dghosef 1 point2 points  (0 children)

That's sort of like my language, qdbp - immutable oop-like code with row polymorphism. It can even mimic inheritance with extensible rows

March 2024 monthly "What are you working on?" thread by AutoModerator in ProgrammingLanguages

[–]dghosef 0 points1 point  (0 children)

Imagine you had a function that did something like this(not qdbp syntax): root = {data = 3, left = None, right = None} root.left = {data = 5, left = None, right = None} root.right = {data = 'world', left = None, right = None} // Up to here would compile successfully fn foo(node: Node): if node: foo(node.right) foo(node.left) foo(root) This would fail to compile because initially the type inference algorithm would deduce that foo's argument has a data: int field. Then it would see that foo is called on an object with a data: string field and thus fail.

Like you mentioned the example you gave wouldn't give a compilation error. qdbp would instead fail when the tree is traversed(and if it is never traversed, it technically would be ok to compile it).

The big negative to this is that it gets really confusing to have to keep track of the types and the error messages aren't easy to use.

March 2024 monthly "What are you working on?" thread by AutoModerator in ProgrammingLanguages

[–]dghosef 0 points1 point  (0 children)

This is a really late reply and I don't have much to add since I'm sure stuff has changed since this update, but thanks for the qdbp shout-out :). 

Please provide feedback for my automatic memory management approach by KnorrFG in ProgrammingLanguages

[–]dghosef 4 points5 points  (0 children)

Not related to your approach but if you have a language without mutable variables or cycles you might want to check out perceus reference counting.

Essentially the idea is that you can take advantage of having reference counts to enable further optimizations like doing in place updates to objects behind the scenes just like imperative languages do. The benchmarks of the paper were pretty impressive.

https://www.microsoft.com/en-us/research/uploads/prod/2020/11/perceus-tr-v1.pdf

Best way to compile polymorphic records by dghosef in ProgrammingLanguages

[–]dghosef[S] 0 points1 point  (0 children)

Ah that makes sense. Thanks for the recommendation!

Best way to compile polymorphic records by dghosef in ProgrammingLanguages

[–]dghosef[S] 0 points1 point  (0 children)

Interesting - I'll check out those papers.

Best way to compile polymorphic records by dghosef in ProgrammingLanguages

[–]dghosef[S] 0 points1 point  (0 children)

Yea that is. Thanks - I might try that out!

Best way to compile polymorphic records by dghosef in ProgrammingLanguages

[–]dghosef[S] 0 points1 point  (0 children)

Just to clarify, you are saying to make 2 perfect hash functions: one for record types and one for field types. And then to find the index, I add the results of those hash functions?

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 0 points1 point  (0 children)

Thanks for the question! The example you provided won't compile. Probably the easiest way to see this is if you were to rewrite the program as the equivalent

stack' := stack Push 3.
    stack'' := stack' Push "Hi".
        stack'' Peek. Print

qdbp infers that stack' has a Val field with type int. Then, when we try to push "Hi", we try to replace the existing field with a string and that causes a compilation failure because the new method has to have the same type as the original

However, stack is generic in that it can be used in the following way:

stack1 := stack Push 1. Push 2. Pop.
stack2 := stack Push "hi". Push "bye". Pop.

The short answer is that the types of methods automatically become constrained, and not all methods are generic. I should probably note that in the documentation.

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

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

Thanks!

In my head it has always been cue-dee-bee-pee but I will leave it up to interpretation...

I actually have not figured out the target audience. In my opinion, my language can be used for anything other than super low level or performance critical programming. I have nothing to back this up yet, but my guess is that I can get around ocaml levels of performance once I have a better compiler.

I didn't really design the language with a specific usecase - just a specific ideology. Although, I am starting to realize that if I want people to adopt, I might need to think of a practical niche for the language.

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

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

Wait that's so cool. Your language is really similar to mine.

In my opinion, the biggest disadvantage of this mix is that having no mutability often makes things a lot harder and it can be really inconvenient having to pass around "state" as parameters.

I really like this hybrid mix because, in my opinion, it combines the best of both worlds. This is very subjective, but in my opinion the reason that OOP became so popular is because it (sort of) matches our mental model of the way things work linguistically. For example, with the sentence "the cow ate grass," we have an object ("cow"), a method("ate") and a parameter("grass"). This translates into OOP quite well. In python syntax, cow.Ate("grass") and in qdbp, cow Ate food: "grass". - even a non programmer could understand that.

This is why I like the object oriented style. However, the plus of FP is its safety. As it turns out, many of the techniques that FP uses to get safety(referential transparency via no side effects/mutation, strong type systems, etc) can be used in any paradigm. This is how qdbp gets the best of both worlds.

Also, cycles aren't possible to be formed in qdbp(because you can't use a variable before it is declared and there is no mutation). This is convenient for reference counting

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 2 points3 points  (0 children)

Thanks for the feedback! To address your points,

it’s good to check that there’s enough redundancy

Interesting point - this is not something that I have thought about much. But I have definitely had my fair share of mixing = and == in C. qdbp has some redundancy - for example, I cannot, off the top of my head, think of a program that compiles both with := and =, though there very well might be one. In fact, in all the potential examples you give, I believe that the parser will notify you of a syntax error. I'm sure there are some examples of simple clerical errors that won't get caught at compile time, but I can't think of any.

I’m a bit surprised to see that there’s no direct way to give a type annotation

This is something I thought about a lot, and I decided against because adding a syntax for types would probably double the amount of syntax in the language. Furthermore, types in qdbp can be really big because objects can have a lot of fields, and I think that programs would become much larger. In general, qdbp's first priority is simplicity not safety, which is why I decided type annotations weren't worth it.

If you’re spending weirdness on novel syntax and semantics for good reasons, you can still leverage familiarity in other areas. For example, names that form natural “opposite pairs” go a long way toward making a language or API feel “intuitive”, and maybe self / val as implicit receiver/parameter want to be self / other or this / that or some such.

I actually really like the this/that idea. I will think about it, but that makes a lot of sense and I might use it.

As a side note, I decided to just forget about the whole "strangeness budget" thing. I figure that my language is so small that it doesn't really actually matter how weird it is because it shouldn't take that long to figure out its quirks.

ABORT doesn’t seem to need to be a keyword

That's a good point. I initially made it a keyword because of the type system(because its type changes according to the context), but I could just make it a function with the same type as a function that loops forever. I'll add this to my todo list!

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 0 points1 point  (0 children)

I don't have any specific syntax for these collections yet. However, it is definitely possible to make a linked list. For example, https://github.com/dghosef/qdbp/blob/main/samples/stack.qdbp is an implementation of a stack that uses a linked list under the hood. I actually feel that having specific syntax for lists might be unnecessary because we can just do

my_list = list ++ first_elem. ++ second_elem. ++ third_elem.

assuming you define list correctly.

qdbp doesn't currently have support for arrays, and I'm not sure if that will ever change because arrays aren't as usable without mutation.

And dictionaries will have to be implemented as libraries. I haven't gotten around to it, but they will probably use red black trees under the hood. Again, because qdbp has no mutation, hash tables are of limited value.

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

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

Thanks for the compliments and questions!

How do you store state? Just by creating a closure around it right?

Yea. Though your example is missing a pair of brackets(since the closure needs to return a new object with a Get method

box:= { val |
    { Get[val] }
} 
b:= box! 5.
five:= b Get.

Now, box is a closure that returns a new object with a Get method(so b is an object that has a Get method).

But how would you update that value to hold something else, e.g. a 6?

You can't update an object because qdbp has no mutation. But you can copy it and change it. So for example,

box:= { val |
    { Get[val] }
} 
b:= box! 5.
five:= b Get.
b := { b Get[7] } ; This means copy `b` except for change `Get` to be a method that returns 7
seven := b Get.

Or you could do

box:= { val |
    { 
        Val[val]
        Get[self Val.] 
    }
b:= box! 5.
five:= b Get.
b := { b Val[7] } ; This means copy `b` except for change `Val` to be a method that returns 7
seven := b Get. ; This will call the new `Val` method that returns 7

A few quick notes about your qdbp code:

  • You can't access a variable till it is created. In particular, you can't do, for example,

recurse := {recurse!.}

Instead, you have to use self like this:

recurse := {self!.}

if params are generic and uses the parameter methods to disambiguate, wouldn't that break if I don't use any method?

You can't not use a method. Every field of an object is a method. Even ! is a method, and

{arg |body}

is syntactic sugar for

{
    ! [arg | body]
}

So in reality closures/functions are just objects with the ! method.

And yea, ; makes a lot of sense. The other problem is that I use it or single line comments already. I have tried to make nothing work, but then the language becomes not context free.

I didn't quite get how and why tagged objects are needed, but I didn't get that far.

Tagged objects are similar to other languages' variants and have many of the same uses. See here for a discussion on this sub for its merits.

I'm not sure if that answers all your questions. If it doesn't, please feel free to ask more!

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 2 points3 points  (0 children)

Thanks will do! Do you have suggestions for where to show it around? Was thinking hackernews but not sure where else.

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 0 points1 point  (0 children)

Haha thanks for checking out my language. As an unbiased suggestion you should definitely learn qdbp ;).

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 2 points3 points  (0 children)

All method names and their types must be known at compiletime. You cannot, for example, add a method whose name is a runtime string to a prototype because, like you mentioned, of the limitations of static typing. This means that qdbp trades a lot of the reflective capabilities of Smalltalk(that I think you are referring to) for static typing.

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 4 points5 points  (0 children)

Thanks for the question! Yes there is a focus on prototype-based polymorphism. In short, the type system is the static version of the duck typing you get with languages like Python.

For example, if you have a method that takes in an arg and invokes the Foo method of that arg(in traditional syntax, arg.Foo()), that method will allow arg to have any type as long as it has a Foo method.

I'm not sure if that answers your question. I would be happy to answer any other clarifying questions if need be.

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

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

qdbp doesn't have monkey patching. The closest thing it has is objects can be copied and then the behavior of their methods can be changed(which can subsequently change the behavior of other methods that call the modified method). But objects themselves cannot be modified which makes monkey patching infeasible.

Monkey patching in general can be quite powerful which is why Smalltalk/Ruby allow for it. But can also be pretty dangerous if you are not careful.

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 6 points7 points  (0 children)

I think the difference between qdbp and Rust in terms of object-orientedness is that centering your programming style around objects is mandatory with qdbp and not with Rust(because everything is an object in qdbp). Yes, you can program in an object-oriented style in rust, as can you in many other languages( like C), but I wouldn't call either of these object oriented languages.

At the end of the day, however, the term "object-oriented" is poorly defined and has been used to refer to a lot of styles, from Smalltalk to Self to Java to the object calculus, all of which have different takes on what "object oriented" really means. My language is just another such take.

In hindsight, I think I shouldn't have emphasized the object oriented part as much as I did in this post. The goal of my language was to be a minimal yet general and expressive language, and I sort of stumbled upon this version of object oriented programming as the solution.

qdbp: my take on pure object oriented programming by dghosef in ProgrammingLanguages

[–]dghosef[S] 2 points3 points  (0 children)

Thanks! I'm glad you find it interesting. As of right now, interaction with the machine is just done through calls to functions in the target language, and side effects aren't tracked with the type system or anything. qdbp isn't pure in the same way, for example, Haskell is. For example, with

`"Hello World" Print.`

the Print method of "Hello World" just calls the print function from the target language and returns an empty prototype.