Pas un autre langage de programmation. Partie 1: Logique de domaine



Récemment, un grand nombre de nouveaux langages de programmation sont apparus sur le marché: Go, Swift, Rust, Dart, Julia, Kotlin, Hack, Bosque - et ce n'est qu'un de ceux qui sont entendus.
La valeur de ce que ces langages apportent au monde de la programmation est difficile à surestimer, mais comme Y Combinator l'a noté l'année dernière en parlant des outils de développement:
Les cadres s'améliorent, les langues sont un peu plus intelligentes, mais en gros nous faisons de même.
Cet article parlera d'un langage construit sur une approche qui est fondamentalement différente des approches utilisées dans toutes les langues existantes, y compris celles répertoriées ci-dessus. Dans l'ensemble, ce langage peut être considéré comme un langage polyvalent, bien que certaines de ses capacités et la mise en œuvre actuelle de la plate-forme qui en découle, limitent néanmoins probablement son application à un domaine légèrement plus étroit - le développement de systèmes d'information.

Je ferai une réservation tout de suite, il ne s'agit pas d'une idée, d'un prototype ou même d'un MVP, mais d'un langage complet prêt à la production avec tout le langage d'infrastructure nécessaire - de l'environnement de développement (avec un débogueur) à la prise en charge automatique de plusieurs versions du langage (avec des corrections de bugs de fusion automatique entre eux) , release-note, etc.). De plus, en utilisant ce langage, plusieurs dizaines de projets de complexité du niveau ERP ont déjà été mis en œuvre, avec des centaines d'utilisateurs simultanés, des bases de données de téraoctets, des «délais d'hier», des budgets limités et des développeurs sans expérience en informatique. Et tout cela en même temps. Eh bien, bien sûr, il convient de noter que ce n'est pas l'an 2000, et tous ces projets ont été mis en œuvre par-dessus les systèmes existants (qui n'étaient pas là), ce qui signifie qu'au début, il fallait faire «comme ça» progressivement, sans arrêter l'entreprise, puis, aussi progressivement, faites comme il se doit. En général, c'est ainsi que l'on vend les premières voitures électriques non pas à de riches hipsters californiens, mais à des services de taxi à bas prix quelque part à Omsk.

Une plateforme construite dans cette langue est publiée sous la licence LGPL v3. Honnêtement, je ne voulais pas l'écrire directement dans l'introduction, car c'est loin d'être l'avantage le plus important, mais, en parlant à des personnes travaillant sur l'un de ses principaux marchés potentiels - les plates-formes ERP, j'ai remarqué une caractéristique: toutes ces personnes sans exception disent que même si vous faites la même chose qui est déjà sur le marché, mais gratuitement, alors ce sera déjà très cool. Alors laissez-le ici.

Un peu de théorie


Commençons par la théorie pour souligner la différence dans les approches fondamentales utilisées dans ce langage et dans d'autres langages modernes.

Un petit avertissement, d'autres considérations dans une certaine mesure sont une tentative de tirer un hibou sur le globe, mais avec une théorie fondamentale de la programmation, en principe, disons carrément, pas vraiment, donc vous devez utiliser ce qui est.

L'une des toutes premières et principales tâches résolues par la programmation est le problème du calcul des valeurs des fonctions. Du point de vue de la théorie computationnelle, il existe deux approches fondamentalement différentes pour résoudre ce problème.

La première approche de ce type consiste en diverses machines (dont la plus célèbre est une machine de Turing) - un modèle qui se compose de l'état actuel (mémoire) et d'une machine (processeur), qui à chaque étape modifie cet état actuel d'une manière ou d'une autre. Cette approche est également appelée l'architecture de Von Neumann, et c'est lui qui sous-tend tous les ordinateurs modernes et 99% des langages existants.

La seconde approche est basée sur l'utilisation d'opérateurs, elle est utilisée par les fonctions dites partiellement récursives (ci-après ChRF). De plus, la principale différence entre cette approche n'est pas dans l'utilisation des opérateurs en tant que tels (les opérateurs, par exemple, sont également dans la programmation structurelle en utilisant la première approche), mais dans la possibilité d'itérer sur toutes les valeurs de la fonction (voir l'opérateur de minimiser l'argument) et en l'absence d'état dans processus de calcul.

Comme la machine de Turing, les fonctions partiellement récursives sont Turing complètes, c'est-à-dire qu'elles peuvent être utilisées pour spécifier tout calcul possible. Ici, nous clarifions immédiatement que la machine de Turing et le CRF ne sont que des bases minimales, puis nous nous concentrerons sur eux comme approches, c'est-à-dire sur un modèle avec une mémoire de processeur et sur un modèle avec des opérateurs sans utiliser de variables et la possibilité d'itérer sur toutes les valeurs fonctions respectivement.

Le CRF en tant qu'approche présente trois avantages principaux:

  • C'est beaucoup mieux optimisé. Cela s'applique à la fois directement à l'optimisation du processus de calcul de la valeur et à la possibilité de parallélisme d'un tel calcul. Dans la première approche, la séquelle, au contraire, introduit une très grande complexité dans ces processus.
  • Il est beaucoup mieux incrémenté, c'est-à-dire que pour une fonction construite, il peut être beaucoup plus efficace de déterminer comment ses valeurs vont changer lorsque les valeurs des fonctions que cette fonction construite utilise changent. Strictement parlant, cet avantage est un cas particulier du premier, mais c'est précisément cela qui offre un grand nombre de possibilités, qui ne peuvent fondamentalement pas être dans la première approche, il est donc mis en évidence comme un élément distinct.
  • C'est beaucoup plus facile à comprendre. Autrement dit, la description de la fonction de calcul de la somme d'un indicateur dans le contexte de deux autres indicateurs est beaucoup plus facile à comprendre que si celle-ci est décrite en termes de première approche. Cependant, dans les problèmes algorithmiquement complexes, la situation est diamétralement opposée, mais il convient de noter que les problèmes algorithmiquement complexes dans la grande majorité des domaines sont bons s'ils sont de 5%. En général, pour résumer un peu, le CRF est mathématique, et les machines de Turing sont informatiques. En conséquence, les mathématiques sont étudiées presque à la maternelle, et l'informatique est facultative et au lycée. Comparaison so-so, bien sûr, mais donne toujours une sorte de métrique dans cette affaire.

Les machines de Turing présentent au moins deux avantages:

  • Meilleure applicabilité déjà mentionnée dans les problèmes algorithmiquement complexes
  • Tous les ordinateurs modernes sont construits sur cette approche.

De plus, dans cette comparaison, nous ne parlons que de tâches de calcul de données; dans les problèmes de changement de données sans machines Turing, vous ne pouvez toujours pas le faire.

Après avoir lu à cet endroit, tout lecteur attentif posera une question raisonnable: "Si l'approche CRF est si bonne, pourquoi n'est-elle pas utilisée dans un langage moderne commun?" Donc, en fait, ce n'est pas le cas, il est d'ailleurs utilisé dans la langue qui est utilisée dans la grande majorité des systèmes d'information existants. Comme vous pouvez le deviner, ce langage est SQL. Ici, bien sûr, le même lecteur attentif objectera raisonnablement que SQL est le langage de l'algèbre relationnelle (c'est-à-dire, travailler avec des tables, pas des fonctions), et il aura raison. Officiellement. En fait, nous pouvons nous rappeler que les tables du SGBD sont généralement sous la troisième forme normale, c'est-à-dire qu'elles ont des colonnes clés, ce qui signifie que toute colonne restante de cette table peut être considérée comme une fonction de ses colonnes clés. Pas évident, franchement. Et pourquoi SQL n'est pas passé d'un langage d'algèbre relationnelle à un langage de programmation à part entière (c'est-à-dire travailler avec des fonctions) est une grande question. À mon avis, il y a plusieurs raisons à cela, dont la plus importante est «une personne russe (en fait, toute personne) ne peut pas travailler l'estomac vide, mais ne veut pas travailler sur un estomac bien nourri», en ce sens que, comme le montre la pratique, le travail nécessaire pour cela c'est vraiment titanesque et comporte trop de risques pour les petites entreprises, et pour les grandes entreprises, premièrement, tout va bien, et deuxièmement, il est impossible de forcer ce travail avec de l'argent - la qualité est importante ici, pas la quantité. En fait, l'illustration la plus vivante de ce qui se passe lorsque les gens essaient de résoudre un problème en quantité plutôt qu'en qualité est Oracle, qui a même réussi à mettre en œuvre même l'application la plus élémentaire de l'incrémentalité - des représentations matérialisées mises à jour afin que ce mécanisme ait un certain nombre de restrictions de plusieurs pages (justice) pour le plaisir, Microsoft est encore pire ). Cependant, c'est une histoire distincte, il y aura peut-être un article séparé à ce sujet.

En même temps, ce n'est pas que SQL est mauvais. Non. À son niveau d'abstraction, il remplit parfaitement ses fonctions, et l'implémentation actuelle de la plateforme l'utilise un peu moins que complètement (en tout cas, beaucoup plus que toutes les autres plateformes). Une autre chose est qu'immédiatement après sa naissance, SQL s'est en fait arrêté dans le développement et n'est pas devenu ce qu'il pourrait devenir, à savoir le langage qui sera discuté maintenant.

Mais assez de théorie, il est temps de passer directement à la langue.

Donc, nous rencontrons :


Plus précisément, cet article sera la première partie de trois (car il y a encore trop de matériel, même pour deux articles), et il ne parlera que du modèle logique - c'est-à-dire uniquement de ce qui est directement lié à la fonctionnalité du système et n'est pas lié aux processus développement et implémentation (optimisation des performances). De plus, nous ne parlerons que de l'une des deux parties du modèle logique - la logique du domaine. Cette logique détermine quelles informations le système stocke et ce que vous pouvez faire avec ces informations (lors du développement d'applications métier, elle est aussi souvent appelée logique métier).

Graphiquement, tous les concepts de logique de domaine dans lsFusion peuvent être représentés par l'image suivante:


Les flèches sur cette image indiquent les directions d'utilisation par les concepts les uns des autres, ainsi, les concepts forment une sorte de pile, et, en conséquence, c'est dans l'ordre de cette pile que je vais en parler.


Les propriétés


Une propriété est une abstraction qui prend un ou plusieurs objets comme paramètres et renvoie un objet en conséquence. La propriété n'a aucun effet secondaire et, en fait, est une fonction pure, cependant, contrairement à cette dernière, elle peut non seulement calculer des valeurs, mais aussi les stocker. En fait, le nom «propriété» lui-même est emprunté à d'autres langages de programmation modernes, où il est utilisé à peu près aux mêmes fins, mais il est cloué à l'encapsulation et, par conséquent, n'est pris en charge que pour les fonctions avec un paramètre. Eh bien, le fait que ce mot même «propriété» soit plus court que «fonction pure» et qu'il n'y ait pas d'associations inutiles, a joué en faveur de l'utilisation de ce terme particulier.

Les propriétés sont définies de manière récursive à l'aide d'un ensemble prédéfini d'opérateurs. Il y a beaucoup de ces opérateurs, par conséquent, nous ne considérons que les principaux (ces opérateurs couvrent 95% de tout projet statique moyen).

Propriété principale (DATA)


La propriété principale est une propriété dont la valeur est stockée dans la base de données et peut être modifiée à la suite de l'action correspondante (à ce sujet un peu plus tard). Par défaut, la valeur de chacune de ces propriétés pour tout ensemble de paramètres est égale à une valeur NULL spéciale.
quantity = DATA INTEGER (Item);
isDayOff = DATA BOOLEAN (Country, DATE);
, ( ), .

. :

class X { 	
    Y y; 	
    Map<Y, Z> f; 	
    Map<Y, Map<M, Z>> m; 	
    List<Y> n;
    LinkedHashSet<Y> l; //   
    static Set<Y> s;
}

:
y = DATA Y (X);
f = DATA Z (X, Y);
m = DATA Z (X, Y, M);
n = DATA Y (X,INTEGER);
l = DATA INTEGER (X,Y);
s = DATA BOOLEAN (Y);

(JOIN), , (+,-,/,*), (AND, OR), (+, CONCAT), (>,<,=), (CASE, IF), (IS)

f(a) = IF g(h(a)) > 5 AND a IS X THEN ‘AB’ + ‘CD’ ELSE x(5);
- , . , , , :

  • , . , ( NULL). , lsFusion – , , , – TRUE ( FALSE NULL), 3-state’.
  • NULL: (+), (-), CONCAT . :
    • : NULL 0, – , 0 NULL ( 5 (+) NULL = 5, 5 (-) 5 = NULL, 5 + NULL = NULL 5 — 5 = 0).
    • : NULL ( CONCAT ‘ ‘, ‘John’,’Smith’ = ‘John Smith’, CONCAT ‘ ‘, ‘John’, NULL = ‘John’, ‘John’ + ‘ ‘ + NULL = NULL).
  • (IF) ( ) : f(a) IF g(a), f(a) g(a) NULL, NULL – .

(GROUP)


– . (, ) .

:

  • :
    sum(Invoice i) = GROUP SUM sum(InvoiceDetail id) IF invoice(id) = i;
    currentBalance(Sku sk) = GROUP SUM currentBalance(sk, Stock st);
    , ( i sk). , , - :
    x() = (GROUP SUM f(a)) + 5;
  • SQL-:
    sum = GROUP SUM sum(InvoiceDetail id) BY invoice(id);
    currentBalance = GROUP SUM currentBalance(Sku sk, Stock st) BY sk;
    ( , , )

, ( ), – ( ). , , , ( , , SQL – ). , ( BY), - :
// BY     ,   s
sum(DATE from, Stock s, DATE to) = GROUP sum(Invoice i) IF date(i) >= from AND date(i) <=to BY stock(i); 
, , , , , .

:

  • /,

  • .

/ (PARTITION … ORDER)


( , ) , . , ( , ). / .
place(Team t) = PARTITION SUM 1 ORDER DESC points(t) BY conference(t);
, , , , , , .

SQL ( , ) (OVER PARTITION BY… ORDER BY).

(RECURSION)


– , . , , .

. , ( ):

  • (result) ( ) :
    • result(0, o1, o2, ..., oN) = initial(o1, ..., oN), initial –
    • result(i+1, o1, o2, ..., oN) = step(o1, ..., oN, $o1, $o2, ..., $oN) IF result(i, $o1, $o2, ..., $oN), step – .
  • , ( o1, o2, …, oN). , .

, , , , :
//   integer  from  to (       System)
iterate(i, from, to) = RECURSION i=from STEP i=$i+1 AND i<=to CYCLES IMPOSSIBLE;
 
//      a  b   ( ,  ,  )
edge = DATA BOOLEAN (Node, Node);
pathes '- ' (a, b) = RECURSION 1 IF b=a STEP 1 IF edge(b, $b);
 
// ,     child  parent,  null,     (         child')
parent  = DATA Group (Group);
level '' (Group child, Group parent) = RECURSION 1 AND child IS Group AND parent = child STEP 1 IF parent = parent($parent);
 
//  ,        to, (   NULL)
fib(i, to) = RECURSION 1 IF (i=0 OR i=1STEP 1 IF (i=$i+1 OR i=$i+2AND i<to CYCLES IMPOSSIBLE;
, / , , , .

, , , , , lsFusion – .

SQL CTE, , , . , Postgres GROUP BY , , , , , , . , , WHILE’ .

. , , .


– , , , , ( , , ). , , “”, , -, , , -, , “”.

, – lsFusion. , – . , – ( , , , ). , – . .

, . / , , , , , . 3 97, lsFusion – 60 40.

, , . , -, ( , ), .

, :

(FOR), (WHILE)


, lsFusion , , NULL ( ).
FOR selected(Team team) DO
    MESSAGE 'Team ' + name(team) + ' was selected';
, :
showAllDetails(Invoice i) {
    FOR invoice(InvoiceDetail id) = i ORDER index(id) DO
        MESSAGE 'Sku : ' + nameSku(id) + ', ' + quantity(id);
}
, , (IF).

(WHILE) , :

  • , NULL ( )


(EXEC), ({…}), (CASE, IF), (BREAK), (RETURN)

f(a) {
    FOR t=x(b,a) DO {
        do(b);
        IF t>5 THEN
            BREAK;
    }
    MESSAGE 'Succeeded';
}
- . , , .

(CHANGE)


. , , , , NULL. :
//       
setDiscount(Customer c)  {
    discount(c, Item i) <- 15 WHERE selected(i);
}
, :
setDiscount(Customer c)  {
    FOR selected(Item i) DO
        discount(c, i) <- 15;
}
, , , ( , , , , selected discount – ), . , , .

(NEW)


( , , , , ). , , .

:
newSku ()  {
    LOCAL addedSkus = Sku (INTEGER);
    NEW Sku WHERE iterate(i, 13TO addedSkus(i);
    FOR Sku s = addedSkus(i) DO {
        id(s) <- 425;
        name(s) <- 'New Sku : ' + i;
    }
}
, , — NEW (FOR), ( ):
FOR iterate(i, 13NEW s=Sku DO  {
    id(s) <- i;
    name(s) <- 'New Sku : ' + i;
}
, FOR :
NEW s=Sku DO {
    id(s) <- 425;
    name(s) <- 'New Sku';
}
– , , , .

(DELETE)


– :
DELETE Sku s WHERE name(s) = 'MySku';
, «» .

, , .


, , . , , . .

, . , — / .

, , . , ( ), .

, , . , :
LOCAL f = INTEGER (INTEGERINTEGER);

f(1,3) <- 6;
f(2,2) <- 4;
f(f(1,3),4) <- 5;
f(a,a) <- NULL//      1-  2-  (  2,2)

MESSAGE GROUP CONCAT a + ',' + b + '->' + f(a, b),' ; '//  1,3->6 ; 6,4->5
: (APPLY) (CANCEL). , , , . , , , . – , , , , . — .

(NEWSESSION, NESTEDSESSION)


(, , http- ..). , , . , , , «», «» ( , ). NEWSESSION, ( ). :
run() {
    f(1) <- 2;
    APPLY;
    f(1) <- 1;
    NEWSESSION {
        MESSAGE f(1); //  2,       
        f(2) <- 5;
        APPLY;          
    }
    MESSAGE f(1); //  1,     1  ,   
}
, , , . , :
run(Store s) {
    NEWSESSION
        MESSAGE 'I see that param, its name is: ' + name(s);
}
, , ( ). NESTED, , , , . , ( , NESTED). :
g = DATA LOCAL NESTED INTEGER ();
run() {
    f(1) <- 1; g() <- 5;
    NEWSESSION NESTED (f) {
        MESSAGE f(1) + ' ' + g(); //  1 5
        f(1) <- 5; g() <- 7;
    }
    MESSAGE f(1) + ' ' + g(); //  5 7
}
. :

  • , , , < —
  • , , : < —
  • , : < — .

, (, ), , . , , - , , , , , :


  • ,

(APPLY), (CANCEL)


– , . , . , :

  • . , , , NESTED ( ).
  • , . , - , , , . , (update conflict), , , . , :

// -------------------------- Object locks ---------------------------- //
 
locked = DATA User (Object);
lockResult = DATA LOCAL NESTED BOOLEAN ();
 
lock(Object object)  {
    NEWSESSION { 
        lockResult() < - NULL;
        APPLY SERIALIZABLE {
            IF locked(object) THEN {
                CANCEL;
            } ELSE {
                locked(object) <- currentUser();
                lockResult() <- TRUE;
            }
        }
    }
}
 
unlock(Object object)  {
    NEWSESSION
        APPLY locked(object) <- NULL;
}
PS: Authentication, , , ( ) ( ). , , lsFusion , (, ).

– , , , , ( ).

(PREV, CHANGED, SET, DROPPED)


: (PREV), (CHANGED), NULL NULL (SET) .. ( ), , , :
f = DATA INTEGER (INTEGER);
run() {
    f(1) <- 2;
    APPLY;
 
    f(1) <- 5;
    f(2) <- 3;
    MESSAGE GROUP SUM 1 IF CHANGED(f(a)); // ,    f     ,  2
    MESSAGE '. : ' + f(1) + ', . : ' + PREV(f(1)); //  . : 5, . : 2
}
. , , , , .


“ ?”, “ ?”. , , .

, , . , . .

:

  • – .
  • – , / .

, , :

  • – .
  • – .

, , , .

:

  • , , , .
  • , .

:

  • , « ». , , , ( , , 5-10 , ).
  • , ( ), , , , .

:

  • , , .

:

  • , , ( ), , .

( , ), .
ON { //   ,       APPLY
    MESSAGE 'Something changed';
}
, , , , ‘Something changed’ (!) ( , ). , , - , (CHANGED, SET, DROPPED ..). , - , -, -. – :
//  email,          
WHEN SET(balance(Sku s, Stock st) < 0DO
      EMAIL SUBJECT '     ' + name(s) + '   ' + name(st);

WHEN LOCAL CHANGED(customer(Order o)) AND name(customer(o)) == 'Best customer' DO
    discount(OrderDetail d) <- 50 WHERE order(d) = o;

, – , . , :
ON {
    FOR SET(balance(Sku s, Stock st) < 0DO
        EMAIL SUBJECT '     ' + name(s) + '   ' + name(st);
}
, , / , , .

SQL ( ) . , , , ( ) , .

, :

  • – , ( )
  • – , . , , «» .


, , . , , NULL:
//    0
CONSTRAINT balance(Sku s, Stock st) < 0 
    MESSAGE '    ';

// ""  
CONSTRAINT DROPCHANGED(barcode(Sku s)) AND name(currentUser()) != 'admin' 
    MESSAGE ' -       ';

//      ,   
CONSTRAINT sku(OrderDetail d) AND NOT in(sku(d), customer(order(d)))
    MESSAGE '        ';
, – , NULL (SET) , – NULL . – , , , / , .

, – ( ), , , ( ), , .


. , , , – . , :
f = DATA A (INTEGER);
, f NULL , A. :
f = Object (INTEGER);
CONSTRAINT f(i) AND NOT f(i) IS A MESSAGE ' '// f(i) => f(i) IS A
, , – . ( , , «»), , , , : - , .

, lsFusion . , lsFusion . , . lsFusion , , - :
CLASS A {
    f = DATA LONG (INTEGER); //  f = DATA LONG (A, INTEGER)
}
lsFusion , :
CLASS Animal;
CLASS Transport;
CLASS Car : Transport;
CLASS Horse : Transport, Animal;
, – .


lsFusion – . , , :
speed = ABSTRACT LONG (Transport);
/ , :
CLASS Breed;
speed = DATA LONG (Breed)
breed = DATA Breed (Animal);

speed(Horse h) += speed(breed(h)); //       
( ):
CLASS Thing;
CLASS Ship : Thing;
CLASS Asteroid : Thing;

collide ABSTRACT (Thing, Thing);
collide(Ship s1, Ship s2) +{
    MESSAGE 'Ship : ' + name(s1) + ', Ship : ' + name(s2);
}
collide(Ship s1, Asteroid a2) +{
    MESSAGE 'Ship : ' + name(s1) + ', Asteroid : ' + name(a2);
}
collide(Asteroid a1, Ship s2) +{
    MESSAGE 'Asteroid : ' + name(a1) + ', Ship : ' + name(s2);
}
collide(Asteroid a1, Asteroid a2) +{
    MESSAGE 'Asteroid : ' + name(a1) + ', Asteroid : ' + name(a2);
}
, , ( ), . ABSTRACT :
speed(Transport t) = CASE 
    WHEN t IS Horse THEN speed(breed(t))
    //  
END
, .

, , :
speed(Horse h) = speed(breed(h));
, , ( ). , , .


, , . () : , , . , , , , , . NULL , , :
f = DATA LONG (LONG);
g = DATA LONG (A);
h(a) = OVERRIDE f(a), g(a); //   


( ) – , . , , , :
CLASS Direction '' {
    left '',
    right '',
    forward ''
}

result(dir) = CASE
    WHEN dir = Direction.left THEN ' '
    WHEN dir = Direction.right THEN ' '
    WHEN dir = Direction.forward THEN ' '
END
, .

enum’, , .


( lsFusion, ) :

  • ( ).
  • ( ).
  • .

.

() , NULL . , , , , , .

:
//   A    B
b(A a) = AGGR B WHERE a IS A; 
//     a     B    A,   b(a(b)) = b

createC = DATA BOOLEAN (A, B)
//    A  B    createC    C
//     ,       ,      
c = AGGR C WHERE createC(A a, B b); 
//     a  b     C      B 
, , ( ):
CLASS Shipment '';
date = ABSTRACT DATE (Shipment);
CLASS Invoice '';
createShipment ' ' = DATA BOOLEAN (Invoice);
date ' ' = DATA DATE (Invoice);
CLASS ShipmentInvoice '  ' : Shipment;
//    ,       
shipment(Invoice invoice) = AGGR ShipmentInvoce WHERE createShipment(invoice);
date(ShipmentInvoice si) += sum(date(invoice(si)),1); //   =   + 1
, (, ), ( ) , . , ERP 1100 . , , , . , ( 50 ), , 1100 300 .

, , , , . , , – , – , , , « - », , , .


, lsFusion . , , , , , , , , , ( , , ). , lsFusion ( ACID), , SQL-, . , DSL lsFusion , , – – . , SQL , , lsFusion . , , – , github ( , ), (IDE, , VCS, ), slack telegram- , (linux maven, - ), , , , , lsFusion , SQL, ABAP 1 – .

, , lsFusion, , ( ), : ERP-, SQL- , ORM-, RAD-, . , , .

, :

  • SQL- – - – , , .
  • ERP- – - – , , ..
  • ORM- – - (, ), .
  • RAD – - , , , IT-.
  • – , RAD, , - , , Excel (, , , – ).

, lsFusion ERP, RAD SQL , lsFusion ( ). , SQL, , , , , , Fortran C ( , ). ORM- , – . , , - .

SME ( , ), ( ). , 1 , .

, , , , , ( 12 ), .

UPD: .

Source: https://habr.com/ru/post/fr458376/


All Articles