Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
My thoughts on new programming language

I have been thinking a lot about implementing functional lazy language targeting JVM with syntax like:

class Maybe a = Just a | Nothing
  implements Monad, Eq when a implements Eq, Ord when a implements Ord
  where {
    derive Ord;
    derive Eq;
    override {
      return = Just;

    add :: Maybe a -> Maybe a -> Maybe a when a implements Num;
    add (Just a) (Just b) = Just (a + b);
    add _ _ = Nothing;

    override {
      join (Just (Just a)) = a;
      join (Just Nothing) = Nothing;
      join Nothing = Nothing;

      fmap f (Just a) = Just (f a)
      fmap f Nothing = Nothing

    debug :: Text -> Maybe a -> IO Maybe a
    debug = ...

interface Functor where {
    fmap :: (a -> b) -> (this a -> this b);

interface Monad extends Functor where {
    return :: a -> this a;

    (>>=) :: this a -> (a -> this b) -> this b;
    m >>= f = join (fmap f m);

    join :: this (this a) -> this a;
    join = (>>= id);

    override {
      fmap f m = m >>= (return . f);

So it's basically a Haskell but with different syntax. There are some details that can be investigated feather to be added to language:

  • Export lists. Export lists are clearly better than "public" keyword. But I don't know where to place them syntacticly.
  • Should there be some kind of Java "packages"?

Present above syntax clearly forbids overlapping and orphan type-class instances. Interface (type-class) evolution is allowed from the ground up. Maybe class (type) from the example above implements not only Monad interface, but Functor interface either. "this" type-variable is automatically bound and can't be used anywhere else except interface (type-class) declaration. You can add ApplicativeFunctors to the hierarchy later and you wouldn't need to change any code.

Furthermore every class (type) has associated name space. Records accessors can always be referenced by class (type) name.

Nested classes should be allowed and datatype families:

class Bool implements Trie
  override {
    class TrieClass this value = PairTrie value value;

    lookup :: this -> TrieClass this value -> value
    lookup true (PairTrie a b) = a
    lookup false (PairTrie a b) = b

  class GBool a = GFalse | GTrue

No "newtype" keyword, sctrictness annotation should be enough:

class Left a = Left !a where {...}

No layout rule at first to simplify compiler. But we should always leave possibility to add layout rule later.

Type directed name resolution

When we add name space to each class (type) we can use type information to resolve name space collisions...

Here is some example (dot is NOT composition operator):

(map.lookup 123) >>= (+1)

Should be rewritten to something like

Map.lookup 123 map >>= (+1)

based on the fact that map type is already known to be Map. And "Map" is not only type, but type and corresponding name-space.

I still think how to do it. I still don't know all the details

  • operator precedence
  • what should we use as name-resolution operator and as a composition operator
  • should we declare name resolution explicitly (using "this" keyword for example) or should it be implicit

Type level programming and Meta programming

What I'd like for the language is to leave type system as simple as possible (somewhere close to Haskell 98). I think all feather enhancement should be solved with meta-programming. I think the following use cases cover 90% of all possible programmer needs.

  • Ability to generate instance implementation based on some kind of external config-file. This should be solved using the same technique as Template Haskell: separate compilation. What I want to enhance is type-class instances generation: it should be simple and straight forward. This feature should cover all Yesod uses of quasi-quotation (but external files should be used instead of quasi-quotes)

  • Ability to use dependent types. I think we can solved it using the same technique as as Template Haskell: separate compilation. You can use some function defined on values to operate on types only if this function is from another compilation-unit. Full type-inference will be always available as always, but you would be able to import symbol to another level: from value-level to type-level, or from type-level to value-level.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment