При написании алгоритмов зачастую возникает ситуация, когда какая-то функция нуждается в вызове с тем же количеством аргументов, но с не совпадающими типами. Решаем.

Надуманный пример на C++:

#include <iostream>
#include <string.h>
using namespace std;

void a(int x)
{
    cout<<"number\n";
}

void a(float x)
{
    cout<<"number\n";
}

void a(const char *x)
{
    cout<<"string\n";
}

template <typename T>
void f(void(*g)(T x), T x)
{
    g(x);
}

int main()
{
    f(a, 1);
    f(a, 1.0f);
    f(a, "Alexey");

    return 0;
}

Тот же код на Javascript:

function f(g, x)
{
    g(x)
}

function a(x)
{
    alert(typeof x)
}

f(a, 1)
f(a, 1.0)
f(a, 'Alexey')

Очевидно, что код на javascript является более удачным. Давайте разберёмся, благодаря чему так происходит.

Динамический подход


В javascript у каждой переменной есть свойство «тип», который возвращается при действии оператора typeof на переменную. Это свойство хранится во время выполнения программы, всегда, когда существует переменная. И когда к переменной применяется какой-то оператор, функция, реализующая этот оператор, проверяет тип переменной. В соответствии с типом выполняются какие-то действия. Например 2+2 вернёт 4, 2+'Alexey' вернёт '2Alexey'. Т.е. операторы языка обязаны почти всегда проверять типы переменных, к которым они применяются. Назовём данный подход «динамической типизацией».

Минусы этого подхода достаточно очевидны, точнее один минус-необходимо делать дополнительные вычисления во время выполнения программы.

Плюсом будет код, в котором нет понятия типа в явном виде, только последовательность действий, что положительно сказывается на читаемости.

Ещё одним плюсом будет код, в котором не нужно дублировать значительную часть логики. Это слегка видно на примере выше-функция f реализуется один раз, но при этом результат её выполнения зависит от переданной в неё функции, реализующей различную логику.

Статический подход


Язык C требует указывать тип переменной и запрещает его менять. В результате во время выполнения программы нет никаких проверок типов, операторы применяются к переменным однозначно. Но при этом нет красивых механизмов обобщения. Что есть: универсальный указатель void*, возможность передать функции указатель на другую функцию. В языке C++, который в некотором роде является расширением языка C, появились такие механизмы как полиморфизм функций(перегрузка) и шаблоны.

Полиморфизм функций позволяет назвать 2 функции, делающие одно и то же но с разными типами данных, одинаковыми именами.

Шаблоны вводят новый тип-общий для всех типов. Хороши эти механизмы тем, что с их помощью, без участия программиста, производится подстановка функций с нужными типами. Т.е. если шаблон одной функции задействует 2 разных типа, то во время компиляции будут созданы 2 разные функции. При вызове исходной функции в коде будет подставляться в скомпилированный файл вызов скопированной функции нужного типа.

Плюсы подхода: максимальная скорость выполнения.
Минусы: больший объём памяти, необходимый для выполнения.

Вывод типов


Другими словами, когда нужна максимальная производительность и памяти хватает более предпочтительным является статический подход.Но при выборе, допустим, языка C++, теряется читаемость кода. Ведь на каждый чих приходится указывать тип, с которым вызывается функция.

Например, код быстрой сортировки:

#include <iostream>
#include <string.h>
using namespace std;

template<class T>
void qsort(T** array, int length, int compare(T *a, T *b))
{
    int  left  = 0;
    int  right = length-1;
    T   *middle_element;

    middle_element=array[length/2];

    do
    {
        while( compare(array[left], middle_element)<0 )
            left++;

        while( compare(array[right], middle_element)>0 )
            right--;

        if(left<=right)
        {
            swap(array[left], array[right]);

            left++;
            right--;
        }
    }
    while(left<=right);

    if(right>0)
        qsort(array, right, compare);

    if(left<length)
        qsort(array+left, length-left, compare);
}

int cmp(char *a, char *b)
{
    return strcmp(a, b);
}

int main()
{
    char *strings[]={"Alexey", "Borisenko"};

    qsort(strings, 2, cmp);

    return 0;
}

При полном удалении типов только выигрывает в читаемости:

#include <iostream>
#include <string.h>
using namespace std;


qsort(array, length, compare)
{
    left           = 0;
    right          = length-1;
    middle_element = array[length/2];

    do
    {
        while( compare(array[left], middle_element)<0 )
            left++;

        while( compare(array[right], middle_element)>0 )
            right--;

        if(left<=right)
        {
            swap(array[left], array[right]);

            left++;
            right--;
        }
    }
    while(left<=right);

    if(right>0)
        qsort(array, right, compare);

    if(left<length)
        qsort(array+left, length-left, compare);
}

cmp(a, b)
{
    return strcmp(a, b);
}

main()
{
    strings={"Alexey", "Borisenko"};
    qsort(strings, 2, cmp);

    return 0;
}

Из этих соображений был сделан вывод, что отказаться от типов-хорошая идея. Давайте порассуждаем, как же это реализовать.

Выделим 3 основных типа:

1) Простой
2) Составной
3) Функция

К простому типу отнесём все типы, в которые не вложены другие(строковый, числовой, массив ...). К составному соответственно составные конструкции, такие как гетерогенный массив, структура и т.д. Функция стоит особняком, поскольку у неё есть аргументы, которые сами являются типами.

Итак, вывод типа производится при следующих операциях:

1) Присваивание
2) Вызов функции

При выводе типа переменная не может поменять тип. Доказательтво от противного:

f(a)
{
    x=1

    if(a<2)
    	x='Alexey'

    return x
}

В этом коде возникает необходимость выполнить код, чтобы узнать возвращаемый тип функции f, что является противоречием статического подхода.

Из этого вытекает необходимость проверять тип при каждом присваивании либо вызове функции во время компиляции программы.

Вывод типа из выражения делается вполне однозначно, так как операторы, как унарные, так и бинарные, действуют во множество определённого типа. Так, оператор "+" при сложении двух типов int даёт в результате int. Проблема возникает если применить бинарный оператор к переменным с разным типом. Сложение int с float может быть и int, и float, либо вообще быть ошибкой, поскольку возможна потеря точности.

Таким образом при присваивании значения переменной проверяется совпадение типа переменной с выведенным типом выражения. Это уже позволяет выводить типы для кода, у которого нет вызова функции, принимающей на вход другую функцию. Если есть такой вызов, то придётся выяснять, какую функцию подставлять в качестве аргумента.

Рассмотрим более подробно вывод основных типов.

Простой, очевидно, включается в остальные. Составной выводится присваиванием:

x={name:"Alexey"}

А также через описание типа в аргументах функции с указанием всех используемых в этом типе переменных:

f(user {name})
{
}

Проблема вывода функции в том, что она может принимать аргументом другую функцию а также возвращать функцию. В этом случае приходиться рекурсивно выводить тип вложенной функции и лишь затем выводить типы аргументов.

Концовка


На основании всего написанного я, автор статьи, пишу язык программирования, который, будучи динамическим, со временем станет статическим. Уже поддерживается вывод типом на первом уровне функции (без использования замыканий).
Поделиться с друзьями
-->

Комментарии (69)


  1. DrPass
    02.09.2016 11:35
    +6

    > Шаблоны вводят новый тип-общий для всех типов
    Вообще-то шаблоны — это именно шаблоны. А общий тип вводят дженерики, которые есть в Java, C#, Delphi, и напрочь отсутствуют в С++. И да, синтаксически выглядят подобно шаблонам. И вот как раз «общий тип», сиречь, дженерик, не генерирует несколько разных экземпляров кода под разные типы, а оперирует приведением типов.


    1. lair
      02.09.2016 11:39
      +4

      И вот как раз «общий тип», сиречь, дженерик, не генерирует несколько разных экземпляров кода под разные типы, а оперирует приведением типов.

      В C# именно что генерирует разные экземпляры. Типичный пример:


      class Smth<T>
      {
          static readonly Serializer S = new Serializer(typeof(T));
      }


    1. Shannon
      02.09.2016 13:50
      -3

      Чтобы нагляднее было, например swift:

      func f<T>(fn: T -> Void, _ item: T) {
          fn(item)
      }
      
      func a<T>(item: T) {
          print(item.dynamicType)
      }
      
      f(a, 1) // Int
      f(a, 1.0) // Double
      f(a, "Alexey") // String
      


      1. roman_kashitsyn
        02.09.2016 14:51
        +3

        Чтобы нагляднее было

        А что именно стало наглядней?


        1. Shannon
          02.09.2016 19:16

          Чтобы автору статьи стало нагляднее, что дженерики решают всё то, что написано в статье


          1. roman_kashitsyn
            04.09.2016 00:26

            Чтобы автору статьи стало нагляднее, что дженерики решают всё то, что написано в статье

            Автор статьи не хочет явно типы писать, как дженерики решают эту проблему?


            1. Shannon
              04.09.2016 01:40

              Автор статьи не хочет явно типы писать, как дженерики решают эту проблему?
              Вероятно он хочет решать не эту проблему, потому что эта проблема уже решена в js

              Самое начало статьи:
              При написании алгоритмов зачастую возникает ситуация, когда какая-то функция нуждается в вызове с тем же количеством аргументов, но с не совпадающими типами. Решаем.
              И приводится решение этой задачи 2-мя примерами на C++ и JS, после чего промежуточный вывод «Очевидно, что код на javascript является более удачным.»

              Дальше идет, собственно, и сама суть:
              Другими словами, когда нужна максимальная производительность и памяти хватает более предпочтительным является статический подход.Но при выборе, допустим, языка C++, теряется читаемость кода. Ведь на каждый чих приходится указывать тип, с которым вызывается функция.
              Соответственно вместо написания своего языка, обозначенную проблему (писать максимально производительный код без потери читаемости и без необходимости писать тип на «каждый чих») можно решать другим путем, и я лишь дополнил наглядным примером, что получившийся код, например, на swift не сильно сложнее варианта на js


              Но раз речь зашла про не указывать типы явно, то например, в swift тоже не обязательно описать типы явно, они выводятся из контекста, а для функций можно использовать дженерики, таким образом получится гибрит, когда ни один тип явно не задан и код не теряет в читаемости (тут http://www.runswiftlang.com/ можно поэкспериментировать онлайн)

              func f<T>(fn: T -> Void, _ item: T) {
                  fn(item)
              }
              
              func a<T>(item: T) {
                  print("item type: \(item.dynamicType)") // аналог typeof item
              }
              
              let a1 = 1
              let a2 = 1.0
              let a3 = "Alexey"
              
              f(a, a1) // item type: Int
              f(a, a2) // item type: Double
              f(a, a3) // item type: String
              
              let z = "zzz"
              let zz = ["key1": z]
              f(a, zz) // item type: Dictionary
              
              var b1 = 1
              var b2 = "str"
              b1 = b2 // error: cannot assign value of type 'String' to type 'Int'


              1. lair
                04.09.2016 01:54

                когда ни один тип явно не задан

                Ну, это неправда. Типы параметров функций задаются явно (то, что они при этом дженеричны — это второй вопрос).


                let f fn item = fn item
                let a item = fn.GetType() |> printfn "%A"

                Вот тут типов параметров функций и правда нет (при этом все статически типизированное).


                1. Shannon
                  04.09.2016 02:14

                  Да, не правда, поэтому и назвал это «гибридом», чтобы просто идею продемонстрировать


    1. qadmium
      02.09.2016 22:22
      +2

      А общий тип вводят дженерики, которые есть в Java, C#, Delphi, и напрочь отсутствуют в С++.


      C#?

       class Program
          {
              static void Main(string[] args)
              {
                  Generic(1);
                  Generic("1");
              }
      
              static string Generic<T>(T value)
              {
                  return value.ToString();
              }
          }
      


      int:

      --- C:\Users\sow\Documents\Visual Studio 2015\Projects\testawait\testawait\Program.cs 
          24:             return value.ToString();
      029C2DE8  push        ebp  
      029C2DE9  mov         ebp,esp  
      029C2DEB  sub         esp,8  
      029C2DEE  xor         eax,eax  
      029C2DF0  mov         dword ptr [ebp-8],eax  
      029C2DF3  mov         dword ptr [ebp-4],ecx  
      029C2DF6  cmp         dword ptr ds:[0FBC7D4h],0  
      029C2DFD  je          029C2E04  
      029C2DFF  call        72BE6150  
      029C2E04  lea         ecx,[ebp-4]  
      029C2E07  call        717B6220  
      029C2E0C  mov         dword ptr [ebp-8],eax  
      029C2E0F  mov         eax,dword ptr [ebp-8]  
      029C2E12  mov         esp,ebp  
      029C2E14  pop         ebp  
      029C2E15  ret  
      


      string:
      --- C:\Users\sow\Documents\Visual Studio 2015\Projects\testawait\testawait\Program.cs 
          24:             return value.ToString();
      029C2E2C  in          al,dx  
      029C2E2D  or          al,33h  
      029C2E2F  ror         byte ptr [ecx+4D89F445h],0FCh  
      029C2E36  mov         dword ptr [ebp-8],edx  
      029C2E39  cmp         dword ptr ds:[0FBC7D4h],0  
      029C2E40  je          029C2E47  
      029C2E42  call        72BE6150  
      029C2E47  mov         ecx,dword ptr [ebp-4]  
      029C2E4A  mov         eax,dword ptr [ecx]  
      029C2E4C  mov         eax,dword ptr [eax+28h]  
      029C2E4F  call        dword ptr [eax]  
      029C2E51  mov         dword ptr [ebp-0Ch],eax  
      029C2E54  mov         eax,dword ptr [ebp-0Ch]  
      029C2E57  mov         esp,ebp  
      029C2E59  pop         ebp  
      029C2E5A  ret  
      


  1. Halt
    02.09.2016 11:42
    +10

    Советую вам обратить внимание на литературу по этой теме, дабы не изобретать велосипед. А если и изобретать, то делать это осознанно.
    То что вы описали, реализуется на полиморфном типированном лямбда-исчислении алгоритмом Хиндли-Милнера.

    Буквально неделю назад, я читал доклады на эту тему на конференции C++ Siberia. Видео еще нет, а слайды можно посмотреть.


    Реализацию алгоритма вывода типов можно посмотреть у меня на гитхабе. Вывод типов в циклах и замыканиях присутствует.


    1. babylon
      02.09.2016 13:41
      -3

      +2


  1. Veliant
    02.09.2016 11:42
    +2

    Язык C требует указывать тип переменной и запрещает его менять. В результате во время выполнения программы нет никаких проверок типов, операторы применяются к переменным однозначно. Но при этом нет красивых механизмов обобщения. Что есть: универсальный указатель void*, возможность передать функции указатель на другую функцию. В языке C++, который в некотором роде является расширением языка C, появились такие механизмы как полиморфизм функций(перегрузка) и шаблоны.

    Читайте стандарт C11. Там есть следующие конструкции:
    #define cbrt(X) _Generic((X), long double: cbrtl,                               default: cbrt,                               float: cbrtf)(X)
    

    При вызове cbrt с аргументом long double типа будет вызвана cbrtl, с аргументом float — cbrtf


    1. Lex20
      02.09.2016 17:33

      Имел ввиду ANSI С


  1. lair
    02.09.2016 11:44

    К простому типу отнесём все типы, в которые не вложены другие(строковый, числовой, массив ...).

    И почему же в массив не вложены другие типы?


    Вывод типа из выражения делается вполне однозначно, так как операторы, как унарные, так и бинарные, действуют во множество определённого типа. Так, оператор "+" при сложении двух типов int даёт в результате int.

    Это только ваше допущение, оно не обязательно всегда верно.


    И да, как вы, собственно, собираетесь выводить тип аргументов функции?


    f(user {name}) {}

    Какого типа user.name?


    1. Lex20
      02.09.2016 17:43

      И почему же в массив не вложены другие типы?

      В простом массиве все данные одного типа.
      В гетерогенный вложены.
      И да, как вы, собственно, собираетесь выводить тип аргументов функции?

      Тип выводиться в теле функции.


      1. lair
        02.09.2016 17:47

        В простом массиве все данные одного типа.

        … и этот тип не "вложен" в массив разве? Собственно, массив — это первый и канонический пример обобщенного типа, вообще-то.


        Тип выводиться в теле функции.

        fun f(a) -> a.x


        Какого типа a? Какого типа возвращаемое значение?


        1. roman_kashitsyn
          02.09.2016 18:07
          +2

          Какого типа a? Какого типа возвращаемое значение?

          В OCaml, к примеру, функция f имеет вполне определённый тип, тип аргумента определяется его структурной, тип возвращаемого значения — полиморфный:


          let f a = a#x;;
          
            val f : < x : 'a; .. > -> 'a = <fun>


          1. lair
            02.09.2016 18:10

            Я, в принципе, в общем случае представляю себе, как это делается, но есть, так сказать, нюансы. Ну и мне интересно, что именно автор по этому поводу думает.


            (кстати, а OCaml в таком случае требует, чтобы у a было как минимум x, или только x?)


            1. roman_kashitsyn
              02.09.2016 18:19
              +2

              как минимум x, или только x?

              Как минимум x. За это отвечают две точки после точки с запятой: ; .. >. Если бы этих точек не было, тип бы означал "только x".


        1. Lex20
          03.09.2016 14:21

          Аргументы выводятся при каждом вызове функции. Тип возвращаемого значения выводится в теле самой функции. Если возвращаемых значений 2 и более — ошибка компиляции.


          1. lair
            03.09.2016 14:25
            +1

            Я там выше привел пример. Какого типа параметр функции и возвращаемое ей значение?


            Ну или вот вам другой пример.


            fun f (a, b) = a > b


            Какого типа a и b?


            Или вот еще:


            fun f (a, b) = if a > b then a.x else b.y


            1. Lex20
              03.09.2016 14:40

              fun f (a, b) = a > b

              При таком вызове функции:
              f(1, 'string')
              

              a-числовой, b-строковый тип. Если для таких a и b определён оператор >, то возвращаемое значение функции a будет того же типа, что и возвращаемое значение оператора >.
              fun f (a, b) = if a > b then a.x else b.y
              

              Здесь ошибка, поскольку в аргументах не обозначено, что переменная a включает в себя переменную x, b, соответственно, y.
              Если было бы обозначено, и функция возвращала значения разных типов(типы переменных x и y), то была бы ошибка компиляции.


              1. lair
                03.09.2016 14:48
                +1

                При таком вызове функции f(1, 'string') a-числовой, b-строковый тип.

                Ну то есть вы не выводите тип параметра, на самом деле (а, значит, и тип функции). Вы, наоборот, подставляете ранее выведенный тип в параметр, и от этого пытаетесь посчитать, возможна ли функция. А что делать, если вызовов нет?


                Здесь ошибка, поскольку в аргументах не обозначено, что переменная a включает в себя переменную x

                И опять, это означает, что вы не выводите тип параметра по употреблению (хотя это возможно), а на этот раз требуете этой аннотации от программиста (и чем это отличается от явной типизации?).


                Если было бы обозначено, и функция возвращала значения разных типов(типы переменных x и y),

                … а как вы определяете, каких типов x и y? Опять подстановкой при вызове?


                Честное слово, модель Хиндли-Милнера, с выводом ограничений, выглядит продуктивнее.


                1. Lex20
                  03.09.2016 15:05
                  -1

                  А что делать, если вызовов нет?

                  В этом случае функция не нужна.
                  И опять, это означает, что вы не выводите тип параметра по употреблению (хотя это возможно), а на этот раз требуете этой аннотации от программиста (и чем это отличается от явной типизации?).

                  Предполагалось задавать порядок следования элементов в структуре, хотя может вы и правы.
                  … а как вы определяете, каких типов x и y? Опять подстановкой при вызове?

                  Их типы задаются при первой инициализации структуры.


                  1. lair
                    03.09.2016 16:18

                    В этом случае функция не нужна.

                    Ну то есть никаких компонентов для повторного использования. Вы это серьезно?


                    Предполагалось задавать порядок следования элементов в структуре

                    Кого он вообще волнует в рамках системы типов?


                    Их типы задаются при первой инициализации структуры.

                    … а структура инициализируется снаружи, что опять означает подстановку.


                    1. humbug
                      03.09.2016 19:16
                      +3

                      Функция main нигде в программе не вызывается, значит её можно удалить. Затем можно удалить все остальные функции, которые вызывались только из main, затем и взаимно рекурсивные функции, которые не вызываются снаружи. То есть совершенно всё.


                      1. lair
                        03.09.2016 19:18

                        Кстати, да, как же делать "вывод" типов по использованию для входящих данных?


                        1. Lex20
                          04.09.2016 10:19
                          -1

                          Их не нужно выводить, они уже известны.


                          1. lair
                            04.09.2016 10:21
                            +1

                            Это откуда же?


                      1. Lex20
                        04.09.2016 10:18
                        -1

                        С main начинается вывод типов, дальше рекурсия по вызовам.


                        1. lair
                          04.09.2016 10:31
                          +2

                          А если в прикладном коде нет main? (любой код под внешним фреймворком, будь то тесты, веб-приложение, сервис (который на запросы отвечает) и так далее)


              1. lair
                03.09.2016 14:53
                +1

                Если для таких a и b определён оператор >

                Эээ, стоп. Что значит "для x определен оператор y"?


                1. Lex20
                  03.09.2016 15:06

                  Предусмотрено сравнение числа и строки.


                  1. lair
                    03.09.2016 16:16
                    +1

                    "Предусмотрено" кем?


                    Смотрите, концептуально нет никакой разницы между оператором и функцией; оператор — это частный случай функции. В функциях вы типы входящих значений не знаете, а просто подставляете то, что пришло снаружи, и проверяте, сходится ли. Но ваша сходимость, в итоге, основана на тех же функциях, просто на уровень ниже. Как вы будете решать эту рекурсивную проблему?


                    1. Lex20
                      03.09.2016 17:38

                      Задам операторы для стандартных типов.


                      1. lair
                        03.09.2016 17:49
                        +3

                        Указав типы, не так ли?


                        1. Lex20
                          03.09.2016 18:59
                          -1

                          Не вижу проблемы.


                          1. lair
                            03.09.2016 19:17
                            +1

                            Да и нет никакой проблемы, кроме того, что теперь у вас есть функции, тип которых не выводится, а указывается.


                          1. andybelo
                            04.09.2016 11:01
                            -2

                            Не беседуйте с троллем. Он только минусует. Мне он доказывал прямо противоположное. Дяде просто скучно, он удовлетворяет свои садистские потребности. Надо сказать, что бы его выпилили с Хабра. Благодаря таким аудитория Хабра снижается быстрее, чем растёт. Дело не в его знаниях, он никого ни чему не учит. Имея карму выше, он может нести любую пургу и минусовать до опупения. Надо менять правила, иначе Хабр не то что вымирает, он выхолащивается. Таких надо из зенитки, как в С. Корее.


                            1. lair
                              04.09.2016 13:59
                              +1

                              Мне он доказывал прямо противоположное.

                              Оу, правда? И где же?


  1. impwx
    02.09.2016 11:50
    +1

    Из этих соображений был сделан вывод, что отказаться от типов-хорошая идея.

    А как теперь быть с перегруженными функциями, которые по-разному обрабатывают аргументы разных типов?

    При полном удалении типов только выигрывает в читаемости:

    Не выигрывает. Теперь, чтобы определить, что можно передать в функцию, придется попотеть. Из вашего же примера, что именно принимает qsort в качестве значения для аргумента compare? Какой объем кода придется проанализировать человеку \ среде разработки, чтобы это определить?


  1. claygod
    02.09.2016 12:24
    +1

    На основании всего написанного я, автор статьи, пишу язык программирования

    Сейчас уже столько языков существует… может Вам присоединиться к какому-нибудь
    сообществу языка программирования, наиболее близкого по духу?


    1. NeoCode
      02.09.2016 18:01

      Как бы то ни было, но я всегда уважаю разработчиков собственных языков программирования. Можно конечно и присоединиться, но и своя собственная разработка это очень хорошо. Может потом наоборот к автору присоединятся:)


  1. NeoCode
    02.09.2016 12:24
    +4

    Мне не нравится динамическая типизация и идея отказаться от типов. Вот в динамической типизации обычно можно написать такое

    x = 10;
    if(condition)
      x = "Alexey";

    Но скажите — кому может понадобиться на ходу менять тип переменной с числа на строку? Если х — число, то оно скорее всего используется в математике, возможно как счетчик цикла или индекс. Если х — строка, то это работа с текстом, строковые алгоритмы, может быть имена файлов и т.д.
    И это только простые типы. А если х — навороченная структура?
    Поэтому когда я в коде вижу
    foo(x,y,z) { }

    то что я должен ожидать от x, y и z? Какие действия я имею право с ними совершать?
    А если написано
    foo(int x, string y, File z) { }

    то все становится гораздо понятнее. И поиском можно найти типы, и погуглить по ним информацию (если они из стандартных библиотек), и случайно не станешь умножать файл на строку чтобы получить число.


    1. Lex20
      02.09.2016 17:36

      Если функция foo работает только с такими аргументами, то

      foo(int_x, string_y, file_z) { }
      


      1. NeoCode
        02.09.2016 18:00
        +3

        А это костыль вместо естественного механизма типов (как и всякие специальные комментарии для php). В сложном коде ничто не помешает программисту случайно сохранить где-нибудь значение не того типа, которое в конечном итоге будет передано в эту функцию, и все сломает.
        Прелесть статической типизации именно в ее декларативности, в том что ошибки выявляются не во время выполнения (если ветвь кода редко выполняется, то ошибка может не проявляться годами), а во время компиляции, где программа — просто дерево, и компилятор гарантированно пройдет по каждой ветке этого дерева.


    1. andybelo
      03.09.2016 15:58

      Как ни странно, если у вас заранее неизвестен ответ, вы не можете утверждать, что он будет типа int, long или double long или ещё длиннее, то языки типа С++ вам не подойдут, придётся использовать язык с динамической компоновкой. Особенно это касается баз данных. Например, автор книги может быть от одного до… скольки? Слово в словаре может иметь какую максимальную длину? Или длина сообщения в канале связи? или размер рисунка и т.д. Большинство обычных задач с базами данных требует вычислений типов, и динамических языков. У русских фамилия, имя и отчество, ФИО. У многих народов с этим гораздо круче. Или какова длина названия книги, лекарства, вещества, города, административной единицы?


      1. lair
        03.09.2016 16:21
        +2

        Как ни странно, если у вас заранее неизвестен ответ, вы не можете утверждать, что он будет типа int, long или double long или ещё длиннее, то языки типа С++ вам не подойдут, придётся использовать язык с динамической компоновкой.

        Зачем? Достаточно иметь числовой тип данных с произвольной точностью.


        Слово в словаре может иметь какую максимальную длину?

        Определенную требованиями, очевидно.


        Большинство обычных задач с базами данных требует вычислений типов, и динамических языков.

        Зачем для работы с БД динамически типизованный язык? Я вот прекрасно работаю с БД из статически типизованных языков (да и пишут БД регулярно на статически типизованных языках).


        1. andybelo
          03.09.2016 17:55

          Если вы делаете базу товаров, сколько характеристик товара в базе и есть в базе ли картинки и какого они размера?


          1. lair
            03.09.2016 18:09

            сколько характеристик товара в базе

            Столько, сколько указано в требованиях. Если требования говорят, что атрибуты должны меняться без модификации таблиц, то строим EAV или аналоги.


            есть в базе ли картинки и какого они размера?

            Если по требованиям нужны, то есть. Размер тоже определен требованиями.


            1. andybelo
              03.09.2016 20:16

              Мы ж не протребования, мы про типы объектов и их размер. Для носков характеристики имеют другой тип, чем для характеристик смартфонов
              «Достаточно иметь числовой тип данных с произвольной точностью.» — Ну если у вас есть терабайт ОЗУ. И кстати, как вы запишите число ПИ в числовом типе данных с произвольной точностью?


              1. lair
                03.09.2016 20:31

                Для носков характеристики имеют другой тип, чем для характеристик смартфонов

                Правда? Я думаю, что все характеристики носков сводимы к четыре типам — строке, перечислению, числу и ссылке. У смартфонов приблизительно то же самое. Добавите структурные типы — вообще все покроется.


                Ну если у вас есть терабайт ОЗУ.

                Зачем же?


                И кстати, как вы запишите число ПИ в числовом типе данных с произвольной точностью?

                Так, как нужно для решения задачи.


                1. andybelo
                  03.09.2016 22:33
                  -2

                  «Я думаю, что все характеристики носков сводимы» -ага, значит таки типы разные у характеристик носков и смартфонов. Спасибо.
                  «к четыре типам — строке, перечислению, числу и ссылке.» — ну так в итоге всё сводится к байтам и битам. И?
                  «терабайт ОЗУ.» будет заполнен в основном нулями, при использовании реляционных баз с полями постоянной длины. А иначе у вас будет быстродействие низкое, заказчик будет недоволен.
                  «как вы запишите число ПИ в числовом типе данных с произвольной точностью?»
                  «как нужно для решения задачи.» Да вы просто тролль?


                  1. lair
                    03.09.2016 22:47
                    +1

                    ага, значит таки типы разные у характеристик носков и смартфонов.

                    Разные, конечно. Вообще, в статически типизированных языках типы разные (иначе нет смысла в статической типизации). И что?


                    будет заполнен в основном нулями, при использовании реляционных баз с полями постоянной длины.

                    Оу, а откуда взялись поля постоянной длины?


                    А иначе у вас будет быстродействие низкое, заказчик будет недоволен.

                    Ну, если заказчику и правда нужна арифметика с произвольной точностью, то с быстродействием придется мириться. Хочет быстрее — придется ограничивать точность.


                    Что характерно, ни один из ваших вопросов не содержит ответа на мое "зачем для работы с БД динамически типизованный язык?". Более того, все описанные ответы равноприменимы для статически и динамически типизированных языков.


                    1. andybelo
                      04.09.2016 09:57
                      -1

                      Я никогда не даю ответов тем, кто меня минусует. Досвиданья.


                      1. lair
                        04.09.2016 10:09

                        Ну да, в обиженную позу-то легче встать, чем свои слова обосновывать.


    1. playermet
      03.09.2016 17:17

      > А если написано
      Неправильный пример. Ответом на
      «foo(x,y,z) {}»
      является
      «foo(type1 x, type2 y, type3 z) {}»
      И как видно это не очень помогло, нам все равно нужно совершать дополнительные действия. Если бы у переменных были нормальные имена, было бы намного проще.


      1. NeoCode
        03.09.2016 20:33

        Пусть даже такой пример. Но если я в теле функции напишу

        x = y + z

        а это невозможно, потому что не существует понятия сложения для типов type2 и type3, ошибка возникнет сразу же, на этапе компиляции, а не неизвестно когда на этапе выполнения в продакшене.


        1. playermet
          03.09.2016 22:07

          Пример о котором мы сейчас говорим, это не столько динамическая/статическая, сколько явная/неявная типизация. На самом деле, ничто не мешает создать инструмент, выводящий и проверяющий типы статически, до выполнения. Проблемы возникнут только в случаях, где переменная получает значение из «черного ящика».


          1. NeoCode
            04.09.2016 10:00

            Ну так вот, все такие случаи когда переменная получает значение из «черного ящика», лучше бы обозначить в коде явно, с помощью специального динамического типа any (в чем-то по аналогии с ключевым словом unsafe, существующим в некоторых языках для ограждения небезопасного кода).
            Против неявной типизации и вывода типов я ничего не имею — поскольку тип выводится на этапе компиляции, потенциальные ошибки также выведутся на этапе компиляции.
            PS. интересно, если в языке есть и тип «any», и вывод типов, который в сложных случаях вполне может вывести «any» и ничего не сказать программисту… но это уже тонкости реализации компилятора. Как минимум warning я бы на такой случай давал.


            1. playermet
              12.09.2016 14:41

              > лучше бы обозначить в коде явно, с помощью специального динамического типа any
              Если бы я делал язык, я бы обошел это по другому. Что-то вроде конструкции похожей на switch, но для типов, с обязательной other/default секцией. Пока тип переменной неизвестен, она не может участвовать нигде кроме как в этой конструкции, а дальше на все предусмотренные варианты есть гарантированная логика с наверняка известными типами.

              P.S. Все хочу запилить статический анализатор типов для Lua, но никак не соберусь.


  1. Eivind
    02.09.2016 14:06
    +7

    Минусы: больший объём памяти, необходимый для выполнения.

    А теперь я предлагаю вам подтвердить своё утверждение результатами тестов, где ваш сравниваемый код на javascript требует для выполнения меньше памяти.


  1. Dark_Daiver
    02.09.2016 18:26

    А что мешает просто воткнуть auto в С++ коде везде где это возможно? И не нужно писать велосипеды.


    1. Lex20
      02.09.2016 20:21
      -1

      auto можно вообще не писать, об этом статья.


      1. Antervis
        05.09.2016 06:17

        нет. Синтаксис объявления переменной отличается от синтаксиса её использования. Вдруг я хотел не объявить локальную переменную, а воспользоваться экспортированной из либы?


  1. babylon
    04.09.2016 19:43
    -1

    Бессмысленная статья и такие же комментарии к ней.


  1. Antervis
    05.09.2016 06:22

    статическая типизиация позволяет сэкономить примерно вдвое, т.к. не надо проверять тип на каждом шаге использования переменной. Ничто в принципе не мешает написать оболочку над variant/any и пользоваться той же самой динамической типизацией. Просто от введения её туда, где не надо, проиграет только код.


  1. fynivx
    05.09.2016 14:39
    +1

    Мне кажется, прежде чем пытаясь заимплементить вывод типов, нужно познакомиться с каким нибудь функциональным языком. В C++, C# и иже с ними, даже в современном виде, вывод типов очень скуден. Я бы посоветовал посмотреть на Haskell. По сравнению с указанными выше языками он просто чудеса творит в смысле вывода типов.