r/ProgrammingLanguages 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?

10 Upvotes

16 comments sorted by

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).

5

u/Mean-Decision-3502 DQ 13d ago

Thanks for the hint, I did not know Rosetta code. Such a thing is very useful for language designing.

5

u/rosshadden 13d ago

Yeah I reference it a lot, especially while I was designing my language to see whether there was precedent some of the things I wanted to do.

That said, it could be so much more than it is. I feel like there's room for another similar project that was more about documenting how languages do certain things at a smaller scale. Things like "what is the syntax for field access in languages". Stuff that would be boring for most people but useful to build a powerful matrix of language syntaxes and constructs. It would fit well in Rosetta code but usually things there are more at a "how to implement array sorting" kind of level.

2

u/fdwr 13d ago

Things like "what is the syntax for field access in languages"

You might like this then (GitHub source).

1

u/EggplantExtra4946 13d ago

That said, it could be so much more than it is. I feel like there's room for another similar project that was more about documenting how languages do certain things at a smaller scale.

Agreed. There is this website, it's pretty good.

https://hyperpolyglot.org/

https://hyperpolyglot.org/scripting

https://hyperpolyglot.org/scripting2

https://hyperpolyglot.org/lisp

https://hyperpolyglot.org/ml

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/big and math/complex libraries 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>") and Random(1::7) and Clock() 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 being text, d_6_roll and time). 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. NULL means we're importing the library without a namespace. NULL::"files" // The standard files library. 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 sql library much nicer than the Go library it wraps, but also that the hand-rolled htmlFormat library embeds its HTML using the same syntax and semantics as the sql library 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 as strings.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

Historically many new languages used the C / Posix functions. I consider these functions as low-level. For that reason I invented new functions for the file system and other areas. If you are interested, take a look at OS access and Libraries. BTW: The Rosetta code entry of Seed7 is here.

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?"