Programowanie bez pisania typów

4 Lipca 2018

Języki takie jak Java, C#, czy C++ wymagają od użytkownika pisania dużej ilości typów. Adwokaci języków dynamicznych takich jak Python czy Ruby mówią, że mogą szybciej pisać i modyfikować programy nie działając na typach. Ja uważam, że dynamiczne języki pozwalają na masę błędów programisty, pozwalając mu pisać kod, który po uruchomieniu jest skazany na porażkę.

W językach funkcyjnych spotykamy się z inferencją typów, czyli wnioskowaniem poprawności programu patrząc na wartości jakimi on operuje. Jeśli funkcja ma dwa argumenty, a w środku robi na nich dodawanie, to wiemy, że są to liczby. Skoro tak, to jeśli programista spróbuje przekazać do tej funkcji wartość innego typu to powinien od razu dostać informację, że robi coś nie tak.

Inferencja pozwala na dedukowanie typów na podstawie wartości (stałe liczbowe, znakowe, inne zmienne), a tam gdzie wartości nie ma, to na uogólnienie typów. Stąd złożenie funkcji ma typ (a->b) -> (b->c) -> (a->c) dla pewnych typów a,b,c.

W programowaniu obiektowym można pójść krok dalej i zrobić polimorficzne interfejsy. Dla argumentu x zbieramy informacje o wywoływanych na nim metodach i własnościach i konstruujemy interfejs. Wołający funkcję musi dostarczyć obiekt takiego typu, który będzie ten interfejs spełniał. Taki interfejs jest oczywiście anonimowy i moim zdaniem to podejście sprawdza się lepiej niż kiedy musimy określić wszystkie interfejsy implementowane przez naszą klasę w momencie jej pisania. Bo jeśli ktoś stworzy nowy interfejs, który nasza klasa de facto spełnia, ale nie ma wpisane że go implementuje to już jej za niego nie podstawimy.

W związku z przedstawionymi powyżej pomysłami na uproszczenie pracy z typami w językach obiektowych, rozpoczynam przygodę ze stworzeniem nowego języka programowania na platformę .NET o nazwie Great#. Będzie to wzorowany na C# i F# język który ma uprościć pisanie programów.

W pierwszej wersji zajmę się obiektową stroną języka, a w drugiej wersji stroną funkcyjną. Great# będzie kompilowany do C#, a wszelkie paczki będą rozprowadzane jako kod źródłowy żeby zachować rozszerzone informacje typowe, które zostaną utracone podczas kompilacji.

Jestem na bardzo wstępnym etapie ogarniania tego, nie jestem pewny na ile ten projekt wyjdzie, ale mam na niego całe wakacje. Poniżej krótkie porównanie:

C#
using System;
namespace Test
{
    public class Person
    {
        private int id;
        protected int Id => id;
        
        public string Name { get; set; }
        public int Age { get; set; }
        
        public Person(string name, int age)
        {
            Age = age;
            Name = name;
            id = 1;
        }
        
        public override string ToString()
        {
            return $"{Name} ({Age})";
        }
    }
}
Great#
import System
module Test
    class Person
        var id = 0
        protected Id => id
        
        public val Name = default<String>
        public val Age = 0
        
        public new(name, age) =
            Age = age
            Name = name
            id = 1
            
        public override ToString() =
            return $"{this.Name} ({this.Age})"

Pierwszym krokiem jaki zrobię, będzie napisanie generatora parserów dla gramatyk czułych na wcięcia. O idei jego działania, będę pisał za tydzień.