
Lily: An interpreted language with a focus on expressiveness and type safety - MuhammedAbiola
http://lily-lang.org
======
losvedir
One of the bullet points is "Abstract data types (with `Option` and `Result`
predefined)." I wonder if they mean "Algebraic data type", since I have seen
Option/Result before as an example of those, and they're both commonly
abbreviated ADTs. Or maybe they're trying to get at a concept I'm missing
here?

~~~
FascinatedBox
Yeah, that's a mistake on my part. I pushed a fix just now. Thanks for the
tip.

------
koube
Maybe off topic, but I do a lot of ad-hoc python scripts where I drop scripts
everywhere in my filesystem, but I need dependencies installed in a virtualenv
so my ad-hoc scripts can access them. The problem I'm having is that sometimes
things will run for a whole day and then fail at the end because of a typo or
something.

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.

~~~
the_duke
It was weird for me at first, but actually Rust provides a great solution
here.

With [https://github.com/DanielKeep/cargo-
script](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.

~~~
dmux
Likewise with Apache Groovy, which has optional typing:

    
    
        #!/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"

~~~
blattimwind
Kotlin can do it, too:
[https://github.com/holgerbrandl/kscript](https://github.com/holgerbrandl/kscript)

------
derriz
I read the first example of Lily on the site. I found nothing offensive in the
language at all - in fact there's a lot to like particularly the readability.

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.

~~~
enitihas
Well, for good or for bad, python does the same.

~~~
cpeterso
You can use dict's get() method to get back a None for missing keys instead of
throwing a KeyError exception. That avoids the overhead of checking `if key in
dict` or catching KeyError.

~~~
wool_gather
Another option, it's very easy to wrap a dict in a defaultdict:

    
    
        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)

~~~
Quelklef
This is almost always a bad idea, though; it doesn't distinguish between

    
    
        {k: None}[k]
    

and

    
    
        {}[k]

------
staticassertion
> Lily uses reference counting for memory management with garbage collection
> as a fallback.

I'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.

~~~
panic
'reject:' is originally from Smalltalk, along with 'collect:' (map), 'select:'
(complement of reject), 'detect:' (first matching element), and 'inject:into:'
(foldl). I agree that these names are better -- they even rhyme so they're
easier to remember!

------
nickpsecurity
Prior art like Wirth's or Modula-3 language added a minimal amount of features
like exceptions or some OOP on top of simple, efficient, systems languages.
Usually have a GC but many prefer ref counting for efficiency. This seems like
that kind of thinking in an interpreted language.

Just going by what's on the front page: didn't dig deep into it or anything.
Interesting project.

~~~
weberc2
Reference counting isn't more efficient than GC; usually the value of
reference counting is that it's deterministic and you can bind finalizers to
them (with traditional GC, finalizers run when GC runs which may not even
happen). The canonical example is a file object that closes its operating
system file when the object is no longer used; this is nice in theory, but as
the Python folks found out, not a good substitute for properly closing files.

~~~
nickpsecurity
Oh yeah, my mistake. I should've said predictable, not efficient. I remember
people liked it to avoid long pauses coming out of nowhere.

~~~
blattimwind
With (naive) ARC you can still get long pauses "seemingly out of nowhere",
e.g. when a large object tree goes out of scope.

~~~
Narishma
That's not out of nowhere, since you have exact control over when it goes out
of scope.

------
_bxg1
I'm not seeing what sets this language apart. None of its features are very
unique, and I believe all except garbage collection already exist in C(++).
Can anyone explain what makes it interesting?

------
wruza
I wish we had humane keyboards with special symbolic row (or two), since this:

    
    
      ["+" => (|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.

