Lily: An interpreted language with a focus on expressiveness and type safety
lily-lang.org
lily-lang.org
I've been looking at other languages to see if there's a language like python but type checked, or some kind of prechecking so I don't lose hours of processing to a print(str+int).
I took a look at Scala but instead of virtualenv style dependency management it's more like maven projects. It has Ammonite where you can declare dependencies at the top of files but that seems kind of hacky.
The other option is Python compile time checks using type hints and mypy, which I think I should eventually learn but haven't gotten around to.
The Lily language looks like the closest thing to "functional type checked python" that I've seen and that's cool.
go run source.go
gives you the compile time checks, while still giving you the source code visibility and simplicity of scripts
I think for your use case though, Go would be perfect. Everything is statically compiled, so you don't need to mess with virtualenvs, and everything is statically typed ahead of time. I'm a professional Python developer, but I often do my prototyping in Go because the type system is more ergonomic than Python/mypy.
With https://github.com/DanielKeep/cargo-script you can have single file Rust "scripts" with ecosystem dependencies.
When executing the file (via the cargo-script wrapper), dependencies are fetched and a binary is compiled on demand (and cached for re-runs).
It's pretty awesome for scripts where type safety is important.
#!/usr/bin/env run-cargo-script
//! This is a regular crate doc comment, but it also contains a partial
//! Cargo manifest. Note the use of a *fenced* code block, and the
//! `cargo` "language".
//!
//! ```cargo
//! [dependencies]
//! time = "0.1.25"
//! ```
extern crate time;
fn main() {
println!("{}", time::now().rfc822z());
}
Then you can just execute it with ./my-script, which will download dependencies, compile and execute. #!/usr/bin/env groovy
// @Grab annotation won't work in scripts
import static groovy.grape.Grape.grab
grab(group: 'org.apache.commons', module: 'commons-csv', version: '1.5')
println "Starting to parse CSV"print(str, int)
Instead of a comma I would actually recommend f-strings, which are python's string interpolation. The only downside is that the code within the f-strings I think have even less prechecking.
https://www.python.org/dev/peps/pep-0498/
Example: print(f"There are {len(lights)} lights!")
print("There are", len(lights), "lights!")
Example:
#!/usr/bin/env kscript
// Declare dependencies
@file:DependsOn(“com.offbytwo:docopt:0.6.0.20150202“)
import org.docopt.Docopt
val usage = “...”
val doArgs = Docopt(usage).parse(args.toList())
println("Hello from Kotlin!")
println("Parsed script arguments are: \n" + doArgs)You could do virtualenv style by setting the CLASSPATH environment variable if you really want. The ecosystem tends to prefer using explicit dependencies though.
> It has Ammonite where you can declare dependencies at the top of files but that seems kind of hacky.
Sort of, but I don't think you'll do better anywhere else. Lots of other replies are suggesting various languages that do essentially the same thing - a magic comment at the top of the file that declares your dependencies.
Gradual typing: https://perl6.party/post/Perl-6-Types--Made-for-Humans Command line arguments: https://perl6advent.wordpress.com/2010/12/02/day-2-interacti...
And at the end of the day it's perl, so it's the king of command line scripts.
One advantage of Lily not mentioned (with regard to typing), is that the type-checking is very fast. One of the reasons I made Lily interpreted and homebrewed all the parts was because as much as I like static typing, it's often slow. Slow static typing, I think, diminishes some of the value of it since you're still waiting but in a different way.
Side rant. Unfortunate I can't get past the decision to use exception handling to deal with non-existing keys in map. I'm of the exception-disliking school which is thankfully growing but I guess not yet universal. For me using an exception to indicate the lack of a value corresponding to a key in a map (is it really exceptional for this to happen?) is a textbook example of exception-abuse where the code is obfuscated by the non-locality of the control flow.
class optionaldict(defaultdict):
"""
A defaultdict that disregards KeyErrors and returns None for missing keys.
"""
def __init__(self, *original, **kwargs):
super().__init__(lambda: None, *original, **kwargs) {k: None}[k]
and {}[k]If you want the Option behavior, there's .get()
Anyways, very cool looking language.
[0] https://doc.rust-lang.org/std/collections/struct.HashMap.htm... [1] https://doc.rust-lang.org/book/second-edition/ch08-03-hash-m...
Sometimes it is, sometimes it isn't. Most languages allow you to choose whether you want a hash to throw a key error on non-existing keys or return a signal value. My favorite is the uber-flexible approach and nice syntax in Crystal:
hash[foo] is sugar for hash.fetch(foo)
hash[foo]? is sugar for hash.fetch(foo, nil)
hash.fetch(foo) will return the default value of the hash table on nonexistent keys if one was assigned, otherwise raise keyerror
hash.fetch(foo, nil) will return nil on nonexistent keysI'd just call this GC where it's rc + (presumably) a tracing GC.
> .reject(|r| r.is_space() )
This is nice. I've always found "filter" to be a confusing name. Filter in? Filter out?
'reject' is a very clear name - if it is a space, reject it.
Code looks really reasonable.
I find `: {` to be a strange idiom. Why not one or the other? Seems to distinguish between single vs multiline? I guess that's reasonable. A bit noisy.
Just going by what's on the front page: didn't dig deep into it or anything. Interesting project.
There is one other aspect of RC systems that I have come to appreciate but never seen anyone else write about: they play well with each other. It's fairly trivial to create foreign object wrappers for python and objective-c in both directions, that each call the appropriate refcount ops of the foreign system. You can then have allocated data structures referring back and forth across the language bridges and when you abandon the root, everything deallocates properly and deterministically in both runtimes. I do not have much experience with GC runtimes and their FFIs but from what I've read I suspect this might be trickier to reason about. This is an obscure point, but one that I did find interesting in terms of language interop.
Isn't it more efficient in terms of memory usage?
["+" => (|a: Integer
split(" ").reject(|r|
stack: List[Integer]
is a shift-hell challenge to type it. Most of my unproductivity goes from being tired of mistyping or too concentrated on precise shifting. Even $150 keyboards go with classic/tkl layout at most; no chance unless going handmade.