Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduction? #3

Open
JeffreyBenjaminBrown opened this issue Jul 12, 2019 · 29 comments
Open

Introduction? #3

JeffreyBenjaminBrown opened this issue Jul 12, 2019 · 29 comments

Comments

@JeffreyBenjaminBrown
Copy link

Mirth sounds great, but how can I get into it? In order of priority, I think it needs:

  • Code snippets for the README
  • A brief argument -- what's so great about (each feature in) Mirth?
  • Documentation
  • Longer example code
@halis
Copy link

halis commented Jul 12, 2019

Yeah, the first thing I want to know when investigating a new language is, what does the code look like.

@typeswitch-dev
Copy link
Contributor

Hi @JeffreyBenjaminBrown, thanks for the feedback! I will certainly keep it in mind. Mirth is a work in progress and isn't close to being released yet.

@JeffreyBenjaminBrown
Copy link
Author

Ah. Did you know it's on Hacker News?

@typeswitch-dev
Copy link
Contributor

Ah, yeah, I found out via Twitter 😅

@suhr
Copy link

suhr commented Jul 13, 2019

I'll try to guess the semantics from the source example.

  • So you extend a concatenative language with with application (func(arg1, arg2))? Interesting...
  • Comma is not an operation on functions. So comma in applicative syntax is actually ad-hoc
  • I do not understand match and case. They look like functions, but not really? Is a -> b syntax valid anywhere else?
  • There seems to be no way to bind a value, even though it's easy to define.

UPDATE: my wrong, there's lambda. With the mysterious a -> b syntax...

@typeswitch-dev
Copy link
Contributor

Hi @suhr, to answer your questions (this isn't the right place, but...):

  • Yep, to make it easier to work with higher-order words, and especially macros/special forms.
  • Comma isn't an operator, it's part of the syntax for passing arguments to higher-order words.
  • match and cond are special forms, they're defined in the bootstrap interpreter. Also, lambda is a special form. These forms have full access to the abstract syntax tree of their "arguments", so each can be defined in their own way. These all interpret -> as syntax, although in other contexts -> could be interpreted like any other word.

@ahribellah
Copy link

To follow up on this, what state is this in currently? I know that it's WIP, but about how usable is it in general? I'm pretty interested in the general idea behind it, but it's also a bit difficult to tell exactly how developed the language is.

@typeswitch-dev
Copy link
Contributor

Hi @arabelladonna! Thanks for your interest!

Mirth is a WIP, and it's really only available for fun and experimentation at the moment. Please don't rely on it in any serious way, because it is very unstable. I'm working on the first version of the Mirth compiler in Mirth itself. I think that should tell you a little bit about how much of the language there is (there's enough to write a compiler!) but also allude to the instability involved.

There is a bootstrap interpreter for Mirth, implemented in Python (bootstrap/mirth.py). The bootstrap can interpret modules (single Mirth files), packages (a directory tree), and it has a REPL. The bootstrap is ready to use, and it's basically "done", although I think some of the features in the bootstrap version of Mirth are misfires and will have to be reworked in the first version of Mirth. I don't plan on changing the bootstrap very much in the future, except to ditch it as soon as I can in favor of the Mirth compiler that I'm working on.

If you are interested in exploring for yourself (caveat explorator!), you may find the Mirth files bootstrap/prelude.mth and those under src/base to be quite useful and instructive.

@ahribellah
Copy link

ahribellah commented Apr 5, 2020

I just happened to get on GitHub to see that a "self-hosting low-level" version of Mirth just went up. I had a few questions:

  • How different is this from the previous WIP version?
  • Does it work on Windows with MSYS2 or something similar? I see from the commit notes that syscall- functions were renamed to posix- (though I can't seem to find anything about their implementation), which makes me concerned about Windows support. Answered my own question. It builds in the MSYS2 terminal as long as I remove -Weverything from the Makefile. It doesn't build natively in the MinGW64 terminal, though, due to the sys/mman.h dependency.
  • Is there any way to reach out to C libraries to get extra functionality while it's a WIP?

EDIT: I found the post about the new compiler on the Patreon. So this is a more minimal language and the type system and other features aren't in yet? I also noticed that the compiler seems to just recompile itself at the moment (I was having issues finding code that might handle command line arguments), but I suppose you could theoretically replace mirth.mth with other Mirth source code and it would compile it?

@typeswitch-dev
Copy link
Contributor

typeswitch-dev commented Apr 5, 2020

  • How different is this from the previous WIP version?

Quite a bit different. The focus this time was to build a small low-level language to write a self-hosting compiler, while following the basic idea for Mirth. It doesn't have a working type system yet, but on the other hand, it does self-interpret and self-compile. Top-level code is interpreted at compile time, and it can invoke compiler procedures like output-c99 to control the build.

  • Does it work on Windows with MSYS2 or something similar? I see from the commit notes that syscall- functions were renamed to posix- (though I can't seem to find anything about their implementation), which makes me concerned about Windows support. Answered my own question. It builds in the MSYS2 terminal as long as I remove -Weverything from the Makefile. It doesn't build natively in the MinGW64 terminal, though, due to the sys/mman.h dependency.
  • Is there any way to reach out to C libraries to get extra functionality while it's a WIP?

Linux + Windows support is something I'm planning to tackle next. Part of that will involve adding the ability to read header files (to some extent) and interact with C libraries.

Right now this doesn't self-compile on Linux, because it's hard-coding some values for MacOS. That should be easy enough to fix for now. But I would be very surprised if it works against any sort of Windows–POSIX compatibility layer. My plan for Windows is to program against the Win32 API directly, but we'll see (I don't have any experience there, so I'm sure it will be a learning experience).

Thanks for the tip about -Weverything -- I'll be sure to drop that flag when working on MSYS2 compatibility. It's really very handy when it's available, but it's clang-specific.

@typeswitch-dev
Copy link
Contributor

EDIT: I found the post about the new compiler on the Patreon. So this is a more minimal language and the type system and other features aren't in yet? I also noticed that the compiler seems to just recompile itself at the moment (I was having issues finding code that might handle command line arguments), but I suppose you could theoretically replace mirth.mth with other Mirth source code and it would compile it?

Yes. At the moment you could put your own code instead of mirth.mth and invoke the compiler.

Sorry for this terrible UI! It's really not ready for users!

@ahribellah
Copy link

ahribellah commented Apr 6, 2020

Thanks for the response. I will probably keep an eye out for Windows support and wait for that to be in before I start playing with the new implementation, then.

I did notice that there's the +IO type mentioned in mirth.mth, though. Is that implemented on some level and necessary to do IO?

@typeswitch-dev
Copy link
Contributor

I did notice that there's the +IO type mentioned in mirth.mth, though. Is that implemented on some level and necessary to do IO?

It's merely informative for now, no part of the type signatures is currently being used. (That will change soon.)

@ahribellah
Copy link

I see that there's a basic type checker and a basic form of FFI now. I went ahead and tested it in MSYS2 and got the snake example running. It seems like one could now mess with the language a bit, so I just had a few questions:

  1. I see how you define a new C file export, but how does this work, re:outputting snake.c?
snake.c: mirth2 mirth.mth
	./mirth2
	rm -f mirth.c
  1. Why do certain type signatures have a + in front of them while others don't?

  2. I know that there's not a module system yet based on the way it's all in one file, but how much can I safely remove from mirth.mth and still write workable programs?

@typeswitch-dev
Copy link
Contributor

I see that there's a basic type checker and a basic form of FFI now. I went ahead and tested it in MSYS2 and got the snake example running.

Very nice :-)

I wonder if it will work in MSVC. I should try it.

It seems like one could now mess with the language a bit, so I just had a few questions:

  1. I see how you define a new C file export, but how does this work, re:outputting snake.c?
snake.c: mirth2 mirth.mth
	./mirth2
	rm -f mirth.c

There's currently no way to specify what build target you want, so the compiler outputs everything that is specified with an output-c99 command. In this case, mirth.mth outputs both mirth.c and snake.c, so we're just removing mirth.c.

  1. Why do certain type signatures have a + in front of them while others don't?

Great question! The + types are effects. These are used to track mutable state and other effects throughout the program. These are currently ignored by the compiler but eventually they will be used to enforce proper tracking and scoping of mutable state and other effects. They are a very lightweight version of monads from, for example, Haskell.

Examples are +IO, which represents file and console IO, +Lexer which represents "uses or modifies the Lexer state", and +SDL which represents "interacts with the SDL library".

These are being ignored by the compiler right now, so they will likely suffer a bit of an overhaul once they start being enforced.

  1. I know that there's not a module system yet based on the way it's all in one file, but how much can I safely remove from mirth.mth and still write workable programs?

I think you can remove all of the contents from mirth.mth and write your own program in there. You may want to keep the utility functions around (like over, nip, dup2, etc), as well as string/int output functions, to make your life easier. But there should be nothing that is required to keep the language running. Mirth has almost no runtime requirements.

Happy hacking :-)

(But please understand that the language is unstable, I may break your program.)

(Note to self ... work on modules & improving the compiler interface. :-)

@ahribellah
Copy link

ahribellah commented Apr 22, 2020

Okay, I am absolutely sure that I'm just not understanding how something works (I've worked with Forth some, but not a lot), but I did what I usually do and attempted to recreate Project Euler problem 1 in Mirth to get a feel for it. I know that I could definitely do this in a more "idiomatic" Forth manner using only the stack, but I wanted to get a feel for how one would work with variables and whatnot in Mirth, so I did not do it in an "idiomatic" Forth manner.

Here's what I've got:

i32 def-static-buffer(pe1i)
i32 def-static-buffer(pe1sum)

def(pe1-get@, Ptr -- Int, i32@ I32->Int)

def(pe1!, -- +IO,
    pe1i pe1-get@ 1000 < while(
        pe1i pe1-get@ 3 % pe1i pe1-get@ 5 % | if(
            id,
            pe1sum pe1-get@ pe1i pe1-get@ + Int->I32 pe1sum i32!
    ))
    pe1sum pe1-get@ int-print!
    )

"pe1.c" output-c99(pe1!)

It builds and runs, but never outputs anything.

A few things I'm not sure about:

  1. I'm 100% sure I'm overcomplicating the process of getting and setting an I32, which I chose because I didn't see getters/setters for Ints, which I thought would be necessary for using a buffer of that type? I'm almost certain that I over-engineered that, though.
  2. I'm not entirely sure how if statements work. I have the id call there because it wanted two arguments, but I don't know why I needed the call to id or what that first argument represents in general because I assume that the if check is done when I call if, but maybe I'm wrong and the id call is where the check is occurring?
  3. It occurs to me that I'm not entirely certain how buffers are initialized, so they may not even be initializing to 0.
  4. It also occurs to me that I could be using i32! backwards, which could be part of the problem.
  5. I'm not really sure where this is failing in general.

Update: As I was typing this, it hit me that it might be the while loop that is the issue, so I added a drop at the beginning of the while loop and...now it's not compiling.

New code (barely changed):

i32 def-static-buffer(pe1i)
i32 def-static-buffer(pe1sum)

def(pe1-get@, Ptr -- Int, i32@ I32->Int)

def(pe1!, -- +IO,
    pe1i pe1-get@ 1000 < while(
        drop
        pe1i pe1-get@ 3 % pe1i pe1-get@ 5 % | if(
            id,
            pe1sum pe1-get@ pe1i pe1-get@ + Int->I32 pe1sum i32!
    ))
    pe1sum pe1-get@ int-print!
    )

"pe1.c" output-c99(pe1!)

Error:

$ ./mirth2
Reading mirth.mth
Building.
mirth.mth:4124:25: error: Failed to unify branches. First branch has 1 outputs, second branch has 0 outputs.

Line 4124 is the line with the beginning of the while loop on it. Column 25 is the beginning of while.

On another note, I assume that there's no way to work with C structs yet (due to padding)?

Update 2: I got so bogged down in the type conversion that I forgot to increment pe1i, but that isn't solving the issue with unifying branches.

EDIT: I realized I royally messed up the conditional and forgot to test against 0, so that's fixed now.

New code:

i32 def-static-buffer(pe1i)
i32 def-static-buffer(pe1sum)

def(pe1-get@, Ptr -- Int, i32@ I32->Int)

def(pe1!, -- +IO,
    pe1i pe1-get@ 1000 < while(
        drop
        pe1i pe1-get@ 3 % 0 = pe1i pe1-get@ 5 % 0 = | if(
            id,
            pe1sum pe1-get@ pe1i pe1-get@ + Int->I32 pe1sum i32!
        )
    pe1i pe1-get@ 1+ Int->I32 pe1i i32!
    )
    pe1sum pe1-get@ int-print!
    )

"pe1.c" output-c99(pe1!)

Update 3: Ohhhhhhh. I think I get what the error is now. It's if(then, else), isn't it? Not sure how I didn't think of that sooner. At present, this new syntax and set of semantics is really messing with my head, heh.

Update 4: Swapping the branches did not fix my error; it's exactly the same (though I suspect that that may have fixed an error that I would have encountered later?). Hrm. And it is pointing at the while statement, rather than the if statement. I am no longer sure what the exact issue is.

@typeswitch-dev
Copy link
Contributor

typeswitch-dev commented Apr 22, 2020

Hi @arabelladonna, you are very brave. :-)

I'm going to try to help, but it's ok if you have more questions.

1. I'm 100% sure I'm overcomplicating the process of getting and setting an I32, which I chose because I didn't see getters/setters for Ints, which I thought would be necessary for using a buffer of that type? I'm almost certain that I over-engineered that, though.

@ and ! are for Int access. Int and I64 are the same actually, even though the compiler treats them as separate types. (There's some work to be done there.) If you use @ and !, make sure to make the buffers bigger (8 bytes, instead of 4).

Probably Int will go away at some point, leaving only the versions that have an explicit size. I'm not entirely sure. The only reason I still have Int is because I needed to give integer literals a type, but I think I know a better way to do that now.

2. I'm not entirely sure how `if` statements work. I have the `id` call there because it wanted two arguments, but I don't know _why_ I needed the call to `id` or what that first argument represents in general because I assume that the `if` check is done when I call if, but maybe I'm wrong and the `id` call is where the check is occurring?

The if primitive has the signature if(f,g) : *a b -- *c for f : *a -- *c and g : *a -- *c. When if executes, it sees if the top value is zero or non-zero. If it is non-zero, it executes f, and if it's zero it executes g. Thus f is the then branch, and g is the else branch.

You do need to supply both branches. Using id for the second branch (or leaving the second branch empty) is at least telling the compiler that you expect the first branch not to change the stack type, so it's useful to be explicit. (But I can see why you would want a single branch if just to keep things concise. That's probably on the horizon at some point.)

3. It occurs to me that I'm not entirely certain how buffers are initialized, so they may not even be initializing to 0.

They are initialized to 0. :-)

(Actually that's currently ensured by the various operating systems, and Mirth doesn't perturb that property. If at some point I port Mirth to an operating system where that isn't guaranteed, I will initialize the global buffers to zero on that OS.)

4. It also occurs to me that I could be using `i32!` backwards, which could be part of the problem.

It looks like you are using it correctly to me. The convention in Mirth is, for example, ! : Int Ptr --

This is the kind of bug the rudimentary type checker would catch though :-)

[paraphrasing] lots of problems with while loops

Mea culpa, while is really poorly designed. I've been meaning to fix it but I've been putting it off for bigger changes. But I will fix it soon.

Here's the type signature for the while primitive as it currently exists: while(f) : *a b -- *a where f : *a b -- *a b. The way while works is that it checks if the top value on the stack is zero. If it is zero, it drops the value and skips the loop. Otherwise, it runs f once, and does while(f) again. In other words, while(f) is equivalent to dup if(f while(f), drop).

Why is this poorly designed? Well ... usually the condition you want to test will have to be repeated twice -- once before the loop and once after the loop. Further the test value isn't usually something you care about, so it doesn't make sense to have to drop it manually in the loop.

I'm going to replace it with a 2-argument while primitive, while(f,g) : *a -- *a where f : *a -- *a Bool and g : *a -- *a, where f is a test to keep going and g is the body of the loop. Then while(f,g) will be equivalent to f if(g while(f,g), id), and you don't need to repeat the condition twice.

As for the error "failed to unify branches", it's a little confusing but it really is talking about the two branches of while (do nothing vs run f once). It's easier to understand if you think of while(f) as secretly an if statement, dup if(f while(f), drop).

@ahribellah
Copy link

ahribellah commented Apr 22, 2020

Okay, I've updated it and see that I need to use quad for an 8-byte buffer because the compiler is telling me that it doesn't know how to use the Int and I64 primitives yet.

Now I've got the following, which looks a lot cleaner.

quad def-static-buffer(pe1i)
quad def-static-buffer(pe1sum)

def(pe1!, -- +IO,
    pe1i @ 1000 < while(
        drop
        pe1i @ 3 % 0 = pe1i @ 5 % 0 = | if(
            pe1sum @ pe1i @ + pe1sum !,
            id
        )
        pe1i @ 1+ pe1i !
    )
    pe1sum @ int-print!
    )

"pe1.c" output-c99(pe1!)

As for the error "failed to unify branches", it's a little confusing but it really is talking about the two branches of while (do nothing vs run f once). It's easier to understand if you think of while(f) as secretly an if statement, dup if(f while(f), drop).

I understand the concept, but I'm still struggling with why it's telling me what it's telling me. I tried adding a drop to the end of the while loop, but it then told me that I have an underflow error, so there shouldn't be an output?

@typeswitch-dev
Copy link
Contributor

You need to re-compute the condition at the end of the while. :-)

quad def-static-buffer(pe1i)
quad def-static-buffer(pe1sum)

def(pe1!, -- +IO,
    pe1i @ 1000 < while(
        drop
        pe1i @ 3 % 0 = pe1i @ 5 % 0 = | if(
            pe1sum @ pe1i @ + pe1sum !,
            id
        )
        pe1i @ 1+ pe1i !
        pe1i @ 1000 < 
    )
    pe1sum @ int-print!
    )

"pe1.c" output-c99(pe1!)

@ahribellah
Copy link

Ohhhhh. Maybe I didn't understand, heh. Going back and looking at other while loops in mirth.mth, though, I see now that it was right there the whole time.

Thanks for the help. Now it's evident that I've made a critical error somewhere, though, as nothing is being printed, so I'll take some time and figure that one out.

@typeswitch-dev
Copy link
Contributor

Ah, you may need to use int-print-ln! instead of int-print!
Not sure how much of a difference it does, though.

@typeswitch-dev
Copy link
Contributor

@arabelladonna #61 fixes the while loop.

@ahribellah
Copy link

Am I doing anything wrong in trying to use the new module system? I have the following code, which is updated from my previous example, and it looks like it should be fine, but it's not outputting a pe1.c. I tried importing the buffers and names modules, as well, to see if it was just silently failing, but that didn't fix it.

module(pe1)
import(prelude)

quad def-static-buffer(pe1i)
quad def-static-buffer(pe1sum)

def(pe1!, -- +IO,
    while(pe1i @ 1000 <,
        pe1i @ 3 % 0 = pe1i @ 5 % 0 = | if(
            pe1sum @ pe1i @ + pe1sum !,
            id
        )
        pe1i @ 1+ pe1i !
    )
    pe1sum @ int-print-ln!
    )

"pe1.c" output-c99(pe1!)

The command I'm using is bin/mirth2 src/pe1.mth. When I run this, mirth.c and snake.c are being rebuilt, but the file I requested doesn't seem to be being built.

@typeswitch-dev
Copy link
Contributor

Am I doing anything wrong in trying to use the new module system? I have the following code, which is updated from my previous example, and it looks like it should be fine, but it's not outputting a pe1.c. I tried importing the buffers and names modules, as well, to see if it was just silently failing, but that didn't fix it.

module(pe1)
import(prelude)

quad def-static-buffer(pe1i)
quad def-static-buffer(pe1sum)

def(pe1!, -- +IO,
    while(pe1i @ 1000 <,
        pe1i @ 3 % 0 = pe1i @ 5 % 0 = | if(
            pe1sum @ pe1i @ + pe1sum !,
            id
        )
        pe1i @ 1+ pe1i !
    )
    pe1sum @ int-print-ln!
    )

"pe1.c" output-c99(pe1!)

The command I'm using is bin/mirth2 src/pe1.mth. When I run this, mirth.c and snake.c are being rebuilt, but the file I requested doesn't seem to be being built.

Hi Ari! The compiler doesn't take any command line arguments yet. Fortunately the fix is easy in this case: add an import(pe1) in src/mirth.mth

@kephas
Copy link

kephas commented May 7, 2020

I'm surprised, as Mirth is strongly-typed, why not have a Boolean type?

@typeswitch-dev
Copy link
Contributor

typeswitch-dev commented May 7, 2020

I'm surprised, as Mirth is strongly-typed, why not have a Boolean type?

That's a temporary issue. It doesn't have a boolean type yet. :-)

I'm trying to focus on improving the type checker before making too many more changes to the primitive types.

@ahribellah
Copy link

Just saw the commit about the new syntax. How stable is it?

@typeswitch-dev
Copy link
Contributor

I think it would be best to assume that nothing is stable at the moment. That said, the new syntax is actually the same as the old surface syntax from before we switched to a self-hosting compiler. This syntax is what I intend for the future (plus some additional flexibility to support pattern matching).

@booniepepper
Copy link

booniepepper commented Oct 12, 2023

This same thing (unexpectedly getting posted on HN/lobsters) happened to me for one of my languages too. The appetite for concatenative programming languages is really strong right now so I made a concatenative programming discord server. It's linked on the concatenative.org wiki.

(Edit: Do please also support Mirth. The concatenative discord is more general and less exclusive than the Mirth discord that you can access from the project's patreon.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants