Tamgu, a functional, imperative, logical programming language
github.com
github.com
in python this is an error:
>>> i = 10
>>> s = "20"
>>> s+=i;
TypeError: cannot concatenate 'str' and 'int' objects
>>> i+=s;
TypeError: unsupported operand type(s) for +=: 'int' and 'str'It should never happen in production - there should be a test that proves this.
In Common Lisp, the SBCL compiler would be such an example:
* (defun foo () (let ((i 10) (s "20")) (concatenate 'string s i)))
; in: DEFUN FOO
; (CONCATENATE 'STRING S I)
;
; caught WARNING:
; Derived type of I is
; (VALUES (INTEGER 10 10) &OPTIONAL),
; conflicting with its asserted type
; SEQUENCE.
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition
FOO
*my preferred language is pike, which is a strong and declarative typed language (declarative typing is the term i prefer since not everyone defines static typing the same way, and some statically typed languages are weak, like this example here)
pike does catch type errors at compile time, but behaves like a strong dynamic typed language at runtime.
i understand that python and rubys optional types now allow for something similar.
I'd say that while there's certainly a ton of purely dynamically-typed code out there, there's also a current reversal of the trend, with static and gradual typing coming back in vogue. Python has fully-supported type annotations, and Typescript is the trendy new way of writing JS. Facebook has ReasonML and Hack/PHP 7. Go and Rust, the current "cool kid" languages, are fully statically typed.
I think that the bad taste left in people's mouths by "enterprise" Java and its 30-character type names is fading, and that we'll see a few years of static types. Then of course the cycle will repeat itself, as is tradition.
I'm saying this as an experienced dev, please consider from this and others that you may be wrong, and avoid the trouble we've had.
Or not. Everyone has to find their own way.
I continue to use python without type-hints because of know of no other similar languages with type-hints that are as popular.
string s = 10; s += 20;
What I define is an operation within a string. The context is defined by the recipient variable. In the same way:
int i = 20; i += "30";
The variable "i" defines an integer context and therefore string 30 is considered in this case as a number.
As a source code reader I would much prefer to see at least a keyword or function call to make the casting explicit.
string s = cast 10; s += cast 20;
(I had the same reaction as the parent and think the language looks really cool otherwise)
i = int(s)+10;
Each type can be used as a "function" to create the object you like, which is basically what a cast is...
I don't deal with a lot of ad-hoc data, but even when I do, that data usually needs strict validation, as it tends to not be very clean. Having things silently convert hides this and while it may make the code simpler, that code should have validation anyway, so it really won't be any more work in the long run (for me). Slight errors in data cause billion dollar bugs, after all. Maybe not in NLP though, so, again, we have different use cases.
But that's just me and obviously my priorities differ from yours. In any case, congratulations on building this, that's quite an achievement.
Nice!
Perl does this and it's a lot cleaner IMO. Whenever you see a "+" you can consider it an explicit numeric conversion of both arguments: https://codespeaks.blogspot.com/2007/09/ruby-and-python-over...
In fact, there is no dedicated numeric conversion operator in perl. The canonical "operator" for this is called the "0+" operator.
Python-style dynamic-typing and strictness is sort of the worst-of-both-worlds approach. Many times I've lost rarely-seen log messages to "cannot concatenate 'str' and 'int' objects" errors.
I'm building one too in Rust and there exist a very nice concept:
https://doc.rust-lang.org/std/convert/trait.From.html
Is the first time I see a blessed way into a lang to define conversions.
For example, to parse strings into int you need to say (in pseudo-code):
convert::From(String) -> Int //Or better convert::TryFrom
and suddenly you get the way to convert to strings into ints EVERYWHERE.Is amazing.
The key here is that is for much more than just basic stuff. You can do a lot of very convoluted things here, like Auto-Convert a vector of ints into a Zip compressed stream in one go.
----
The second things required is how avoid the boilerplate when dealing with data. If I read a file and need a lot of parsing and validation and need to be relaxed about this... how about the same thing between eager and lazy?
- Have "strict" by default and need to manually do
1->Str + "hello" + today->Str + ...
calls- Have a "auto-convert" when need to chain a lot of steps:
auto {
1 + "hello" + today + ...
}
this is nice because you can see where the " big dirty vicious beasts full of mistakes and traps" are in your code!In this example, I implement a join that takes a vector and transforms it into a string.
//type declaration
<joining:: vector -> string>
//The function itself
<joining(v) = x | x <- v>
println(joining(['a'..'z']));
10 + "20" = 30
I guess I should have been more explicit. I'm sorry for the confusion.
First, Tamgu is a language in which the recipient variable defines the context in which the instruction is evaluated.
If your recipient variable is an integer then everything on the right side of your assignment will be treated as an integer. If it is a string, well everything will be treated as a string.
We use this approach in many other instances:
int pos;
vector v;
string s="This is a testing case";
pos = "i" in s; //we are looking for "i" in the string
In this case, pos==2.
v = "i" in s; //we are looking for all positions of "i" in the string
v == [2,5,14]
string s="This is a testing case";
println("i" in s);
But separately your example now looks even worse. It's quite reasonable to expect 'in' to behave the same way for both cases; that they both return [2,5,14] - now you've added another complication.No offence but without some overriding principle, and a justification for that principle (that "things are demonstrably easier if you do it this way"), you've just dumped and extra load on the programmer which I do not need!.
Well, I did my fair share of programming over the years in so many different languages (Pascal, Cobol, C, C++, Java, Python, APL, Lisp, Prolog, Basic, Small Talk, various assemblers) that I honestly cannot remember all of them.
Tamgu is the result of this experience and my choice was always towards compactness and readability, with Perl being my personal nemesis.
The advantage of this approach is that you don't need to remember a long list of operators, they are simply re-interpreted in context and the re-interpretation is pretty consistent over most of the code.
But, well as the adage goes: Of tastes and colors...
println(vector("i" in s));
Having said that, if it works for you, awesome. Don't let me tell you otherwise, not everything has to be to my taste, after all.
"20" + 10 = "2010"
10 + "20" = 30
This was confusing, given that they are sequential statements listed without any clear separation.Prolog is neat, but I wouldn't use it for text processing and database access, so the logic programming piece to me makes better sense as embedded functionality in a more general purpose language, which is what I'm guessing you did here?
We can therefore apply operations like: [?X|?Z].
Finally, Tamgu considers that the reception variables before the "=" sign define the execution context. Therefore, if we take the following program:
parent ("John", "Peter").
parent ("John", "Mary").
parent ("Peter", "Roland").
parent ("Roland", "Pierre").
bool b = parent("John",?X);
vector v = parent(?X,?Y);
The first case will return "true" if we find a matching occurrence. Here "b==true".
The second case will return all the possibilities that match:
[parent("John", "Peter"),parent("John", "Mary"),parent("Peter", "Roland"),parent("Roland", "Pierre")
Access to each parameter is possible:
v[0] = parent ("John", "Peter")
v[0][1] == "Peter"
v[0].name() == "parent"
So "?X" is not true logical variable, but more of a part of a pattern expression? -- in other words, a logical var is a first-class object that can be stored in a data-structure (possibly in an unbound state) and be bound at a later time, and perhaps unbound and re-bound again if there is backtracking. So in Tamgu, "parent(?X,?Y)" acts as a one-shot "find" if the LHS is atomic, and acts as a generator (similar to a possibly nested list comprehension) if the LHS is a collection (but no inter-statement backtracking points are established), correct? So is the generation of the sequence eager/realized or lazy/by-need?
?X is actually a true Prolog variable that can be unified over and over again. Furtheremore, you can call Tamgu functions from your predicate description, in which these variables can be used if they are unified.
grandparent(?X,?Y) :- parent(?X,?Z), println(?Z), parent(?Z,?Y).
In the case above, the "println", which is not a predicate, will try to display the content of ?Z, if ?Z has been unified.
println could be replaced with a call to an actual function, as long as this function returns true. If the function returns false, then it will be considered as a fail.
Can you show some realistic usage or application and how do all these feature hold together? An mvp todo list could make it, for web or for the command line.
I provide binaries for Windows, Mac OS, Fedora, Centos and Ubuntu: https://github.com/naver/tamgu/releases
You also have some examples in: https://github.com/naver/tamgu/tree/master/examples
I provide makefiles for all platforms:
Windows: Visual 2013
Mac OS: both command line Makefile and Xcode Makefiles (including the native Mac OS GUI)
Linux: (also for Mac OS), I provide a python script: install.py that determines what is available on your machine and create a Makefile.in.