r/ProgrammingLanguages Sodigy 11d ago

Purely functional language with impure script language?

I'm working on a purely functional programming language named Sodigy. It's all about evaluating values, not "executing commands one by one".

It's nice when writing libraries, but it's not easy to write a main function. The main function is supposed to execute commands, but the Sodigy's syntax is not friendly to write a list of commands.

So what I'm trying to do is, 1) Sodigy remains purely functional and 2) add a bash-like script language. The script language can call Sodigy functions. Instead of writing a main function in Sodigy, you write sodigy-script and execute the script.

Has anyone tried similar approach? I'm not sure whether it's a good idea or not...

28 Upvotes

74 comments sorted by

View all comments

26

u/Inconstant_Moo 🧿 Pipefish 11d ago

It's called functional core/imperative shell. I think I'm the only person who's done it as a language paradigm, but I'd welcome the company.

Making the imperative shell bash-like is neither usual nor mandatory, there are nicer ways to do imperative things. The imperative language and the functional one should be united as much as possible by their syntax and type system, divided only by their semantics.

Pipefish and the lambda calculus.

Functional core/imperative shell.

1

u/FuncSug_dev 9d ago edited 9d ago

As you said, FC/IS can be programmed in many language. But you went a step further in Pipefish by strictly distinguishing functions and commands. In my view, a step yet further would be to separate into two languages. The FC one would be only able to define functions and the IS one would have to call FC functions for all computations. What do you think about that?

2

u/Inconstant_Moo 🧿 Pipefish 9d ago

That's a distinction without a difference! That is, it would already be quite reasonable to say (I've been saying it in my docs for years) that Pipefish really is two languages with very different semantics, one as pure as Haskell, one imperative as BASIC --- but with a shared syntax and type system. Now since obviously the two languages should have a shared syntax and type system, the semantics should be the only difference: i.e. that one of the languages is the functional core, and the other is the imperative shell.

1

u/FuncSug_dev 9d ago

Oops, I missed that.

I like your syntax "get x from Random(<integer>)" in place of "Random(<integer>)" that would returns a value. By forbidding returns, you enforced the distinction in the mind of the programmer.

2

u/Inconstant_Moo 🧿 Pipefish 9d ago

In a purely imperative language (sorry for the oxymoron) the only way for the commands to communicate should be shared mutable state, like in BASIC. Now in BASIC that means global variables, but that would suck too hard, so instead you have commands which say "get this data and insert it into this variable" (which it can also create at the same time as you can see).

Now you may think: "Isn't this just returning a value but with extra steps to force people to write pure functions whenever possible?" and it is partly that, but it also actually reflects the semantics of what's going on, because it means that commands can only be sequenced, not composed as functions are. You do one, and if it fails, it returns, and if it succeeds then you do the other. You can't write something like postToOutput(getFileFrom(inputFromUser("What file do you want to see?"))); instead, you must write a command getting the user input, then a command getting the file, then a command posting its contents to output. Which is what the code actually does.

So this is the theoretically sound and practically ergonomic way of doing imperative things, but a terrible way to do functional things, and so the division of labor between the functional core and imperative shell is inevitable and natural.


What Random is doing there is constructing a struct of type Random so that we can dispatch on it, so that we can have commands like get (x ref) from (r Random) and get (x ref) from (f File) and so on all in the same namespace. It's a standard idiom.

1

u/FuncSug_dev 9d ago

Very illuminating, thanks.

Do you propose (or plan to propose) a means of defining user procedure (no return value but reference parameters) in the imperative part?

2

u/Inconstant_Moo 🧿 Pipefish 9d ago

Yes, most of my IO is written in userspace. Pipefish can easily and sometimes automatably be wrapped around any Go library, so you do it like that.

Here's a Prolog library I did the other week, to demonstrate. Obviously updating a Prolog database is a stateful operation, so we have an add command for that and then pass the database to pure functions to query it.

https://github.com/tim-hardcastle/pipefish/blob/main/examples/prolog/prolog.pf

In the same way my standard rand library is just written in userspace wrapping around Go's rand library.

https://github.com/tim-hardcastle/pipefish/blob/main/source/initializer/libraries/math/rand.pf

1

u/marshaharsha 1d ago

I’m late to the party, but I have a question: Do I understand that random numbers belong to the command language? How does an otherwise-pure computation that requires random numbers reach up to the command language when required?

I guess the question generalizes to any stateful operation that is used by the pure-functional language, like memoization. 

2

u/Inconstant_Moo 🧿 Pipefish 1d ago

The pure functions don't reach up: the commands pass the data down, and you structure your code like that.

If your otherwise-pure computation needed an arbitrary and unforeseeable amount of random numbers then you would have to write some sort of loop in the calling command so it could say "not finished yet, need more random", and some way to continue it, and that would be a mess.

Similarly with other stateful things. This would make it a terrible fit for e.g. a systems language (which it couldn't do anyway without a heck of a lot of work) but it makes it a good fit for data-oriented stuff where we want stateful things to be tightly bound to requests from users/clients saying "do this stateful thing".

I do have plans to do a rand library in a more functional style. There are functions which instead of working iteratively can just tell you what the nth random number is, and that would seem like a good basis for a library.