This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
ElvishJerricco's profile picture

What Do You Look For in a Programming Language?

Started by ElvishJerricco, 04 February 2015 - 11:58 AM
ElvishJerricco #1
Posted 04 February 2015 - 12:58 PM
Over time, I've made myself involved in many programming language oriented projects on these forums, such as JVML-JIT, LuaLua, ClamShell, and LASM. I feel sufficiently prepared with a knowledge of grammar and compiler design that I'm taking the plunge and writing my own language from scratch. I've been slowly collecting ideas for a while but it's all very loose.
  • Focus on modern style.
  • I've recently started to love Haskell. The ideas it has are fantastic. But it can be extremely difficult to use. I am going to make my language a little bit Swift-esque in this regard. There will be good support for the pure style, while also supporting the imperative style in most OOP languages. The main ideas being the distinction between pure and impure functions that Haskell and Swift both lack, and the collaboration between algebraic data types and object oriented data types.
  • VM with language independence in mind.
  • I'm going to be making a VM that's designed to inhabit 3rd party languages. So it will be using a precompiled bytecode format like Java. It will also feature a comprehensive memory model allowing the use of either a built in GC, stack allocated values, and manual heap management with optional custom allocators. The VM will also accept dynamic typing, unlike the language I will be writing for it. A bytecode file is slightly inspired by Lua's bytecode, except flattening nested function declarations instead of nesting them in bytecode. The idea is that a bytecode file represents a list of functions. A function has a prototype name, and expects to be instantiated with its upvalues. So something like this pseudo code:
    
    pure func overlyComplexAdd(a: Int, b: Int) -> Int {
    	return (() => b + a)() // Create a closure and tail call it
    }
    impure func main(args: [String]) -> () {
    	let n = overlyComplexAdd(14, 6)
    }
    
    Would result in bytecode like this:
    
    overlyComplexAdd:
    	nArgs: 2
    	nUpvalues: 0
    	code: [ // asm
    		closure 2 "overlyComplexAdd$0" 0 1 // create a new instance of the closure declared in overlyComplexAdd,
    										   // 0 and 1 representing registers on the vm stack to use as upvalues,
    										   // which in this case are the arguments a and b.
    										   // 2 representing the stack index to store the closure in
    
    		tailcall 2 0					   // special call instruction for tail call optimization
    										   // 2 is the register of the closure, 0 is the number of arguments
    	]
    overlyComplexAdd$0:
    	nArgs: 0
    	nUpvalues: 2
    	code: [
    		add 2 0 1 // add the upvalues in 0 and 1 and store the result in 2
    		return 2
    	]
    main:
    	nArgs: 1
    	nUpvalues: 0
    	code: [
    		loadc 1 14
    		loadc 2 6
    		callstatic 1 "overlyComplexAdd" 2  // special call instruction for calling functions with no upvalues without using a closure
    										   // 1 is the register to store the return in as well as the register
    										   // that arguments start at,
    										   // 2 is the number of arguments
    	]
    
So I'd like to hear people's thoughts on what they think makes a good language. I've got a lot of the VM design down, but only a bit of the language design, so I want to make sure I don't step on any obvious toes.
SquidDev #2
Posted 04 February 2015 - 07:10 PM
Some of these are would be nice but not really needed, most are a requirement.
  • Braces! Why wouldn't I want this?
  • Classes, I think multiple inheritance would be a wonderful thing, such as Mixins but not really needed.
  • Metaprogramming. D has a really nice way of handling compile time code, which I think is a much better method than Lisp's macros.
  • Type system, but also with type inference (such as auto/var from D, C#, etc…) - I guess that is the same as let.
  • Operator overloading. I still don't understand why this doesn't exist in Java.
  • I see you have Lambda/Closures.
  • Dynamic code generation - Both bytecode generation and maybe something like Linq's Expression Trees
From a VM point of view, I wonder if tailcall should be a JIT optimisation rather than a compile time optimisation, it just means mucking around with bytecode at runtime or in external tools would be easier.

GravityScore wrote something for his Hydrogen language on memory management, I thought it was quite cool, but I can't see it working for some elements.

I would be interested in working on this, but if you don't want me to that's fine!

Compulsory xkcd because you mentioned Haskell:
Spoiler
Edited on 04 February 2015 - 06:11 PM
Lignum #3
Posted 04 February 2015 - 07:37 PM
Oh this is quite interesting. Please do open source it, I'd love to help out!
One thing I find important is having the language structure well documented. I think that an important step of mastering a programming language is to understand it fully. Without understanding how it works internally, you won't understand it completely. Many languages have their structure documented poorly or not at all. In addition to that, maybe an assembler? Although you'd probably need one for testing anyway, wouldn't you?
ElvishJerricco #4
Posted 04 February 2015 - 09:13 PM
Some of these are would be nice but not really needed, most are a requirement.
  • Braces! Why wouldn't I want this?
  • Classes, I think multiple inheritance would be a wonderful thing, such as Mixins but not really needed.
  • Metaprogramming. D has a really nice way of handling compile time code, which I think is a much better method than Lisp's macros.
  • Type system, but also with type inference (such as auto/var from D, C#, etc…) - I guess that is the same as let.
  • Operator overloading. I still don't understand why this doesn't exist in Java.
  • I see you have Lambda/Closures.
  • Dynamic code generation - Both bytecode generation and maybe something like Linq's Expression Trees
From a VM point of view, I wonder if tailcall should be a JIT optimisation rather than a compile time optimisation, it just means mucking around with bytecode at runtime or in external tools would be easier.

GravityScore wrote something for his Hydrogen language on memory management, I thought it was quite cool, but I can't see it working for some elements.

I would be interested in working on this, but if you don't want me to that's fine!

Compulsory xkcd because you mentioned Haskell:
Spoiler

GravityScore's just looks like automatic reference counting, am I wrong? For memory management, I was thinking of having an instruction for each of my allocation techniques. The garbage collector will work using ARC primarily, but also with an ordinary generational mark-sweep collector that runs rarely (letting ARC take the bulk of the work, as it should) to make up for retain cycles. Although I guess I should allow the use of pure GC or pure ARC memory management, so I'll have an instruction that takes parameters to enable each of these on a value.

Anyway I agree on all other accounts with you. My OOP will be strictly multiple inheritance, although I hope that most users take advantage of the plans I have for algebraic data types. For example, I'll be treating Int as an ADT where integer literals are treated as constructors for it. This lets users take advantage of an idea I had:

func divide(x: Int, y: Int(!0)): Int {
	return x / y
}
func divideOrNot(x: Int, y: Int): Maybe Int {
	case b {
		0 = {
			return nil
		}
		otherwise = {
			return Just divide(x, y)
		}
	}
}
Basically, the idea is that the compiler tracks pattern matching that you've done, so that it knows which constructors a value can and can't be. This enables us to define integer values that b can't be, and that can be checked at compile time. Another example using something other than integers:

data MyType = Cons1 | Cons2 Int | Cons3 String

func someFunction(a: MyType(Cons2 | Cons3): Either Int String {
	case a {
		Cons2 i = {return Left i}
		Cons3 s = {return Right s}
	}
}

func whatever(a: MyType): () {
	let x: Maybe (Either Int String) = case a {
		Cons2 | Cons3 = {Just someFunction(a)}
		Cons1 = {nil}
	}
}

func orThis(): () {
	let x: MyType = Cons3 "Hey"
	let y = someFunction(x)
}
The syntax here is all completely made up on the spot FYI, I haven't done a great amount of design work there yet. But you can see that there are compile time restrictions on the ability to call certain functions. I haven't decided yet if I should make the divide operator require Int(!0) or if dividing by zero should just return NaN. But anyway, the compiler knows about the constructor restrictions on objects because of their instantiations, so it can infer that x can be passed to someFunction.

I need to decide how far I want this feature to go. For example, should code like this be allowed?

func x(n: Int, i: Int(!n)): Int {
	return 2 / (n - i) // because i is known not to be n, the type of n - i is Int(!0)
}
But again, I'm not sure if this is too for or not. It could be difficult for the caller to prove in a case. And it has to be in a case, since == operators can be overloaded, meaning the compiler doesn't know what that operator does.

func y(n: Int, i:Int): () {
	let z = case i {
		n = {nil}
		otherwise = {Just x(n, i)}
	}
}
So this case sees that i isn't matched to n, so it reasons that they can be passed to x. And of course the same can be extended to other ADTs.
Edited on 04 February 2015 - 08:29 PM
ElvishJerricco #5
Posted 04 February 2015 - 10:32 PM
I guess I also need to name my language…
Bomb Bloke #6
Posted 04 February 2015 - 10:44 PM
Elvish Presley? Seriously, that always comes to mind when I see your username.

One thing to consider is that your language can offer performance or convenience, but not always both.

Take Lua's tables for example. Terribly inefficient. Tons of overhead. But they make it very, very easy to get very complex tasks accomplished in very simple ways. This makes Lua absolutely great for certain jobs (simple ones), but absolutely rubbish for others (lengthy, taxing ones). When I've got my pick of languages for a given problem, my choice generally depends not on my "favourite", but rather on the scale of the project.

Personally, I suggest that your language go for convenience over efficiency. Either way, it's well worth deciding on a "philosophy" before you get started.
Kingdaro #7
Posted 04 February 2015 - 11:02 PM
Personally, I like a language to have enough constructs to allow a variety of things to get done, but not so much so as to overload the user with a bunch of syntaxes they need to learn to do anything with it. Pretty much what Lua does.
Anfred #8
Posted 21 June 2015 - 04:27 AM
Um…why are there not hundreds of replies? I would make a robot lang for microcontrollers and call it "Lubot". I am new to CC. I found out about it, when I was searching for ways to make tetris on an Arduino. I come from a robot building, electronics background. I also have great interest in how people learn to code.
I think lua and CC may be the best possible place to start… Well, unless you create a better way! My dream is to have a language like lua, with the processing lang incorporated. Processing is the lang used in arduino microcontrollers, and is the standard for beginner robot building.
It would mean arduinos could be programmed in lua, if a custom firmware was written.
Currently, there are micro versions of java and python, but those langs are terrible for limited ram – there is only 16kb of ram in some boards!
Lua is only available for the "nodemcu" wifi boards and the esp8266 chip. The new digistump "Oak" ($10) is a great wifi dev board with open firmware that needs lua support. This would finally bridge the gap between CC and Arduino as learning solutions, creating the best possible way to learn computer programming for software, the internet of things, and robots.
It would corner the market for the future of code education!
Edited on 21 June 2015 - 02:28 AM
Alice #9
Posted 23 June 2015 - 09:32 PM
Um…why are there not hundreds of replies? I would make a robot lang for microcontrollers and call it "Lubot". I am new to CC. I found out about it, when I was searching for ways to make tetris on an Arduino. I come from a robot building, electronics background. I also have great interest in how people learn to code.
I think lua and CC may be the best possible place to start… Well, unless you create a better way! My dream is to have a language like lua, with the processing lang incorporated. Processing is the lang used in arduino microcontrollers, and is the standard for beginner robot building.
It would mean arduinos could be programmed in lua, if a custom firmware was written.
Currently, there are micro versions of java and python, but those langs are terrible for limited ram – there is only 16kb of ram in some boards!
Lua is only available for the "nodemcu" wifi boards and the esp8266 chip. The new digistump "Oak" ($10) is a great wifi dev board with open firmware that needs lua support. This would finally bridge the gap between CC and Arduino as learning solutions, creating the best possible way to learn computer programming for software, the internet of things, and robots.
It would corner the market for the future of code education!

You are halfway right. Lua would be great for this. However, to interface to the actual device, wouldn't it be better to use something like C?
Also, I understand that people want ComputerCraft in real life more than anything, but let's try to stick to at least getting it running like this, before we go to microcontrollers.