[ mona / prog / sol ]

prog


Monads,Async/Await : Algebraic Effects in C99

1 2021-01-12 12:36

https://www.microsoft.com/en-us/research/wp-content/uploads/2017/06/algeff-in-c-tr-v2.pdf
"But you can't do this in plain C" crowd BTFO

2 2021-01-12 23:04

*requires a 30 page research paper to explain how to implement what other languages already do*

k.

3 2021-01-13 02:42

>>1

"But you can't do this in plain C" crowd BTFO

Programming languages are about expressing thought. Few claim C can't be contorted into doing most anything, but it's clear it's a bad idea.

Just imagine, the same idiots who don't know soft real-time from hard real-time, but claim garbage collection means a programming language on a machine which can only do a mere few billion calculations in one second can't work, boast about contorting C to do something worse in every way from a real language.

4 2021-01-13 04:51

>>2,3
It shuts down Lisp/Haskell weenies claiming their language
is special magic that can't be replicated in C.
Of course its a "bad idea"/"ugly hack"/"dangerous stack juggling",
but it proves some fundamental universality that C is equally flexible as those high-level languages with a fraction of code
that these languages are implemented in.

5 2021-01-13 04:56

>>2 Its the other way around:
a thousand lines of C can replicate the functionality
of a 400mb haskell compiler.

6 2021-01-13 05:19

a thousand lines of C is 100 lines of C++ and 25 lines of Rust(which recently got Monads and GATs integrated).
The high-level functional programming revolution will
not come from above, it will become integrated into existing languages and paradigms.

7 2021-01-13 11:07

It shuts down Lisp/Haskell weenies claiming their language is special magic that can't be replicated in C.

Saying Lisp/Haskell have a different style and idioms than C shouldn't be controversial.

8 2021-01-13 15:52

>>7
They are, but their proponents also claim the language has special constructs that cannot be replicated by C.

9 2021-01-13 23:44

>>8
It's true to a certain degree, you can't write macros with loops or compile-time asserts, or stuff like that.
But imo Lisp shouldn't be compared to C, but rather to Unix, as a programming environemnt. C is just one component in this system, which is simpler than whay Lisp provides in it's entierty.

10 2021-01-14 08:54

>>9
Compile-time asserts:
C11: https://en.cppreference.com/w/c/language/_Static_assert
C99: #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

what is "macros with loops"? If you mean recursion,recursive macros
can be used with void.h for up to 1023 iterations per macros.
(gcc supports 65535 macros arguments, but this will be much slower )
https://github.com/FrozenVoid/C-headers

11 2021-01-14 09:05

>>10

Compile-time asserts:

That's not the same, I mean something lick checking if a parameter is a string, or a symbol. That way you can generate meaningful error messages, or even handle them.

what is "macros with loops"? If you mean recursion,recursive macros

No, I mean something like while loops, not just counting down. The fundamental problem is that C != CPP, which is a pity, imo. But it's probably needed to keep the language from becoming too dangerous.

12 2021-01-14 09:40

#define isstring(x) _Generic((x),char*:1,default:0)
>>11
You means something like constexpr functions with loops?

13 2021-01-14 09:46

>>11
There is nothing "Too dangerous" for C, even inline assembler insert in the middle of function.

14 2021-01-14 09:55

>>11
Its entirely possible to do such things as loops, monads,
functional composition. It will be very slow to utilize the preprocessor for it, but its entirely possible.
http://rosettacode.org/wiki/Category:Order

15 2021-01-14 11:23

>>12-14

#define isstring(x) _Generic((x),char*:1,default:0)

OK, but how can you use that *in* a macro. Take something like this in Lisp (where `pick` is already defined somewhere else):

(defmacro pop-random (list)
  "Pick an element from LIST and remove it."
  (assert (symbolp list))
  (let ((sym (gensym)))
    `(let ((,sym (pick ,list)))
       (setf ,list (delete ,sym ,list))
       ,sym)))

The first line makes sure that `list` is a symbol, and will trigger a compile-time error if that isn't the case. No special DSL needed. To my knowledge, this isn't possible with CPP. Or am I wrong?

There is nothing "Too dangerous" for C, even inline assembler insert in the middle of function.

C is not in danger, C is the danger. Imagine "regular", 9to5 programmers using stuff like what you are describing. It would all fall apart. That's the reason why businesses are interested in boring languages like Java.

Its entirely possible to do such things as loops, monads, functional composition.

Sure, the point I think that most people who compare C and Lisp (even though it might not be the right thing to do) shouldn't be that it isn't possible, but that it shouldn't be done, or you are really straining the language. E.g. Order seems cool, but look more like a POC to me. I don't hate C, I just think it shouldn't be used this way. If you want to do Lisp, use Lisp. If you need C, keep it clean, simple and readable.

16 2021-01-14 13:16

>>15
C preprocessor doesn't have random values(COUNTER and LINE are predictable integers)
but to remove an element from arglist
you can use removenth/removernth in https://github.com/FrozenVoid/C-headers/blob/main/argmanip.h

17 2021-01-14 13:45

Its possible to forward a random value from bash into gcc
argument though.
gcc prog.c -D RND_VAL=$RANDOM
or for specific list size
gcc prog.c -D RND_VAL="$(calc $RANDOM%LIST_LEN)"

18 2021-01-14 13:56

Minor correction it will have to be 1-based since i wrote nth as starting with 1st.
gcc prog.c -D RND_VAL="$(calc $RANDOM%LIST_LEN+1)"

19 2021-01-14 14:17

>>11 you can check at compile time with GCC
[code]
Built-in Function: int __builtin_types_compatible_p (type1, type2)

You can use the built-in function __builtin_types_compatible_p to determine whether two types are the same.

This built-in function returns 1 if the unqualified versions of the types type1 and type2 (which are types, not expressions) are compatible, 0 otherwise. The result of this built-in function can be used in integer constant expressions.

This built-in function ignores top level qualifiers (e.g., const, volatile). For example, int is equivalent to const int.

The type int[] and int[5] are compatible. On the other hand, int and char * are not compatible, even if the size of their types, on the particular architecture are the same. Also, the amount of pointer indirection is taken into account when determining similarity. Consequently, short * is not similar to short **. Furthermore, two types that are typedefed are considered compatible if their underlying types are compatible.

An enum type is not considered to be compatible with another enum type even if both are compatible with the same integer type; this is what the C standard specifies. For example, enum {foo, bar} is not similar to enum {hot, dog}.

You typically use this function in code whose execution varies depending on the arguments’ types. For example:

#define foo(x) \
({ \
typeof (x) tmp = (x); \
if (__builtin_types_compatible_p (typeof (x), long double)) \
tmp = foo_long_double (tmp); \
else if (__builtin_types_compatible_p (typeof (x), double)) \
tmp = foo_double (tmp); \
else if (__builtin_types_compatible_p (typeof (x), float)) \
tmp = foo_float (tmp); \
else \
abort (); \
tmp; \
})

Note: This construct is only available for C.
[/code]

20 2021-01-14 14:43

>>16-18
This is not about random, it's just an example. And even then, the -DRND_VAL approach wouldn't work if you want to use the macro more than once. But comparing the macro directly doesn't even make sense to begin with, since C doesn't have list primitives.

The point of the example was to focus on the =(assert (symbolp list))=, a compile-time assertion about the kind of argument that was passed into the macro. >>19 doesn't fit that bill either, because it just generates code that does the checks at run-time.

And since I'm seeing that you're having so much fun with translating Lisp to C, here's one I recently had fun writing:

(defmacro roll (&body events)
  "Chance-based cond."
  (let* ((random (gensym))
	 (vars (loop for (chance . _) in events
		     collect (list (gensym) chance)))
	 (body (loop for (chance . action) in events
		     for (var . _) in vars
		     collect var into sum
		     collect
		     (cons (if (eq chance t) t
			       `(< ,random (+ ,@(copy-list sum))))
			   action))))
    `(let ((,random (random 1d0)) ,@vars)
       (declare (ignorable ,@(mapcar #'car vars)))
       (cond ,@body))))

which lets me write something like

(roll
  (0.3 'a)    ; return symbol a with a 30% chance
  (0.5 'b)    ; return symbol b with a 50% chance
  (t   'c))   ; return symbol c with a 20% (= 100% - 30% - 50%) chance

I'm looking forward to what you'll come up with, void.

21 2021-01-14 15:04

#if RND_VAL<9600
#define RES "a"
#else
#if RND_VAL <16000
#define RES "b"
#else
#define RES "c"
#endif
#endif

#pragma message("ROLL:" RES)
//gcc prog.c -E -D RND_VAL=$RANDOM

22 2021-01-14 15:30

Of course, if you remove "only at compile-time' pre-condition
its more flexible.
#include "Util/void.h"
#define roll(r) condelse("c",(r<0.3,"a"),(r>0.5,"b"))
int main(){
uint64_t q1=__rdtsc();
randomize(q1);
float r=uintdouble01(q1);
char* RES=roll(r);
print(RES);}

23 2021-01-14 15:33

//lispified version
#include "Util/void.h"
#define roll(r) condelse("c",(r<0.3,"a"),(r>0.5,"b"))
int main(){
atype q1=__rdtsc();
print(roll(uintdouble01(randomize(q1))));}

24 2021-01-14 16:00

//a more generic version that accepts any "roll function"
#include "Util/void.h"
#define roll(r) condelse("c",(r<0.3,"a"),(r>0.5,"b"))
#define roll2(r) condelse("c",(r<0.1,"a"),(r>0.3,"b"))
#define rollx(func) ({atype q1=__rdtsc(); func(uintdouble01(randomize(q1))); })
int main(){print(rollx(roll));}

25 2021-01-14 16:33

reference:
conselse roughly expands to recursive ternary expression like (q?a:(q2?b:(...))) via macros overloaded on number of arguments
which select the first tuple(condition, result) that matches, like in lisp.
randomize (randomize a uint variable)
uintdouble01 - convert uint64 to double
print - prints variadic arguments based on their type via _Generic
atype - __auto_type gnu C extension
__rdtsc() - x86_64 timestamp counter intrinsic(assembler)

26 2021-01-14 16:45

i don't see how this replicates roll tbh. you might just as well write out the logic.

27 2021-01-14 17:11

>>26 its possible to write the roll function inline too.
#include "Util/void.h"
#define roll(r) condelse("c",(r<0.3,"a"),(r>0.5,"b"))
#define roll2(r) condelse("c",(r<0.1,"a"),(r>0.3,"b"))
#define rollx(func) ({atype q1=__rdtsc();func(uintdouble01(randomize(q1))); })
#define rolly1(r,args...) ({ condelse(args);})
#define rolly(args...) ({ atype q1=__rdtsc();\
float r=(uintdouble01(randomize(q1)));\
rolly1(r,args) ;})
int main(){print(rolly("c",(r<0.1,"a"),(r>0.3,"b")) );}

28 2021-01-14 17:32

//its also possible to evaluate rnd() for each argument
//this would seem closer to the lisp version?
#include "Util/void.h"
#define roll(r) condelse("c",(r<0.3,"a"),(r>0.5,"b"))
#define roll2(r) condelse("c",(r<0.1,"a"),(r>0.3,"b"))
#define rollx(func) ({atype q1=__rdtsc();func(uintdouble01(randomize(q1))); })
#define rolly1(r,args...) ({ condelse(args);})
#define rolly(args...) ({ atype q1=__rdtsc();\
float r=(uintdouble01(randomize(q1)));\
rolly1(r,args) ;})

#define rndflt1() ({ atype q1=__rdtsc(); double res= uintdouble01(randomize(q1));res;})

#define prefix1(a) (rndflt1() detuple(a))
#define rollz(args...) condelse(first(args),chainapply(prefix1,rest(args)))
int main(){print(rollz("c",(<0.1,"a"),(>0.5,"b"),(<0.4,"e")) );}

29 2021-01-14 18:05

does the lisp version does something like:
macro(args)-> printf("%s",rndflt1()<0.1?"a":(rndflt1()>0.5?"b":(rndflt1()<0.4?"e":"c")))

30 2021-01-14 19:49

//i've updated headers to introduce condifelse(more versatile version of ternary expression nesting) and this is the result:
#include "Util/void.h"
#define rndflt1() ({ atype q1=__rdtsc(); double res= uintdouble01(randomize(q1));res;})

#define prefix1(a) (rndflt1() detuple(a))
#define rollz(args...) condelse(first(args),chainapply(prefix1,rest(args)))

#define prefix2(a) (rndflt1() detuple(a) )
#define rolls(args...) condifelse(fprint(stderr,"Error:Nothing matched"),chainapply(prefix2,args))
int main(){
#define symbol1 "A string"
#define symbol2 200.5
#define symbol3 (char)'e'
#define symbol4 12 //default
//print is variadic, the macro just demonstrate you can include
anything in the rest of the tuple;
rolls(
(<0.1,print("Probability 10%:"),print(symbol1)),
(>0.8,print("Probability 20%:"),print(symbol2)),
(<0.15,print("Probability 15%:"),print(symbol3)),
(<0.5,print("Probability 50%:"),print(symbol4)));
;}

31 2021-01-14 20:06

This is better, but (from skimming the code) you are missing at least one thing: The last argument to roll can be "t", which is replaced by the rest-chance, so in my example 100% - 50% - 30% = 20%.
Also, this isn't just a chance-cond, the sum of all chances may not exceed 1, in your case, printing "symbol4" doesn't have a chance of 0.5, but is dependent on all the previous cases. So in other words, (* 0.9 0.8 0.85 0.5), which /= 0.5.

32 2021-01-14 20:16

>>31 that is trivial
#include "Util/void.h"
#define prefix2(a) (r detuple(a) )
#define rolls2(args...) ({double r=rdouble();\
condif(chainapply(prefix2,args));})
int main(){
rolls2((<0.3,print("a")),(>0.5,print("b")),(>-1.0,print("c")))
;}

33 2021-01-14 20:23

this is what it expands to;
int main(){
({double r=({ uint64_t q1=__rdtsc(); double res= ({ const union { uint64_t i; double d; } u = { .i = 0x3FFUL << 52 | ({uint64_t x=(uint64_t)q1;x+=~x>>11;x-=~x<<9;x+=~x<<7;x+=~x>>10;x-=~x<<8;x+=~x<<6;q1=(typeof (q1))x;}) >> 12 }; u.d-1.0; });res;}); ({if(0){;} else if((r <0.3)){printf(" "),printf(_Generic(("a"), char: "%c", char*: "%s",long long unsigned int: "%llu" ,long long int: "%lli" ,uint64_t: "%"
# 6 "chal.c" 3 4
"l" "u"
# 6 "chal.c"
, int64_t: "%"
# 6 "chal.c" 3 4
"l" "i"
# 6 "chal.c"
,uint32_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int32_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
,uint16_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int16_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
,uint8_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int8_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
, float: "%." "6" "G",double: "%." "15" "G", long double: "%." "18" "LG",default:"%p" ),"a");} else if((r >0.5)){printf(" "),printf(_Generic(("b"), char: "%c", char*: "%s",long long unsigned int: "%llu" ,long long int: "%lli" ,uint64_t: "%"
# 6 "chal.c" 3 4
"l" "u"
# 6 "chal.c"
, int64_t: "%"
# 6 "chal.c" 3 4
"l" "i"
# 6 "chal.c"
,uint32_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int32_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
,uint16_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int16_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
,uint8_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int8_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
, float: "%." "6" "G",double: "%." "15" "G", long double: "%." "18" "LG",default:"%p" ),"b");} else if((r >-1.0)){printf(" "),printf(_Generic(("c"), char: "%c", char*: "%s",long long unsigned int: "%llu" ,long long int: "%lli" ,uint64_t: "%"
# 6 "chal.c" 3 4
"l" "u"
# 6 "chal.c"
, int64_t: "%"
# 6 "chal.c" 3 4
"l" "i"
# 6 "chal.c"
,uint32_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int32_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
,uint16_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int16_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
,uint8_t: "%"
# 6 "chal.c" 3 4
"u"
# 6 "chal.c"
, int8_t: "%"
# 6 "chal.c" 3 4
"i"
# 6 "chal.c"
, float: "%." "6" "G",double: "%." "15" "G", long double: "%." "18" "LG",default:"%p" ),"c");} ;0;});})
;}

34 2021-01-14 20:35

>>32
I guess that works (though you shouldn't litter the front page by pasting macro expansions). The second problem remains though.
Hard mode: get rid of the "<" and ">". I just want to write

puts(roll((0.5, "50% chance"),
          (0.2, "20% chance"),
          (OTHERWISE, "30% chance))).

Can Void-C do that?

35 2021-01-14 21:11

Can someone sell me on using monads?
t. C programmer

36 2021-01-14 21:28

>>35
bloated OOP bullshit.

37 2021-01-15 04:21

>>34
#include "Util/void.h"
#define prefix4(tup) (r-=(first(detuple(tup))),r<0.0)?(second tup):
#define roll(tuples...) ({double r=rdouble();toatom(chainapply(prefix4,tuples))"";})

int main(){
#define OTHERWISE 1.0
puts(roll((0.5, "50% chance"),
(0.2, "20% chance"),
(OTHERWISE, "30% chance")));

;}

38 2021-01-15 04:26

//using the new blankapply (from updated headers)
#include "Util/void.h"
#define prefix4(tup) (r-=first(detuple(tup)),r<0.0)?(second tup):
#define roll(tuples...) ({double r=rdouble();blankapply(prefix4,tuples)"";})

int main(){
#define OTHERWISE 1.0
puts(roll((0.5, "50% chance"),
(0.2, "20% chance"),
(OTHERWISE, "30% chance")));

;}

39 2021-01-15 04:30

//also, since tuple format is known, its possible to shorten the prefix4 macro to this(tup evaluates to last, second arg);
#include "Util/void.h"
#define prefix4(tup) ((r-=(first tup))<0.0)? tup:
#define roll(tuples...) ({double r=rdouble();blankapply(prefix4,tuples)"";})
int main(){
#define OTHERWISE 1.0
puts(roll((0.5, "50% chance"),
(0.2, "20% chance"),
(OTHERWISE, "30% chance")));

;}

40 2021-01-15 04:37

//its also possible to remove the #define OTHERWISE
#include "Util/void.h"
#define prefix4(tup) ((r-=(first tup))<0.0)? tup:
#define roll(tuples...) ({double r=rdouble();blankapply(prefix4,remlast(tuples)) last(tuples);})
int main(){
puts(roll((0.5, "50% chance"),
(0.2, "20% chance"),
(OTHERWISE, "30% chance")));

;}

41 2021-01-15 06:06

//command line version
#include "Util/void.h"
char* rollarg(int argc,char**argv) {double r=rdouble();
char* res="Nothing";
for(size_t i=1; i<argc;i++){r-=atof(argv[i]);
if(r<0.0){res=tmax(strrchr(argv[i],',')+1,argv[i])/*protect from invalid arg format*/;break;}};
return res;}

int main(int argc,char**argv){
puts(rollarg(argc,argv));
// ./roll 0.5,"50% chance" 0.2,"20% chance" 1.0,"30% chance"

;}

42 2021-01-15 06:57

//argv vector version, in macro form
#include "Util/void.h"
#define rollarg(argv) ({double r=rdouble();char* res="";\
for(size_t i=1; argv[i]/*stop on nullarg*/;i++){r-=atof(argv[i]);\
if(r<0.0){res=tmax(strrchr(argv[i],',')+1,argv[i])/*protect from invalid arg format*/;break;}};\
res;})

int main(int argc,char**argv){
puts(rollarg(argv));
// ./chal 0.5,"50% chance" 0.2,"20% chance" 1.0,"30% chance"

;}

43 2021-01-15 07:01

btw it will also work as
./roll 0.5 0.2 0.3
0.5
as it will print the chance argument as is

44 2021-01-15 07:14

//its also possible to use percentages directly
#include "Util/void.h"
#define rollarg(argv) ({double r=rdouble();char* res="";\
for(size_t i=1; argv[i]/*stop on nullarg*/;i++){r-=atof(argv[i])*0.01;\
if(r<0.0){res=tmax(strrchr(argv[i],',')+1,argv[i])/*protect from invalid arg format*/;break;}};\
res;})

int main(int argc,char**argv){
printf("%s%% chance",rollarg(argv) );
// ./roll 50.1 20.0 29.9
// 50.1% chance

;}

45 2021-01-15 07:27

//array version
#include "Util/void.h"
#define rolldarg(arr) ({double r=rdouble();double res=0;\
for(size_t i=0; i<sizeof(arr)/sizeof(arr[0]);i++){r-=arr[i]*0.01;\
if(r<0.0){res=arr[i];break;}};res;})

int main(int argc,char**argv){
double chances[]={50.1,20.0,29.9};
printf("%.2f%% chance",rolldarg(chances) );
// ./roll 50.1 20.0 29.9
// 50.10% chance
;}

46 2021-01-15 11:21

Not, bad, here's the last challenge: Make C support the following:

roll {
case 0.5:
  printf("50%% chance\n");
  break;
case 0.2:
  printf("20%% chance\n");
  break;
default:
  printf("30%% chance\n");
  break;
}

using "case", "break" and "default" specifically is optional, I just want to see how you can extend something to still look like C.

(P.S. you have read the front page, right? You know that code can be inserted using backticks?)

47 2021-01-15 12:05

>>46
i read lots of sites besides this one, i don't memorize
the local syntax/markdown etc.
see post >>32 and replace < with ==

48 2021-01-15 12:15

#include "Util/void.h"
#define prefix2(a) (r detuple(a) )
#define rolls2(v,args...) ({double r=v;\
condif(chainapply(prefix2,args));})
int main(int argc,char**argv){
rolls2(atof(argv[1]),(0.5,print("50% chance\n")),(0.2,print("20% chance\n")),(!=-1.0,print("30% chance\n")));
// ./roll 0.5
// 50% chance

}

49 2021-01-15 12:19
#include "Util/void.h"
#define prefix2(a) (r detuple(a) )
#define rolls2(v,args...) ({double r=v;\
condif(chainapply(prefix2,args));})
int main(int argc,char**argv){
rolls2(atof(argv[1]),(==0.5,print("50% chance\n")),(==0.2,print("20% chance\n")),(!=-1.0,print("30% chance\n")));
50 2021-01-15 12:26

condif is a new addition to header
it converts arglist tuple from ```(cond,res),(cond2,res2)```
to

if(0){;} else if(cond){res} else if(cond2){res2}

without restrictions of ternary expressions(though expression statements remove most of it).

51 2021-01-15 12:36
//with condeach(NEW), its also possible to test for multiple conditions at once
 
#include "Util/void.h"
#define prefix2(a) (r detuple(a) )
#define rolls2(v,args...) ({double r=v;\
condeach(chainapply(prefix2,args));})
int main(int argc,char**argv){
rolls2(atof(argv[1]),(==0.5,print("exactly 0.5 \n")),(>=0.2,print("more or equal to 0.2\n")),(<1.0,print("less than 1.0\n")));
/* ./roll 0.5
 exactly 0.5
 more or equal to 0.2
 less than 1.0
*/

}
52


VIP:

do not edit these