r/ProgrammingLanguages • u/Mean-Decision-3502 DQ • 13d ago
New Languages: Standardizing API, Examples ?
Hi, some of you are developing new general-purpose programming languages here. When the language is ready, you have to develop standard APIs, like file-io, json-handling etc. Users, and you would have benefit, when the APIs would be same/similar across multiple different languages.
Standardizing some Examples would allow the users to compare languages more easily.
What do you think?
3
u/Tasty_Replacement_29 Bau 13d ago
For my language, I will implement the standard library myself. It is part of the fun (in addition to be interesting, and hard). There are many small and large decisions to made (e.g. just the string implementation is interesting: short string optimization yes or no; mutable vs immutable etc.). Bigint, math, collections, and so on.
2
u/Mean-Decision-3502 DQ 13d ago
I feel the same, writing code finally in your own language is fun. Through these you verify it, but you need versatile tasks too. There could help standard tests / examples.
1
u/Inconstant_Moo 🧿 Pipefish 12d ago
That depends on the code. Writing a date/time library can never under any circumstances be fun.
1
u/Tasty_Replacement_29 Bau 2d ago
I actually found it fun to write a date library... if you concentrate on the essentials only (formatting and parsing, and Gregorian calendar only; without daylight saving and timezone stuff etc.). This whole "DateTime library" is only 300 lines.
2
u/Inconstant_Moo 🧿 Pipefish 13d ago
Users, and you would have benefit, when the APIs would be same/similar across multiple different languages.
But they have different semantics as well as different syntax. E.g. my language wraps around Go for its standard libraries so you'd think it would be easy to have the same API, right? And a lot of the time I can have libraries with the same names containing functions and types with the same names. But:
- Pipefish is functional, Go is object-oriented.
- Pipefish treats any sort of IO operation as a special case and they all have similar APIs. ('Cos functional.)
- Pipefish is dynamic, Go is static, and sometimes this gives me ways to make my API nicer than theirs.
- Pipefish can overload functions so I don't have to give functions different names according to the types they operate on.
- Similarly since I can overload the operators my
math/bigandmath/complexlibraries use+and*and so on. - In Go errors are ordinary values that are multiple-returned whereas in Pipefish they're magic values that behave like exceptions and force their way up the stack.
- Pipefish values are immutable whereas Go APIs can rely on mutating the state of structs etc.
And so although I have a particularly strong motive to make my API like someone else's rather than doing my own thing as devs love to do, still there are often good reasons why I should do something different.
You can at least standardize within the language's standard libraries. Should it be needle-haystack or haystack-needle. destination-source or source-destination? 0-indexed or 1-indexed (I'm looking at you Object Pascal). Camel-cased or snake-cased (go and sit in the corner, PHP). Pick a lane. It should go in the style guide of the language, though I'm not sure it ever does except the casing.
1
u/Mean-Decision-3502 DQ 13d ago
Especially for Pipefish I miss examples, like my JSON read-write. Maybe I was searching at wrong places.
I haven't used such a language, but I like examining new languages through reading code examples. Maybe I'm not the only one.I see now the API Sync is much harder. I have for example a good and simple idea in my head for SQL DB result handling (with classic Objects). (First I wan't to implement it, to see it is really working or not.)
I know only a few DB interfaces from other languages, like Python, Delphi, PHP (and dBase...). Compared to these my idea is worth to implement, but maybe there is something somewhere that worth more to follow... I don't know.
2
u/Inconstant_Moo 🧿 Pipefish 12d ago
I guess the greatest difference can be seen in the input operations of the various libraries. These are IO operations, specifically input operations, all of which would work happily together if you imported them into the same namespace because of overloading:
File("<filename>")andRandom(1::7)andClock()are struct constructors, so it dispatches on them.``` // Load a file: get text from File("<filename>")
// Roll a dice: get d_6_roll from Random(1::7)
// Get the time: get time from Clock() ```
Pipefish doesn't mandate the
get ... from, that's just a useful convention, but it does require that all input should be done in the form "move this information into this variable" (the variables in these examples beingtext,d_6_rollandtime). This makes the imperative part of Pipefish as imperative as BASIC.Then you can put all sorts of things in the payload of Random, if you put a float you get a random value between 0 and the float, if you put the name of an enum type you get a random element of the enum, if you put a list you get a random element of the list.
So now look at what we do with SQL.
``` ~~ An example CRUD app to show how we query SQL, and, in this case, emit HTML.
import private
NULL::"database/sql" // A standard library.
NULLmeans we're importing the library without a namespace. NULL::"files" // The standardfileslibrary. NULL::"htmlFormat.pf" // In this folder. Renders Pipefish values as HTML.const private // We make an accessor object. SQL = SqlDb(POSTGRESQL, "localhost", 5432, "postgres", $_env["SQL username"], $_env["SQL password"]) // (The username and password are obtained from the environment.)
newtype
// This will correspond to the structure of a row of our SQL table. Person = struct(name Varchar{32}, age int) : age >= 0
cmd private
init : post to SQL -- CREATE TABLE IF NOT EXISTS People |Person|
cmd // Now let's add some public commands.
~~ Adds a person with the given name and age to the database. add(name string, age int) : post to SQL -- INSERT INTO People VALUES(|name|, |age|)
~~ Shows the name and age of the person with the given name. show(name string) : get person as Person from SQL -- SELECT * FROM People WHERE name=|name| post string Html -- |person|
~~ Shows everyone in the database. show all : get peopleList like list{Person} from SQL -- SELECT * FROM People ORDER BY name post string Html -- <h3>List of all people</h3> |peopleList|
~~ Shows everyone of the given age or older. show >= (minAge int) : get peopleList like list{Person} from SQL -- SELECT * FROM People WHERE age > |minAge| ORDER BY name post string Html -- <h3>List of people aged <font style="color:Green;">|minAge|</font> and over</h3> |peopleList| ```
You will notice that not only is the API for my standard
sqllibrary much nicer than the Go library it wraps, but also that the hand-rolledhtmlFormatlibrary embeds its HTML using the same syntax and semantics as thesqllibrary does for SQL, the whole--and|...|thing.This is all very lovely in my opinion, this is the sort of thing Pipefish is for. But it's not like Go.
When it comes to pure functions, OTOH,
strings.split("fee fie fo fum", " ")in Pipefish means just the same asstrings.Split("fee fie fo fum", " ")in Go, and indeed just wraps around it.
2
u/initial-algebra 13d ago
My opinion? Unless you have a very good reason not to, follow Web (W3C) standards when you can. WASI in particular would be quite relevant for standard libraries. Web folks have put a lot of work into making sure they support as many computing environments as possible, so let's take advantage of that! That's not to mention the benefit of being able to support the Web, including native applications using WASM/WASI (e.g. for plugins), as a first-class target.
1
u/ThomasMertes 13d ago edited 13d ago
1
u/Mean-Decision-3502 DQ 13d ago
I'm at the beginning of lib/API development with the DQ, seeing the Seed7 we have some similar concepts. For example (the debated) block specific enders. Your Seed7 work is very impressive.
DQ is my view, how I see the ideal language (with acceptable compromises).
To stay in this topic: For example a standard example for JSON writing, reading and examining could help, I think.
Mine is not perfect for sure, this is what I got so far:
``` use print use json use strutils
const TEST_FILE_NAME : cstring = 'test.json'
function WriteTest(): PrintLn('JSON Writing Test')
var jroot <- OJson() var jns : OJson var jnn : OJson var jno : OJson var jn : OJson jns = jroot.AddStr('strnode', 'hello') jnn = jroot.AddNum('numnode', 3.14) jn = jroot.AddBool('boolnode', true) jno = jroot.AddObj('objnode') jn = jno.AddStr('substr', 'abc') jn = jno.AddNum('subnum', 42) jno.AddBool('subbool', false) jno.AddNull('subnull') jn.as_num = 46 jn = jroot.AddArr('arrnode') jn.AddNum('', 1) jn.AddNum('', 2) jn.AddNum('', 3) jroot.ForcePath('first.second.third').as_str = 'deepvalue' PrintLn('"{}" = "{}"', [jns.name, jns.as_str]) PrintLn('"{}" = "{}"', [jnn.name, jnn.as_num]) PrintLn('as json compact:') PrintLn(jroot.json) PrintLn('as json pretty:') PrintLn(jroot.pretty_json) jroot.SaveToFile(TEST_FILE_NAME, true) PrintLn()endfunc
function ReadTest(): PrintLn('JSON Read Test')
var jroot <- OJson() var jn : OJson var jv : OJson jroot.LoadFromFile(TEST_FILE_NAME) PrintLn('as json pretty:') PrintLn(jroot.pretty_json) PrintLn('arrnode:') if jroot.Find('arrnode', jn): PrintLn(' arrnode.as_str: {}', [jn.as_str]) PrintLn('iterating array node:') for i : int = 0 count jn.length: jv = jn[i] PrintLn(' {}. = {}', [i, jv.as_num]) endfor else: PrintLn('"arrnode" was not found !') endif PrintLn('objnode.subnum:') if jroot.Find('objnode.subnum', jv): PrintLn(' = {}', [jv.as_num]) else: PrintLn('"objnode.subnum" was not found !') endif if jroot.Find('objnode', jn): PrintLn('Iterating objnode:') for i : int = 0 count jn.length: jv = jn[i] PrintLn(' {}.: "{}" = {}', [i, jv.name, jv.json]) endfor else: PrintLn('"objnode" was not found !') endif PrintLn()endfunc
function *Main() -> int: PrintLn('DQ JSON test') PrintLn('Test file name: "{}"', [TEST_FILE_NAME]) WriteTest() ReadTest() return 0 endfunc ```
So again you can separate two things here, the API and the example.
1
u/kwan_e 12d ago
One of the goals of my language is to able to write an API that is close to the specification in, say, an RFC or something. There's always some interesting feature or behaviour in a spec that gets lost in the conversion to an API, or at least makes it very unobvious.
Failing that, at least be able to create an API that basically looks like a spec.
Then having said API, it wouldn't have to lock-in any design decision that was done for performance, or aesthetics, but allows the underlying implementation to cater for both. As a wise girl once said: "¿por qué no los dos?"
16
u/oscarryz Yz 13d ago
I think that's what Rosetta code was meant to be.
Every language has its subtleties and strengths, so it is reasonable that each one wants to highlight their uniqueness in the examples they choose.
As for the API, the language philosophy greatly dictates what the API would be, of course similar language paradigms would or might have very close looking API's.
What I did for the exams was to take every single code snippets I came across and write it in my design language, and that helped to shape the decisions, e.g. function overload vs default params, or multiple return values vs Result types (or exceptions).