Pull to refresh

Comments 441

UFO just landed and posted this here

Да, интересно и раскрыта объемная тема в простых словах. Но для меня главный вопрос — куда делась предыдущая статься про F#?

Забрал переписывать, что бы донести до вас то же самое посильнее
Это навело меня на мысль, что весь вопрос в комбинации этих параметров.

Угу. Хотя можно представить себе язык, отдающий эти решения программисту:

type A = { name: string } // структурный тип, вместо него можно передать экземпляр B
nominal type B = { name: string } // номинальный тип, вместо него нельзя передать A

Насколько программист будет рад каждый раз делать этот выбор — неясно.
Насколько я помню, так есть в OCaml. Есть специальные структуры данных, которые номинативные, а есть структурные

Уточню. В окамле несколько концептуально разных механизмов, при этом заметно пересекающихся по возможностям. Есть записи (records), и они номинативно типизированы, несмотря на то что выглядят как структуры: https://ideone.com/8mQqs7. Есть объекты с методами, и они типизированы структурно: https://ideone.com/GS3bVF. А ещё есть модули, у которых есть свои вроде-типы (сигнатуры) и вроде-функции (функторы), и они тоже типизированы структурно: https://ideone.com/TBiz1F.

К слову говоря, Python позволяет так делать. Можно использовать аналог номинального типа Protocol, который проверяет объект на соответствие методам, атрибутам. А можно использовать обычный класс или тип, который ведёт себя классическим образом.


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


Но вообще спасибо за статью, понял глубину задумки аннотации типов Python. Какая мощная штука оказалась.

UFO just landed and posted this here
А где я написал, что они мне не угодили?
UFO just landed and posted this here
Так. Это все верно конечно, но как это противоречит тому что написал я?

Но где написано, что это противоречит? :)

Прикольная у вас рекурсия получилась.

в C# нельзя сделать вторую часть, а именно структурную типизацию(наличие поля/метода).
Я одно время даже думал из-за этого писать на F# немного, так как там я могу написать универсальный тип Vector.

UFO just landed and posted this here

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


Вон ниже, как раз пример привели

В C# получается как то так:
public class A : IA 
{
  public string id { get; set; }
}

public class B : IA
{
  public string id { get; set; }
  public string name { get; set; }
}

public interface IA
{
  public string a { get; set; }
}

public void test(IA a) {}

1)Туда входят только методы и свойства, поля и статические методы(в том числе операторы) не могут входить.
2)В сторонний класс реализацию интерфейса не запихнуть.


Этот комментарий показывает примеры чего шарп не может.

Но тогда в классе должна явно быть заявлена реализация интерфейса. Т.е., уже существующий сторонний класс, соответствующий нашему интерфейсу, но не объявленный как реализующий его, мы использовать не сможем.
UFO just landed and posted this here
В Go, если не ошибаюсь, как раз такая(на мой взгляд, довольно удобная) реализация интерфейсов — структура считается реализующей интерфейс, если у неё есть метод\методы соответствующие сигнатурам интерфейса, т.е. явное объявление не нужно. При этом, сами структуры номинативные.

Тут принципиальна не неявность реализации интерфейса, а отделение описания типа от реализации интерфейса для типа. И Go в этом плане не оригинален: такое же разделение, но с явной реализацией, есть в Rust, Swift, Scala, Ocaml, Haskell и древнем StandardML — и это только те, о которых мне известно.

Хм, всё больше желания потыкать Rust.

Что-то вроде такой ситуации?


interface Shape {
int area();
}
class Square: Shape { // тут интерфейс
  int area(){ /*impl*/}
}
class Circle { // а тут нет интерфейса
  int area(){ /*impl*/}
}

Да, при этом class Circle у вас где-то в libcircles.dll и запатчить туда свой интерфейс нет возможности.

а чем вам generic классы не угодили?

тем что в C# нельзя написать такой метод
static T Sum<T>(T x, T y): where T: operator (+) => x + y;
который бы работал для любых типов, у которых объявлен статический оператор сложения.


А в F# можно

UFO just landed and posted this here
UFO just landed and posted this here

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


static string GetId<T>(T entity)`
    : where T: string Id { get; } => 
    entity.Id;
Через рефлексию можно и в сишарпе.

Это можно сразу исключить. Рефлексия не является zero-cost abstraction и она не проверяется статически.


В фшарпе такая функция неотличима в коде от вызова проперти (то есть zero-cost) и в неё нельзя сунуть тип, у которого нет такого свойства (т.к. это ограничение на тип).

Код, который пользуется операторами, я пишу часто. Вспомните: те же DateTime и TimeSpan перегружают арифметические операторы.

Кстати уже давно висит вот такой feature request в C#: Exploration: Shapes and Extensions. С таким нововедением ваш Sum тоже можно будет писать на C#, правда с лишними телодвижениями. Раньше проскакивала информация что это планируют добавить в C# 9.0, но к сожалению на данный момент это не в списке candidates.

См. ниже. При желании всегда можно написать руками, и выглядит не очень ужасно. Что до автоматического генерирования структурных тайпклассов — ну… Не знаю, насколько это хорошая идея. Мне кажется, что это не очень полезно.

Есть классический способ энкодить тайпклассы в сишарпе:


void Main()
{
    var numbers = new[] { 1, 2, 3, 4, 5 };
    var strings = new[] {"Hello", " ", "World" , "!"};
    Console.WriteLine(MConcat<int, IntSGroup>(numbers));
    Console.WriteLine(MConcat<string, StringSGroup>(strings));
}

public interface SGroup<T> // наш тайпкласс
{
    public T Zero { get; }
    public T Add(T x, T y);
}

public struct IntSGroup : SGroup<int> // реализации для наших типов - привет, имплиситы
{
    public int Zero => 0;
    public int Add(int x, int y) => x + y;
}

public struct StringSGroup : SGroup<String>
{
    public String Zero => "";
    public string Add(string x, string y) => x + y;
}

// пример абстрактного кода, работающего с тайпклассами
public static T MConcat<T, TGroup>(IEnumerable<T> items) where TGroup : struct, SGroup<T> {
    var typeclass = default(TGroup);
    return items.Aggregate(typeclass.Zero, typeclass.Add);
}

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

UFO just landed and posted this here
Нет, конечно. Чем меньше притащено в рантайм, тем быстрее всё работает. Да и если вы типы проверили, то зачем вам эта информация в рантайме?


Тащить их всегда — да, наверное не нужно. Но они ведь не только для проверок используются.
Например паттерн матчинг по типам — он же не заработает без метаинформации. Понятно, что компилятор может протащить инфу только для тех типов, на которых используется этот паттерн матичнг, и наверное, это и есть правильный путь
UFO just landed and posted this here
Я просил пример паттерн матичнга по типам, который сможет работать без информации о типах

Насколько я понимаю, хаскеллевские алгебраические типы компилируются во что-то подобное. 0xd34df00d, вероятно, не считает kind информацией о типе

UFO just landed and posted this here
А в каком языке (кроме Idris 2) вы это можете делать?

C#

UFO just landed and posted this here
public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}


Пример отсюда
UFO just landed and posted this here
Согласен: если речь идёт о функции, которая принимает object и сверяет его тип на соответствие конкретным типам, такой код явно пахнет.

Но в случае, например, Union-типов, когда на входе у нас, например, `Customer | Error`, можно обработать ошибочный сценарий в функциональном стиле при помощи паттерн-матчинга. Эх, вот бы наконец завезли в C# Union-типы…
Кстати, в фп япах то обычно нет юнионов — там размеченные юнионы, т.е.

type A = B of int | C of int

и собственно вот этот размеченный лейбл и едет в рантайм. И по нему же, наверное, паттерн матчинг и работает
Дык собственно в «серьёзных языках» изначально были именно тегированные Union'ы. В том же ALGOL, Pascal (в том, что Вирт придумал, конечно, а не в том, что Хейлсберг реализовал).

В Fortran, впрочем, были COMMON-блоки, которые позволяли калбмур типизации делать.

А потом — случилась революция.

Движение в сторону строгости и меньшего числа ошибок, фактически, прекратилось — и вместо этого задачу борьбы с ошибками возложили почти всю на программиста.

А сейчас — вообще период двоемыслия. Когда все менеджеры «бьют себя пяткой в грудь» и публично объясняют что борьба с ошибками — это очень важно и нужно… а потом устраивают общения с разработчиками один-на-один и пытаются сделать так, чтобы «таски» как можно быстрее закрывались. «А если ошибки возникнут — так мы их потом пофиксим».
UFO just landed and posted this here
Эх, вот бы наконец завезли в C# Union-типы…

Переходите на PHP — к нам завозят в этом году :)

Так, а теперь можно подлиться мнением по прошествию времени.
ФП-языки очень боятся давать возможность делать паттерн-матчинг по типам, потому что это ломает параметричность

Нет, не поэтому. Просто для паттерн-матчинга по типам нужно для начала ввести в язык подтипы и полиморфизм подтипов.


А параметричность вы сами ломали когда делали Has на дженериках, и никто из-за этого не умер...

UFO just landed and posted this here

Ну, я писал про паттерн-матчинг переменной по типу, а не типа по типу. Если начать считать вторые случаи — то в список языков с паттерн-матчингом по типам надо внести С++ (специализация шаблонов работает как паттерн-матчинг), Rust (реализации трейтов) и Haskell (инстансы тайпклассов, ограниченно). Даже странно, что вы не припомнили ни одного из этих языков :-)




Эм, почему же? Это всего лишь рефлексия. Рантайм-поведение не отличается от того, как если бы я писал все нужные инстансы руками.

Отличается. Если я ничего не перепутал, то можно взять для примера вот такую функцию:


foo :: a => (a, Bar) -> Bar
foo = extract

Так вот, для любого a кроме некоторых странных случаев она будет возвращать второй элемент пары, но для пары (Bar, Bar) она вернёт первый. И это далеко не все возможные варианты...

UFO just landed and posted this here
Внутри функции при этом вы паттерн-матчиться на конкретный инстанс тайпкласса не можете.
Погодите. Вот тут же написано что можете:
char x = case cast x of
           Just (x :: Char) -> show x
           Nothing -> "unknown"

Опять же, там матан за пределами моих знаний, но моя ментальная модель такая, что всё ломается, когда вы внутри функции можете сделать паттерн-матчинг по типу.
Ментальная модель ломается — но код-то работает!
UFO just landed and posted this here
Но тогда вы всю непараметричность запихиваете в выбор словаря для инстанса Stupid. А этот выбор в итоге в том или ином виде происходит статически, и вы могли бы аналогично без тайпклассов передать нужную функцию в той самой точке.

Ха-ха:


notId :: a => a -> a
notId = stupidId

Ну да, вроде и статически реализация выбирается, но это не помогает: все "бесплатные теоремы" похерились точно так же, как могли похериться при паттерн-матчинге по типу.


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

Параметричность ломается не только при паттерн-матчинге по типу, это всего лишь самый простой способ её сломать.


Ещё она ломается при:


  • использовании Overlapping instances
  • использовании Type families (именно так вами была сломана параметричность у extract)

В общем, она ломается при любом паттерн-матчинге по типу, вовсе не обязательно внутри функции.

UFO just landed and posted this here
Не имеете права. Надо написать notId :: Stupid a => a -> a. И вот вам ваш словарь.

Неа, не надо. Инстанс instance Stupid a даёт право не указывать такое ограничение, только что проверил.


Это всего лишь вычисления на типах, как они могут повлиять на рантайм-поведение в этом смысле?

Нарушают "регулярность" системы типов языка. Точно не скажу (теперь уже мне матана не хватает).

UFO just landed and posted this here
UFO just landed and posted this here

Э-э-э, а как вы без IncoherentInstances будете использовать Overlapping instances?

UFO just landed and posted this here
UFO just landed and posted this here
Получилось ли у вас?

Неа, я же не проверял как оно в рантайме.


Но если тут нарушается принцип "скомпилировалось -> работает" — это ж ещё хуже, разве нет?

UFO just landed and posted this here
А в восьмой версии языка добавили и switch expressions:

static string Display(object o)
{
    return o switch
    {
        Point p when p.X == 0 && p.Y == 0 => "origin",
        Point p                           => $"({p.X}, {p.Y})",
        _                                 => "unknown"
    };
}


Источник

Можно убрать return и фигурные скобочки


static string Display(object o) =>
    o switch
    {
        Point p when p.X == 0 && p.Y == 0 => "origin",
        Point p                           => $"({p.X}, {p.Y})",
        _                                 => "unknown"
    };
паттен-матчинг по типам?
А в каком языке (кроме Idris 2) вы это можете делать?

Go например.
не так изящно как сигнатурами функций, но вполне рабочий вариант

Такой «паттерн-матчинг» по типам есть много где, где можно узнать тип в рантайме (любой динамический с номинативной типизацией и многие статитические ЯП). switch по метке типа и вперёд. Даже в вашем нике такой ЯП.
Перегрузка в сигнатурах функций — тоже неизящный, ad-hoc полиморфизм.
Стоп, вы про паттен-матчинг по типам? А в каком языке (кроме Idris 2) вы это можете делать?

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

UFO just landed and posted this here

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

UFO just landed and posted this here
Но я всё равно же матчусь по элементам t: T, где T — объединение.

Или, что одно и то же, по типам Ai, где T = A1 U A2 U… U An. Нельзя отличить эти два кейза, это просто два описания одного и того же.

Например так:

trait PatternProducer {
    fn pattern(&self) -> &'static str;
}

struct Class {
    producer_fn: Box<dyn Fn() -> &'static str>
}

impl Class {
    pub fn new(pattern: &'static str) -> Self {
        Class {
            producer_fn: Box::new(move || pattern)
        }
    }
}

impl PatternProducer for Class {
    fn pattern(&self) -> &'static str {
        (self.producer_fn)()
    }
}

fn print_pattern(class: &dyn PatternProducer) {
    match class.pattern() {
        "a" => println!("Match A"),
        "b" => println!("Match B"),
        _ => println!("Unknown pattern")
    }
}

fn main() {
    print_pattern(&Class::new("a"));
    print_pattern(&Class::new("b"));
    print_pattern(&Class::new("c"));
}


На самом деле, информация о типах — это такие же данные, как и все остальное.
На эти данные навешен какой-то функционал в рантайме, который поддерживается языком.

Если у Вас есть Тьюринг-полный язык, вы можете реализовать свою «типизацию». Обычно это легко сделать в рантайме, в компайл тайме не всегда.
Если говорить, об информации о типе, то ApeCoder говорит правильно. В сях информация о юнионе стирается. Поэтому всегда хранится тип данных в куске памяти, выделенной под юнион. Матчите тип данных, и интерпретируете кусок памяти как тип, который вам нужен. Думаю, не нужно говорить, что хранить тип данных можно самыми незаурядными способами.
  1. это не матчинг по типам
  2. это уже реализовано для вас в трейте Any.
1. Почему нет? Любой тип может возвращать константное значение паттерна.
2. Я не против

Матчинг по типам это когда я могу сделать:


fn foo<T>() -> String{
   let ty = T;
   match ty {
      i32 -> "This is int",
      f64 -> "This is double",
      _ -> "This is something else";
   }
}

Впрочем, матчиться непосредственно по типам (даже в языках которые теоретически это разрешают) все равно плохая идея.

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

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

P.S. Я допускаю, что теоркатщики могут себе придумывать проблемы с параметризированностью типов, но на практике, паттерн матчинг делает код чище. Достаточно пописать на Typescript с его кастрированным паттерн матчингом и Rust/ML/Ваш вариант, и увидеть насколько все проще выглядит на практике. Я уже не говорю про С с union/enum парами.

Речь не про паттерн-матчинг, а матчинг по типам. Это нарушает параметричность, отсюда много всякого плохого следует.


Речь про то, что таким образом вы никак не поматчитесь по типам про которые ничего не знаете, да ещё и руками пишете.


В сишарпе, к слову, можно. Не считаю это плюсом.

UFO just landed and posted this here
О, Коннор МакБрайд :)

Круто, а я вот по никнеймам не угадываю что за люди передо мной, не так много всех знаю.


Я там ниже приводил пример функции, которая тайпчекается Idris 2 (который реализует QTT):

Я пока на первом сижу, мб к концу года перейду на 2 (но скорее всего — нет, т.к. после книги по идрису начну читать что-то про ремонт :) )


В этой функции вы можете матчиться на n. Но если вы уберёте {n: Nat}, то всё, нельзя, это рантайм-иррелевантная информация.

Прикольно, да. И не надо туда-сюда конвертеры писать, и параметричность вроде сохранили.

UFO just landed and posted this here

А этот инт логически не будет информацией о типе?

UFO just landed and posted this here
«row polymorphism» — это records из Elm? Всплывающее в поиске не очень доходчиво…
UFO just landed and posted this here
Есть такая тема, как row polymorphism, и это очень клёвая тема. Не знаю, есть ли она в F# (я не знаю F#), но в этом несчастном хаскеле её очень не хватает.
Расскажите, пожалуйста, почему raw polymorphism не хватает? Часто встричаю утверждение что его не хватает в Haskell, но почему не объясняется. Разница между ad-hoc и raw в моём текущем представление только в том что в первом нужно генерировать больше бинароного кода, во втором нужно делать виртуальные таблицы методов, и как следствие делать некую рефлексию, при этом не понятно какие возможности по верх добавит второй?

Динамическая диспетчеризация (если я верно понял суть row polymorphism) обычно используются там, где писать ad-hoc реализацию слишком многословно или когда tagged union построить невозможно ввиду особенностей архитектуры проекта. Или там, где система типов не очень богатая (Java, C# и пр.).


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

UFO just landed and posted this here
> В условном С вы никак не можете статически гарантировать, что обращение по указателю имеет смысл, и указатель не висячий, например, и это тоже признак слабой системы типов.

Зато это можно гарантировать в ATS [1] [2], который скомпилится в нужный C и всё будет безопасно и быстро [3] :)

[1] ats-lang.sourceforge.net/htdocs-old/TUTORIAL/contents/tutorial_all.html#pointers
[2] bluishcoder.co.nz/2013/01/25/an-introduction-to-pointers-in-ats.html
[3] blog.vmchale.com/article/ats-performance
UFO just landed and posted this here
Ну это все же номинативная по сути типизация. Потому что два одинаковых интерфейса с разным именем — это разные интерфейсы. Хотя да, гибкость, которая из коробки есть в структурных япах, в сишарпе достигается именно интерфейсами
UFO just landed and posted this here
О том и речь. Что любой ЯП будет очень сложно описать бинарными терминами вроде «номинативная типизация»
UFO just landed and posted this here
Но вот в описании япов обычно пишут, C# — язык со строгой статической типизацией. Что по-хорошему не совсем так, а иногда и совсем не так

вероятно явную динамическую типизацию причисляют к статике.
В шарпе все же надо явно объявить dynamic, а не оно само.

Это да. Но вот сильной типизацию в C# назвать сложно.
Да чёрт с ней, с типизацией! Мне вот пытались как-то объяснить как битность процессора «посчитать». Ну вот почему 8080й — это 8-битный процессор, а 8086 — уже 16битный? И там и там есть операции с 16-битными числами, и там и там можно складывать две «половинки» 16-битного регистра и переслать, скажем, нижнюю половинку 4го в вернюю половинку 3го… и даже шина данных у 8088 8-битная! В чём разница-то?

Вначале дискуссия крутилась вокруг внутренней реализации (типа если 6502 увеличивает счётчик команд за два такта, «в два прохода» — то это однознано 8-битный процессор, а если 65816 — этого не требует, то это уже 16-битный процессор), но потом, разумеется, Prescott все карты спутал.

В итоге сошлись на том, что если люди называют какой-то процессор 8-битным — то это 8-битный процессор, а если 64-битным, то 64-битный…

Ну зашибись просто! А это, вроде как, просто о чиселке идёт речь! Его, вроде как, можно бы как-то из наблюдаемой под микроскопом картинки извлечь!

А вот нифига.

А вы хотите, чтобы вам чёткие критерии для классификации языков кто-то дал…

А разве битность процессора — это не про адресацию? Т.е. размер указателя на объект из кучи.

Если учесть, что почти все известные науке восьмибитные процессоры используют 16-битный указатель (правда у 8008 он 14 битный)? Нет, вряд ли.

Да даже и у современного x86-64 ведь адрес 48-битный (до Ice Lake, у этих он, прости господи, 57-битный).

Чем, кстати, некоторые нехорошие люди пользуются.

Собственно беда-то вся та же, что и с типизацией: мы хотим одним словом охватить несколько разных объектов (размер РОН'а, размер адресной шины, размер шины данных и так далее).

А в реальном мире эти вещи, внезапно, имеют разный размер. И разные люди, в своих классификациях, выбирают «самым важным» разные вещи…

Если под гибкостью имеется в виду необходимость следовать НЕЯВНЫМ контрактам, то такая гибкость — нафиг не нужна. А нужна возможность извне указать, что один тип совместим с другим типом. Мэдс что то такое для шарпа тут https://youtu.be/WBos6gH-Opk?t=2757 описывает.

Нет, нельзя. Интерфейсы в C# — такие же номинативные типы.

Давайте для примера C++ возьмём. Это — вот как? С одной стороны такое вот законно:
struct A {
  int x;
};

struct B {
  int x;
};

int foo(A a) {
  return a.x + 3;
}

int foo(B b) {
  return b.x * 3;
}

auto result1 = foo(A{3}); // 6
auto result2 = foo(B{3}); // 9
С другой стороны такое вот:
auto result3 = foo(*static_cast<B*>(static_cast<void*>(&A{3}))); // 9
законно тоже.

И? Это номинативные типы или уже структурные?

Не вижу в вашем примере эмуляции структурной типизации через интерфейсы.

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

А еще есть compile time рефлексия или процедурные макросы, которые могут преобразовывать AST на этапе компиляции.
Это часто используется в языках типа Хаскеля и Раста, туда же можно записать лиспомакросы.


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

Посмотрите typescript-is, хорошая библиотека для рантайм-проверок на основе TS-интерфейсов.

Проблема в том, что есть много случаев, когда мне в коде требуются именно номинативные проверки. Например, у меня есть одинаковые по структуре сущности Customer и Client, и я хочу написать метод, который сохраняет клиента в базу.

Вроде есть некий костыль, который позволяет это делать в TypeScript, некоторые его ещё называют "branded types"
https://medium.com/better-programming/nominal-typescript-eee36e9432d2

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

оу, да, прошу прощения, пропустил этот абзац)

Моего понимания не хватает, что бы сказать, нужно ли тащить метаданные типов в рантайм. Я знаю, что тайпскрипт этого не делает, и я решал проблемы, которые из-за этого возникают. Сишарп это делает, и это тоже вызывает проблемы — но я не знаю, были бы у меня такие проблемы, если бы сишарп при этом был структурно типизированным.

Scala позволяет оба варианта, и мне это кажется близким к идеалу. По-умолчанию типы дженериков стираются, но при необходимости можно указать, что требуется ClassTag, и компилятор его предоставит.

Есть языки программирования, которые потащат в рантайм метаинформацию этого типа.

Думаю, это называется рефлексией


Получается, что если я описал тип User, и получил список таких юзеров из JSON, мне придётся руками перечислять все свойства, и делать все проверки. Это объективно — говно.
Теоретически, мы можем научить компилятор тайпскрипта генерить нам рантайм проверки для определенных типов, и не тащить метаданные всех типов в рантайм — это было бы даже круче.

Не уверен на 100%, но кажется эта статья может понять куда дальше двигаться, если хочется прокопать это направление
http://blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-4

Есть языки программирования, которые потащат в рантайм метаинформацию этого типа.
Думаю, это называется рефлексией
Нет, не называется. Метаинформацию в рантайме в C++ вы получить можете (если RTTI не выключите), а вот рефлексии там нет. Хотя есть Magic Get, позволяющий узнать какие у структуры поля и каких типов и Magic Enum, позволяющий узнать названия элементов Enum.

При этом то же самое, но уже без хитрых трюков — собираются добавить в C++23… и это уже будет называться рефлексией…
Нет, не называется. Метаинформацию в рантайме в C++ вы получить можете (если RTTI не выключите), а вот рефлексии там нет.

Хм, интересно… А что ещё из обязательного включается в понятие рефлексии, что получение метаинформации о типах через RTTI нельзя ей назвать?

Тут как и с типами. Единого, твёрдо устоявшегося определения нет. Но можно глянуть что Wikipedia пишет: Рефлексия может использоваться для наблюдения и изменения программы во время выполнения. Рефлексивный компонент программы может наблюдать за выполнением определённого участка кода и изменять себя для достижения желаемой цели. Модификация выполняется во время выполнения программы путём динамического изменения кода.

Вот наблюдать за чем-то можно (но очень ограниченно), а вот «модифицировать себя» — тут напряг. Даже в компайл-тайм нельзя, не говоря про рантайм. Вся поддержка рефлексии, фактически, нужна в C++98, чтобы dynamic_cast работал. Это, как бы сказать, очень ограниченный вариант.

Насколько я понял из https://en.wikipedia.org/wiki/Type_theory нет какой-то единственной и универсальной системы типов, а соответственно и сгруппированных по этой классификации языков программирования

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


List<String>
List<Int>

друг от друга (привет Java).
Опять же, что касается интерфейсов, то помимо традиционной схемы с наследованием есть языки, где можно реализовывать интерфейсы для произвольных типов, в том числе чужих. Привет Haskell type class.
Но некоторым языкам и такого метапрограммирования мало, они вводят еще и макросы, которые бывают как гигиеническими, так и нет.
Получается, что в такой вот общей классификации было бы ну дохрена различных пунктов.

Интересно почему метод падал на Customer и Client, если они структурно одинаковые?


Ну а так заценил преимущество структурной типизации работая с graphql.


Долго пытался протаскивать типы из кверей, а потом понял, что если я в UI компоненте отображаю id, name и value — то надо просто написать что этому UI компоненту нужна сущность с id, name и value — а уже тайпскрипт проверит, что туда спускается из квери соответствующий объект.

Интересно почему метод падал на Customer и Client, если они структурно одинаковые?


Имел ввиду, что метод как раз не упал — произошёл более плохой вариант, когда у нас сущность не в ту таблицу записалась

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

Тоже от ЯП зависит. В номинативном фарше можно наоборот

Номинальная и структурная типизация могут уживаться в одном языке. Например во flow вы можете задавать их явным образом https://flow.org/en/docs/lang/nominal-structural/ .


В typescript преимущественно используется структурная типизация, однако есть несколько специальных случаев, когда типы начинают вести себя как номинальные. Это выглядит скорее как изъян в дизайне, чем осмысленное решение. История давняя, если интересно, следите за https://github.com/Microsoft/Typescript/issues/202 .

Мне кажется вы часто бросаетесь в какие-то крайности и противопоставления. Возможно это такой метод познания мира или авторский стиль. Но когда тема не является полярной, а содержит непрерывный спектр взаимосвязанных элементов, ваш подход скорее всего даёт неправильную картинку.


Система типов это не священная корова, а чисто утилитарный инструмент для проверки программы на наличие логических ошибок. Как существует великое множество различных логик со своими законами, так и дизайн каждой из таких систем отражает видение авторов конкретного языка программирования и является следствием каких-то решений и компромиссов. Закономеро, что в разных языках они отличаются. В пределе, выразительные возможности упираются в теорему Райса https://en.m.wikipedia.org/wiki/Rice%27s_theorem. Поэтому не существует какой-то единственно правильной или однозначно лучшей системы. Расслабьтесь и пользуйтесь тем, что даёт язык, либо попробуйте придумать свой.

Ну как сказать, полярные штуки хороши тем, что поднимают дискуссию. А в этой статье я не вижу ничего полярного, и даже не ищу какой-то совершенной модели, просто пытаюсь разобраться в терминах
<code=typescript>function test(a: A) {}
в
<code=typescript>function test(a: NoExtraProperties<A>) {} (тип NoExtraProperties можно нагуглить или самому написать)
Причем теоретически, я могу заставить тайпскрипт валидировать полное соответствие — как тогда мне называть его модель типизации? Понятия не имею.
UFO just landed and posted this here
Ну кому как. Мне так наоборот хочется разделить все числа посильнее, чтобы метры с килограммами сложить было нельзя. Или чтобы id пользователя и id товара были разные типы, несмотря на то что оба номинально int (или string, неважно)

«Ритуальная шелуха» в современных статически типизированных языках существет только в интерфейсах методов и объектов. И в динамических языках она также существует, только занимает гораздо больше места (в виде проверок на правильность типов аргументов и/или тестов вида «что если передать объект неправильного типа»). В «проектах» на 20 строк кода такого, конечно, может и не быть.

Вот недавно была задачка. В проекте использовались 2d координаты (x, y) в одном домене. Потом понадобилось соединить их с другим доменом в котором использовались 3d координаты (x, y, z) причем «y» там означала высота, и правильная конвертация была такая: (x, y) => (x, 0, y) т.е. y переименовывался в z

Я могу только представить какая это была бы боль в структурно типизированном языке или вообще языке без типов. Но к счастью язык был статически типизируемым и все что понадобилось — это пара предоставляемых IDE рефакторов плюс обновить несколько математических функций.
UFO just landed and posted this here

метры вместо килограмм — редкая. Секунды вместо миллисекунд — уже почаще.


Строчки/числа пришедшие из враждебного внешнего мира должны на этапе парсинга валидироваться/конвертироваться, а не добираться до внутренней логики (так то там вообще null или объект может оказаться, сервис то сторонний, и если это не чекать то получится null island), и здесь как раз языки со статической типизацией сделают все что могут — либо сконвертируют, либо выдадут ошибку сразу при парсинге. Так то еще есть локаль, и в некоторых странах десятичный разделитель это точка, в других запятая, в третьих что-то типа апострофа. Тут никакие операторы уже не помогут если сразу корректно не сконвертить.


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


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

UFO just landed and posted this here

Что такое "вполне типизированный"? Как F# в котором есть поддержка единиц измерения? Или как C?

UFO just landed and posted this here
Если что — у си слабая типизация. Он почти как жабаскрипт, только c
Это как раз подтверждает, что реально нужно более сильное разделение между типами, чем просто операторы для строк и чисел. В Си оператора сложения строк вообще нет, но это не влечёт отсутствие (или почти отсутствие) ошибок, которые можно было бы легко решить типами.
UFO just landed and posted this here
В чём проблема-то вообще?
f(t, v0, a) = v0 * t + a * t^2 / 2
f(1u"s", 1u"m/s", 1u"m/s^2") # выдаёт 1.5 m
f(1u"s", 1u"m/s", 1u"m/s") # выдаёт ошибку
f(1u"s", 1u"kg/s", 1u"kg/s^2") # 1.5 kg

если последний пример не нравится (хотя он имеет физический смысл), можно ограничить чтобы обязательно получалось расстояние:
f(t, v0, a) = v0 * t + a * t^2 / 2 |> u"m"

Тогда с килограммами пример тоже ошибку даст.

Регулярно так пишу, и весьма помогает что здесь разные единицы измерений имеют разный тип. При записи в поле какой-нибудь структуры можно быть уверенным, что там будут именно нужные единицы; причём совместимые типа сантиметров и парсек автоматом переведутся между собой как нужно.
UFO just landed and posted this here

Да. Поэтому и мой комментарий:


Мне так наоборот хочется разделить все числа посильнее, чтобы метры с килограммами сложить было нельзя.
UFO just landed and posted this here

Упадет. Но по крайней мере в базе данных не окажется лицензионного соглашения в поле "широта" :)


Вообще-то все современные языки умеют приводить типы. И решать задачу "как получить int из json если там вдруг строка" должен автор библиотеки json сериализации (т.е. в 99% случаев — не пользователь библиотеки). Т.е. ситуация оказывается строго лучше — мы все также можем распарсить число пришедшее в json в виде строки, но только у нас теперь есть еще и дополнительные гарантии от компилятора.

Что-то не припомню, чтобы апологеты типов заявляли о ненужности тестов (а вот наоборот – бывало).

UFO just landed and posted this here

Он написал о ненужности юнит тестов, а не тестов вообще. Юнит тесты многие не любят

UFO just landed and posted this here
"многие не любят" — это что-то странное

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

UFO just landed and posted this here

Задавать окружение какого-то класса путём поднятия всей системы — не юнит-тест

UFO just landed and posted this here

Как по-мне, то и надо тестировать юнит, который мы тестируем, а не те, в которые он ходит. Моки как раз и позволяют протестировать как работает тестируемый юнит, правильно ли он ходит в соседний, правильно ли обрабатывает то, что он возвращает. А не тестировать как два юнита работают в связки.

UFO just landed and posted this here

веб-серверы — это про nginx или про приложение на каком-нибудь фреймворке?

UFO just landed and posted this here

Нормально я их тестирую юнит тестами только для одного юнита с помощью моков. И общение с операционной системой типа файлов или часов мокаю. На JS/TS или PHP

общение с операционной системой уже не замокать.

О, а я читал в конце апреля статью, в которой рассказывалось про то, как мокали общение с OS

UFO just landed and posted this here

Странная у вас оценка. Инфраструктура для моков создаётся один раз и переиспользуется многократно.

UFO just landed and posted this here
Если мок инициализируется перед каждым запуском теста, то мы заранее знаем, какими должны быть его вызовы и какими ответы в этом тесте. В чём тут сложность?
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
берём юнит, который содержит код, ходящий в http или в Database.
его тест — юниттест или не юниттест?

Юнит тест это размытое понятие. Что конкретно имел ввиду 0xd34df00d под ним — надо спросить у него.


"многие не любят" — это что-то странное.

Почему. Многие не любят острую пищу. Чем это странное понятие? В реальности такое считается.


Напоминаю, мы не обсуждаем нужны ли юнит тесты, мы обсуждаем считает ли 0xd34df00d что никаие автоматические тесты не нужны. По его посту очевидно, что он считает что какую-то часть тестов он считает избыточной при статической типизации а не то, что никакие тесты не нужны.

Юнит тест это размытое понятие. Что конкретно имел ввиду 0xd34df00d под ним — надо спросить у него.
Тест, не общающийся с «внешним миром». Неважно — замоканы другие компоненты или просто задача не требует общения.
UFO just landed and posted this here

Не важно где они там на практике встречаются. Важно какие тесты можно назвать юнит-тестами, а какие нельзя. Потому что если называть одним и тем же словом всё подряд — вас перестанут понимать.

UFO just landed and posted this here
Интересно, а зачем спорить, когда ты настолько некомпетентный?
Вы просто неправильно определили профессию rsync. Он — невероятно компетентен в своей профессии. Просто это — не написание программ и не решение каких-то задач, а объяснение всем остальным, что они «ничего не понимают в колбасных обрезках».

Для этого — что-то там «понимать» или там «определять» — не только не нужно, но даже вредно. Ибо мешает набрасывать лапшу на уши.
UFO just landed and posted this here
UFO just landed and posted this here

Что как бы исключает зависимость от внешнего мира, не так ли? Зачем тест модуля, например, оплаты в интернет-магазине, который зависит от того, что вернёт, если вернёт, настоящий платежный шлюз? я пишк тесты модуля оплаты, а не тесты платежного шлюза.

UFO just landed and posted this here

Юнит тест — да, такому коду не написать.

UFO just landed and posted this here

В юнит-тесте я хочу тестировать свой модуль, а не внешнюю систему или соседний модуль. Если тест падает, то ошибку буду искать в своём модуле.

UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here

Да вам же и нужно:


тесты называются юниттестами если они тестят код внутри юнита.

Если тесты тестят внешний мир, то какие-же они юнит-тесты?


А избавляться от внешнего мира нужно по двум основным причинам:


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

А за интеграцию юнитов друг с другом или юнита с внешним миром, отвечают, как ни странно, интеграционные тесты.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
все вышеприведённые примеры (спутник упал, самолёт перевернуло, самолёт чуть ни упал, итп) приведены в аргумент «хорошо писать на языках с типами», но все эти примеры написаны именно на языках с типами
Ну если вы знаете примеры самолётов с софтом на нетипизированных языках и можете аргументированно показать что им разницы операторов между целыми числами и строками хватило — то, пожалуйста, можете статью написать.

Но ничего подобного я от вас пока что не видел, сплошные рассуждения про великий и ужасный документооборот.

Тут же, знаете ли, всё просто, как с технарями и гуманитариями: если людей с техническим образованием, которые потом стали известны как гуманитарии — полно, а ноборот — как-то не очень, то сразу ясно чего стоит «гуманитарная одарённость».

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


Давайте все программы вот так писать:

>The fly-by-wire flight software for the Saab Gripen (a lightweight
>fighter) went a step further. It disallowed both subroutine calls and
>backward branches, except for the one at the bottom of the main loop.
>Control flow went forward only. Sometimes one piece of code had to leave
>a note for a later piece telling it what to do, but this worked out well
>for testing: all data was allocated statically, and monitoring those
>variables gave a clear picture of most everything the software was doing.
>The software did only the bare essentials, and of course, they were
>serious about thorough ground testing.
>
>No bug has ever been found in the «released for flight» versions of that
>code.
Давайте все программы вот так писать:
Дорого так писать очень. Но вообще — можно и так.

Дорого так писать очень. Но вообще — можно и так.

Как бы я никогда не спорил с тем, что динамически типизированные языки не нужны. Они нужны.

Но не в тех случаях, когда вам нужна работающая программа без ошибок!

И да — я ни разу не макетолог и не продажник. Если мы порождаем, я извиняюсь, дерьмо — то я так и говорю: мы порождаем дерьмо. А не «конфету с альтернативным вкусом».

И дважды два для меня — четыре. Независимо от того, продаём мы или покупаем.

Ну вот так.

А если вам хочется изгнать математику из программирования, чтобы продавать «конфеты с альтернативным вкусом»… да ради бога: пока мне это не приходится расхлёбывать — продавайте кому угодно и что угодно… только без меня.
UFO just landed and posted this here
как раз динамически типизируемый язык позволяет делать меньше ошибок. Именно потому что код алгоритма очищен от ритуальных ненужностей и программист может видеть/понимать больше/лучше
Так пример самолёта или хотя бы автомобиля с управляющим ПО на таком языке — будет али нет?
UFO just landed and posted this here
Ну вот когда будет — тогда и посмотрим. Потому что пока что там, где людям реально нужна надёжность — движение идёт в обратном направлении: от JS к TS, от C++ к Rust и так далее.
UFO just landed and posted this here
пока ещё на Rust не написано ни одного полезного приложения
О как. Внезапно когда речь зашла за Rust — так это оказалось важно. А как про динамические языки говорили — так было достаточно веры в то, что «нет — но обязательно будет».

а TS популярен не столько из за типов сколько из за банально более развитого ООП
Вам самому-то не смешно? Хотя о чём это я: профессиональным демагогам сомнения неведомы. Верить в 10 противоречащих друг другу вещей одновременно — это бывает очень пользительно для зарплаты.
UFO just landed and posted this here
так я Вам вернул Ваш тезис, Вы не заметили?
Заметил, конечно. Почему все демагоги считают, что они такие вумные, мне интересно?

Я уже про это писал явно: с демагогами (и с вами, в том числе) говорить о чём-либо бессмысленно.

О чём можно говорить с человеком, готовым одновременно верить во что угодно (а только лишь при этих условиях и можно «возращать кому-то ваш тезис») если ему это выгодно?

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

В идеале — тем, кто может такого персонажа уволить. И всё, собственно.
UFO just landed and posted this here
Да у вас снова как минимум половина доводов просто бредовые.
языки низкого уровня — традиционно с типами

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

Разница в скорости условного Си и Си# в большинстве случаев при правильном написании кода невелика.
безопасность у языков низкого уровня хуже чем безопасность у языков высокого уровня: на программисте лежит управление памятью, например

Раст на вас смотрит с недоумением.
приход операционных систем общего назначения в такие отрасли как бортовые компы машин (и их автопилоты), самолётов — уже происходит и со временем других бортовых компов и не останется.

Системы реального времени никуда не деваются, и языки с GC туда не приглашают.
UFO just landed and posted this here
Я вот буквально час назад переписывал код в одном классе на более удобочитаемый, и допустил ошибку. Просто упустил из вида один кейс, который проверялся в предыдущей версии кода, несмотря на то, что новый код был на порядок чище и короче, в нём не было никаких проверок типов, только алгоритм. Тест отвалился, и я полез внимательно читать, что же я там сделал не так.
UFO just landed and posted this here
UFO just landed and posted this here
код алгоритма очищен от ритуальных ненужностей


Простите!

const item = {  
  createdAt: new Date(),
}

// function isOutdated(item) {};
console.log(isOutdated(item));


Этот «чистейший алгоритм» отработает корректно?

Ответ — конечно нет. И динамически типизированный язык не позволил ни увидеть, ни понять ни больше, ни лучше. Докопаться до истины мы можем только изучив, что делает функция isOutdated.

isOutdated
function isOutdated(item) {
  // createdAt is timestamp
  return Date.now() - item.createdAt >= 123456;
};



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

Теперь посмотрим, как этот код выглядел бы на Typescript

const item = {  
  createdAt: new Date(),
}

// function isOutdated(item: { createdAt: Timestamp }): boolean {};
console.log(isOutdated(item)); // won't compile


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

function isOutdated(item: { createdAt: Timestamp }) {
  return Date.now() - unwrap(item.createdAt) >= 123456;
};


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


Даже на динамически типизированном языке вы оперируете типами, просто они нигде не записаны, кроме как в голове у условного разработчика. Программист сменил место работы — с ним ушли важнейшие знания о проекте. Адекватный подход с точки зрения работодателя — это когда все знания о проекте остаются в нем.

UFO just landed and posted this here
если объект Date определит привод себя к чиселке — то может быть сравниваем с чиселками

а если объект Date определит привод к себе строчечки, то может быть сравниваем и со строчечками формата YYYY-MM-DD ...

А если и то, и то? Кстати, насколько мне известно, Date в JavaScript работает не так.

если объект Date определит привод себя к чиселке — то может быть сравниваем с чиселками

Есть разные способы привести момент времени к числу — из широко используемых, например, unix time и julian date.
а если объект Date определит привод к себе строчечки, то может быть сравниваем и со строчечками формата YYYY-MM-DD ...

Действительно, а американцев тут будем игнорировать.
UFO just landed and posted this here
UFO just landed and posted this here
Да, извините, перепутал порядок. Но суть-то от этого не меняется: есть множество способов перевода даты в число, или даты в строку — и поэтому сравнение в духе date == int смысла не имеет.
UFO just landed and posted this here
UFO just landed and posted this here
int < 2500 = это просто год
int > 2500 = это timestamp

К счастью, никакой нормальный язык программированию, насколько я знаю, такой критерий для сравения дат и чисел не использует. Правда, очень смешное предложение.
timestamp — это что такое? Предположу, что unix time имеется в виду. Тогда — количество миллисекунд от 1970? Или может быть секунд, а то и дней? А по какому часовому поясу? Почему вместо unix time не брать например julian day? А если мне больше нравится дробный год?

date в int через unix time не позволит различить 2016-12-31T23:59:60 от 2017-01-01Т00:00:00 а между ними одна секунда

UFO just landed and posted this here
UFO just landed and posted this here
я о том что аргумент про типы «вот видите самолёт упал, а типы ему бы помогли» некорректны, поскольку самолёт падал именно на софте с типами.
Покажите мне, пожалуйста, кретина, который утвердает что любая система типов позволяет избежать любых ошибок.

Для каждой конкретной ошибки — да, можно изобрести систему типов, которая позволит её избежать. Для всех сразу — нет, невозможно. Как минимум потому что невозможно избежать ошибок в постановке задачи.

То обстоятельство что я не привёл контраргумент не оставляет возможности наставивать на том, некорректность которого выяснили
Гениально! Вы сами-то прочитайте, что вы написали, а? Если вы не привели контрагумент — то о чём мы тут вообще говорим?

Утверждение, извините, не заключалось в том, что любые типы могут помочь вам избежать любых ошибок.

Утверждение заключалось что чем сильнее система типов, тем меньше ошибок дойдут до стадии полёта. Так же утверждалась, что ваша идиотская идея «достаточно не складывать строки оператором плюс — и настенет нирванна» — не работает.

Оба утвеждения вполне себе хорошо иллюстрируются тем фактом, что ни один самолёт, софт для которого был бы написан на указанных вами принципах не смог не то, что упасть — он даже взлететь не смог!
UFO just landed and posted this here
Боинг как-то новости писал о применении Linux в бортовых компах и Эирбас тоже. Можно поискать.
Поищите, поищите. Там Linux применяется только в системах, на которые не завязана безопасность. Ни в каком MCAS этого нету.
UFO just landed and posted this here
или вот скажем машины (более частый кейз) с автопилотами (которые пока называют помощниками вождению) это про безопасность или нет?
Пока что это про «премию Дарвина» в основном.

какая операционка в Tesla? Linux же.
Ну это уже шаг вперёд для Маска. Когда-то его из PayPal выперли потому, что он вообще Windows хотел вкрутить.

Но это всё — как раз нормально и естественно для «продавца воздуха».

Python де-факто это язык с динамической типизацией.
Python там используется для тренировки моделей.

И даже там делаются попытки от этого уйти. Ибо ненадёжно это всё.

Но пока эти машинки по дорогам сами не ездят и сертификатов не получают — можно и Python, конечно.

Хинты для линтера приделали сравнительно недавно, но никаких ошибок компиляции эти хинты не вызывают.
Пока не вызвают. А дальше — либо начнут вызывать, либо Python будет на что-то другое заменён.

So, если в настоящее время нет самолётов под управлением скриптового языка с динамической типизацией, то в будущем обязательно будут :) Всё к этому идёт.
Поживём — увидим.

О чём-то можно будет говорить после банкротства той самой Теслы. Ибо пока существует достаточно людей, готовых платить на неработающий автопилот как за работающий… ни качественные программы, ни, соотвественно, типы и ФП — действительно никому не нужны.
UFO just landed and posted this here

У вас логическая ошибка: якобы переход к типизированным языкам обязательно вызван желанием повысить быстродействие и обязательно сопровождается снижением надёжности. А это не так, хотя бы потому, что есть, например, Java, вполне себе статически типизированный язык, но с исключениями и без SIGBUS.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
выбор X, Y обычно делают вообще не люди, а обстоятельства: на рынке труда много дешёвых спецов по языку X

То есть люди сделали выбор и выучили язык Х.
первый спец занявшийся этим проектом знал только X

то же самое — программист сам выбрал язык.
UFO just landed and posted this here
Ну и самое главное преимущества из-за которого я никогда не смогу продуктивно работать на динамическом языке — это то что с нормальным инструментарием можно всегда точно сказать что какой-то объект/функция не используется, и точно найти все использования.


А что мешает это делать при динамической типизации? Ну кроме штук вроде variable functions в пхп.

Тот факт, что для того чтобы сделать такой вывод, надо расставить, пусть даже в уме, кучу статических "маркеров" по затронутой части программы. А если это возможно сделать — то чем нам статическая типизация помешала?

UFO just landed and posted this here

Его написать не просто сложно, его зачастую написать невозможно.


Кстати, логика создателей линтеров порой и правда "потрясающая".

UFO just landed and posted this here
Если линтер написать невозможно => компилятор тоже написать невозможно.

Ну разумеется. А что, существуют полноценные AOT-компиляторы для языков с динамической типизацией? Насколько я знаю, в лучшем случае есть JIT, а в худшем — так простым интерпретатором всё и ограничивается.


самый синтаксически богатый язык — Perl. Он же самый динамический. для него написан прекраснейший PerlCritic и серия линтеров.

И что, там можно во всех случаях точно установить обращается ли достаточно сложная программа к конкретному методу или полю объекта? Без доказательств я в такое не поверю.

UFO just landed and posted this here
UFO just landed and posted this here

Типы позволяют установить это во всех случаях, с некоторой помощью программиста.


Линтеры не позволяют этого установить во всех случаях, даже с помощью программиста.

UFO just landed and posted this here

Ну вот пример выше с координатами
Дано — куча функций, некоторые работают с 2d координатами, некоторые с 3d, некоторые даже и с теми и с теми.
Задача — у всех использований 2d координат заменить y на z (т.е. превратить 2d координату в 3d). 3d координаты не трогать.
Как решить эту задачу точно? В смысле без ложноположительных и ложноотрицательных срабатваний?
Бонус: Часть данных грузится с диска (к примеру)
Мега бонус: Если нужно поменять не все 2d координаты, а только часть (но существенную часть). В системе с типами можно просто "начать" менять, а дальше просто пофиксить все места где компилятор говорит что "вот тут тип неправильный".


Еще на всякий случай скажу что это не искусственная задача.

Как решить эту задачу точно? В смысле без ложноположительных и ложноотрицательных срабатваний?
А зачем её решать?

Можно же, вместо этого, попариться с заказчиком в баньке, перетереть всё — и заработать больше денег, чем это сделаете вы, когда что-то там докажите.

Вся логика rsync, по большому счёту, к этому сводится (хотя он прямо в этих терминах вам этого не скажет).
UFO just landed and posted this here
Вы это прекрасно демонстрируете, спасибо.

Кроме того, я не согласен что не ставя типы экономится время.


Немножно времени экономися сначала, но потом:


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


В итоге, код которые "write once" и больше никогда не нужно менять/читать (скрипты и простейшие сервисы) получается выгоднее писать на языках без типизации. Но как только дело разрастается до серьезных вещей — на все приходится тратить "чуть больше времени".

Я не понимаю, при чём здесь типизация вообще?

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

Ну что-нибудь банальное типа такого:
    foo = [False] * 3
    foo[0] = 42
    foo[1] = 57
    foo[2] = 115
    Foo(foo)
Понять — нужно ли у вас в функции Foo обрабатывать случай, когда в массиве есть False — невозможно, если вы не можете понять — отработают у вас все присваивания или иногда могут не отработать.

А это уже, может привести, к вызову функций типа MakeDefaultFoo0 и прочее.

Если же вы подобные вещи никогда не пишите и все типы у вас всегда однозначно выводятся — то вам динамическая типизация «нафиг не упала» и вы вполне могли бы работать и на языке со статической без указания типов (Haskell, OCaml, возможно даже Google Go).

Еще можно добавить что при написании библиотек приходится выставлять публичный интерфейс наружу (или, к примеру, читать json по сети), и вот тут то без указаний типа никакой линтер не скажет ничего более ценного чем "ээээ ну а тут будет any"

Вернее работает только когда вы используете язык с динамической типизацией как язык со статической типизацией, но без явного указания типа.
Я пишу почти одинаково что на статике, что на динамике. Мне не особо нужен тайпчекинг, потому что если я что-то сделаю не так, всё развалится. Зато мне очень нужна возможность запускать то, что я пишу каждые 1-5 минут, т.е. либо интерпретируемый язык (а именно они как правило динамически типизированные), либо очень бысто компилирующийся, типа JAI, который сейчас в бете.
Зато мне очень нужна возможность запускать то, что я пишу каждые 1-5 минут
Это… Если достаточно долго месить чан с перловой кашей, в синтаксическом мусоре можно рано или поздно узреть лик Ларри Уолла?

Да, есть такая технология… какое она имеет отношение к программированию?

Или, если это не попытка узреть лик Ларри Уолла — то что вы за задачу решаете, что требования к ней меняются каждые 1-5 минут? А если они не меняются — то зачем вам код-то запускать, извините?

Напишите — запустите, делов-то. У Haskell, кстати, есть неплохой REPL для экспериментов.
UFO just landed and posted this here
Или, если это не попытка узреть лик Ларри Уолла — то что вы за задачу решаете, что требования к ней меняются каждые 1-5 минут?
Процедурная графика. Там вообще строгих требований быть не может. Если у меня там цепочка аффинных преобразований и подобного, накосячить (например, с их порядком) так, что система типов не отловит — раз плюнуть.

И этот метод не про требования — он про то, что если постоянно запускать то, что ты пишешь, гораздо меньше времени уходит на дебаг, потому что почти все ошибки исправляются сразу. Короткий feedback loop.

И с Ларри Уоллом — это не ко мне, я не о нём практически ничего кроме имени не знаю.
Если у меня там цепочка аффинных преобразований и подобного, накосячить (например, с их порядком) так, что система типов не отловит — раз плюнуть.
Ну и зачем для этого запускать каждые 5 минут систему на 100 миллионов строк?

Вполне хватит «песочницы», которая не будет требовать сборки кода по часу.

И с Ларри Уоллом — это не ко мне, я не о нём практически ничего кроме имени не знаю.
Вы бы весь рассказ прочитали. Там «лик Ларри Уолла» — не более чем метафора…
UFO just landed and posted this here
UFO just landed and posted this here
Что значит «один проект»? Если GWS (который поиском занимается) и GFE (который просто аналог NGINX) собираются из одной репы — это один проект или разные? А у них, вполне возможно, 90% кода общие.

У Гугла вообще нет деления на «проекты». Библиотеки — есть. Модули — есть. Проектов — нет.

Ну или если считать что вот Android и Chrome (которые живут в отдельном проекте) и «неявно» код не шарят — то у Гугла три проекта: Android, Chrome, и, собственно, то, что называется Google3.

Где как раз и живут все эти 2 миллиарда строк и откуда собираются почти все продукты Гугла.
UFO just landed and posted this here
Мне не надо каждые 5 минут 100 миллионов строк. Мне надо, чтобы я что-то написал, запустил, посмотрел, откатил/изменил/оставил. Интерпретируемые языки дают это делать, причём удобно, потому что они именно под это заточены. Вполне хватит их.
Интерпретируемые языки дают это делать, причём удобно, потому что они именно под это заточены.
TypeScript — вполне себе интерпретируемый, но под это не заточен. Или Dart.

А вот Go — компилируемый, но там как раз внимательно следили за тем, чтобы компиляция происходила достаточно быстро для того, чтобы go run имел смысл.
TypeScript компилируется в JS.

Есть deno, которая работает как интерпретатор (точнее, внутри-то он все равно наверняка компилирует, но снаружи этого не очень видно).


А вот "не заточен под интерпретацию" никакая deno не изменит, потому что в тайпскрипте в принципе невозможно тайпчекнуть пару строчек не загружая все модули.

Так типы загружаются в том же файле, как и весь остальной контекст. Вы хотите строки вне контекста проверять?
UFO just landed and posted this here
Ну а я «играюсь» прямо в коде.

Про ошибки, о которых не говорит тайпчекер вроде уже обсуждали.
UFO just landed and posted this here
После всех этих разговоров про repl я вспомнил и пошёл читать про Julia, и похоже она под мои задачи и стиль программирования подходит просто идеально.

Чем пользуетесь для хаскеля, не подскажете?

UFO just landed and posted this here

Примитивный алгоритм сломается на первом же колбеке.
Чуть более сложный алгоритм сломается на первой же обобщенной функции, вроде метода map у массива или функций из библиотеки lodash.


А дальше вы "напарываетесь" на невозможность вывода типа и вынуждены просить программиста помочь вам аннотациями.


Но даже если вам повезёт и вы ни на что не напоритесь — ваш линтер внезапно становиться ещё одним инструментом статической типизации, притом самым лучшим :-) Динамическая типизация языка исчезла в тот момент, когда линтер разметил все функции в программе.

def call_by_name(name, *args, **kwargs):
    return eval(str(name))(*args, **kwargs)

Удачи

UFO just landed and posted this here
Речь идёт вот об этой задаче.

это то что с нормальным инструментарием можно всегда точно сказать что какой-то объект/функция не используется, и точно найти все использования


Как статическая типизация поможет в таком случае?

Особенно если name вы получаете откуда-то извне?
Как статическая типизация поможет в таком случае?

Запретит использовать eval.

UFO just landed and posted this here
Не, eval, по крайней мере в не урезанном виде, не может существовать вместе с компайлтайм проверками.
UFO just landed and posted this here

А для чего вы используете eval в своих проектах?

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Это не только для яваскрипта работает. Схожие трюки можно даже на плюсах или Rust проворачивать, только понадобится с LLVM слинковаться за неимением eval :-)

UFO just landed and posted this here

От задачи зависит, при подходящих задачах вроде численного интегрирования введенной пользователем функции можно и на плюсах +500% получить.

UFO just landed and posted this here

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

UFO just landed and posted this here

Конкретно в случае парсеров хорошо работает кодогенерация. Она, к слову, и для скриптовых языков хорошо работает, даже быстрее чем eval.

UFO just landed and posted this here

Разумеется, генерировать надо заранее.


Например в протоколе части-блоки определяют следующие блоки (например передаётся длина и далее собственно тело) итп.

А в чём, собственно, проблема считать сначала длину, а потом тело? И зачем тут вообще eval?


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

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

UFO just landed and posted this here
или при помощи eval сделегировали эту работу более быстрому by design интерпретатору языка

Конкретно в случае "длины и тела" нормально считать быстрее. eval вам сожрёт всю производительность на постоянных операциях парсинга переданного в него кода.


подключаем как модуль — это и есть вызов eval, просто названо чуток по другому.

Нет. Даже если оно реализовано через eval — это всего лишь одна из реализаций, которая не мешает статическому тулингу.

UFO just landed and posted this here

Оно мешает, когда eval находится в вашем коде.


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

Вы разбираете поток данных используя управляющие структуры на «медленном языке», или при помощи eval сделегировали эту работу более быстрому by design интерпретатору языка
Ну то есть вы сначала накакали себе в карман, а потом пытаетесь придумать как из этой кучи дерьма выудить бумажник?

А может того… не какать? Хотя бы себе в карман?

что такое eval? это вызов интерпретатора/компилятора во время исполнения программы.
Технически — да. А идеологически — нет.

Либо вы вызываете eval на код, который написал ваш разработчик (тогда eval — эта трата дикого количества энергии впустую), либо на код, который к вам пришёл извне (тогда это — почти всегда дыра в безопасности).
UFO just landed and posted this here
то что решили использовать язык высокого уровня?
То, что выбрали язык, который не позволяет написать на нём достаточно быстрый парсер.

Haskell, скажем, язык достаточно высокого уровня (хотя я его и не люблю), парсеры там пишутся без всяких eval и скорость их достаточна для того, чтобы о ней, в 99% случаев не думать (а в том 1% случаев где думать надо — никакой eval PHP или Python всё равно не спасёт).

скорость разработки обычно существенно важнее скорости работы.
В парадигме «нам не нужны работающие программы, нам нужно убедить заказчика в том, что порождённая нами куча дерьма — это конфетка»… согласен.
UFO just landed and posted this here
Либо вы вызываете eval на код, который написал ваш разработчик (тогда eval — эта трата дикого количества энергии впустую), либо на код, который к вам пришёл извне (тогда это — почти всегда дыра в безопасности).

На самом деле, есть третий вариант — вызов eval на код, который сгенерировал разработчик. Иногда это даже имеет смысл, на тех языках где динамической компиляции из AST не завезли.

На самом деле, есть третий вариант — вызов eval на код, который сгенерировал разработчик.
Чем этот третий вариант отличается от первого?

Иногда это даже имеет смысл, на тех языках где динамической компиляции из AST не завезли.
Компилятор напустить на то, что вы там породили… религия не позволяет?

Ну честное слово, что за детский сад: lex и yacc — это ни разу не новинка.
Чем этот третий вариант отличается от первого?

Тем, что код появился только в рантайме. Вы ещё спросите зачем Java и .NET используют JIT, когда AOT компиляция такая замечательная!


Ну честное слово, что за детский сад: lex и yacc — это ни разу не новинка.

А кто тут предлагает от них отказаться (кроме вас)?

Вы ещё спросите зачем Java и .NET используют JIT, когда AOT компиляция такая замечательная!
Спрошу, разумеется. Вот на Android/iOS JIT тупо взяли — и запретили. И ничего: и C# и Java, оказываются могут в AOT, если нужно.

Тем, что код появился только в рантайме.
Откуда он взялся и почему его нельзя было скопилировать нормальным компилятором?
Откуда он взялся и почему его нельзя было скопилировать нормальным компилятором?

  • зависимость от окружения
  • обобщенный код, который тупо неудобно компилировать заранее (вроде того как в С++ есть возможность выносить реализации шаблонных функций в отдельные единицы компиляции — но нахрена?)
  • зависимость от ввода пользователя

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

UFO just landed and posted this here
UFO just landed and posted this here

С такой аргументацией можно вовсе от интерпретируемых языков отказаться — но они почему-то существуют.

Существуют они потому что написать кривую и косую программу на интерпретируемом языке быстрее, чем хорошую на компилируемом. Если вас это устраивает — да ради бога.

Но вот что вызывает удивление — так это непрерывный «бег в колесе»: «ой, мы написали дерьмовый код на дерьмовом языке и получили дерьмо… а как бы нам из него конфетку сделать, а?». Ну если вы хотели конфетку — то зачем вы в дерьмо-то полезли?

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

Если же их скорость перестанет меня устраивать — то последнее, о чём я буду думать — так это о том, чтобы прикрутить туда eval.

Переписать на компилируемый язык (неважно какой: Go, C++, Rust, Haskell… любой язык о скорости которого можно, в принципе, как-то рассуждать) — первое, что стоит сделать.

Применять «грязные хаки» — когда вы сами себя «загнали в угол» и хорошего выхода у вас нет. И этого, в общем, следует избегать…

Переписывать проект на другой язык из-за одного места в коде?


И, если речь идёт о браузере, добавить загружаться пару мегабайт рантайма выбранного языка для WASM?


А это точно лучше одного аккуратного eval?

Переписывать проект на другой язык из-за одного места в коде?
Нет. Не из-за одного места в коде. Из-за того, что задача стала требовать скорости.

Это уже на основе опыта. Ну нельзя расшить одно место — и получить удовлетворительную скорость. «Разошьёте» одно место, получите затык в другом. И, рано или поздно, всё равно придётся переделывать.

Самый эпичный пример, который я знаю — это обсуждение опуса «Please don't use Python except for small scripts» внутри гугла. Полного текста у меня нет, но фишка там не в дискуссии как таковой, а в метадискуссии.

Потому что когда эта вещь была написана — сразу поднялась дискуссия на тему: «ну как же так — мы же все используем для Code review Mondrian, написанный на Python… его, правда, сам Гвидо лично написал, он, наверное, обладал тайным знанием… потому все проекты на Python разваливаются, а Mondrian — живёт и здравствует».

Дальше — это некоторое время обсуждалось… пытались понять — что Гвидо сделал «так» и что все остальные сделали «не так»…

А потом прорезался тонкий голосок какого-то новичка с примерно следующим текстом «я вообще в Python — небольшой специалист, да и в Гугле работаю без году неделя… но как участник прокта по переписывания Mondrian на Java могу сказать следующее: ну и дальше был какой-то текст».

Дискуссия, в общем, заглохла почти сразу. Потому что предмет для дискуссии, в общем-то, исчез.

P.S. На самом деле там было даже два форка: внутренний и внешний. И я даже не знаю над каким из них тот инжинер работал. Но это даже и неважно, так как Python извели из обоих.

А это точно лучше одного аккуратного eval?
Ну вот не видел я чтобы «один аккуратный eval» менял картину.
И, если речь идёт о браузере, добавить загружаться пару мегабайт рантайма выбранного языка для WASM?
Браузер — это статья особая…

Как известно, бег в мешках и просто бег — это две сильно разные спортивные дисциплины. Очень сильно. Тот факт, что в брайузере есть особый, выделенный (и достаточно-таки убогий) язык, который в него встроен — очень сильно меняет картину мира. Тут я просто не могу ничего посоветовать… потому что я «в мешке» бегал очень мало.
Например в протоколе части-блоки определяют следующие блоки (например передаётся длина и далее собственно тело) итп.
Ужжжаснейшая, сложжжнейшая задача! Главное — хорошо отваричиваться когда вам доку на ParSec или Ragel будут подсовывать.

в случае с наличием eval, мы просто однопроходно преобразуем это в код и вызываем на нём eval. Получаем очень быстрый шаблонизатор.
Не. Получаем не шаблонизатор, а дыру в безопасности. И потом лет 10, пока этот шаблонизатор не «выпилят» — с неё кормимся.

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

программы, которые могут писать другие программы — самые счастливые программы в мире!
это вот про такие алгоритмы
Нет, это про кодогенерацию и метапрограммирование. Они с типами как раз отлично получаются. На Haskell есть и шаблоны и парсеры и куча всего ещё.

А eval — это про дыры в безопасности и «Job Security».
UFO just landed and posted this here
Дыру в безопасности мы получаем, если в исполняемый код попадают входящие данные as is.

А типы как раз позволяют проверить, что данные попадают только должным образом проверенные и отфильтрованные.

Не только типы это позволяют. Да и сама проверка должна быть плюс-минус одинаковой.

UFO just landed and posted this here

Eval — это дыра в безопасности исключительно в рантаймах без поддержки песочниц. Но даже для js песочницу сделать вполне реально.

Сделаете — заходите. Я знаю о двух попытках: в PHP и в Python.

Ну и Java тоже не шмогла.

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

«Почему-то» (подумайте почему) более-менее вменяемую «песочницу» удаётся сделать только в языках, где ни «песочница», ни «eval» не нужны…
И где это практически используется? Понимаете в чём беда: создать безопасный, но бесполезный sandbox — несложно. Ну запретите там всё, кроме операции "+" и целых чисел — и всё, вот вам безопасность.

Создать полезный, но небезопасный sandbox — тоже: просто ничего не проверяйте — будет возможность делать что угодно, но и безопасности не будет тоже.

Пока я не очень вижу — куда и для чего можно было бы применить ваш sandbox, потому, скорее, он ближе к первому варианту.

С этого все начинают. Судя по количеству упоминаний оной песочницы в Internet её пока ни для чего серьёзного использовать никто даже не пробовал.

По второй ссылке как раз пример практичного использования. Как взломаете — заходите.


А пока, вот вам ещё интересный пример с объявлением функции и применением её для свёртки диапазона: https://calc.hyoo.ru/#title=Sandbox/A1=reducer%20%3D%20%28a%2Cb%29%3D%3E%20%28a%2Bb%29%2F2/B1=1/A2=result%20%3D%20%28B1%3AC2%29.reduce%28_.reducer%29/B2=2/C1=3/C2=4

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

Всё это действительно построено вокруг «безопасного режима» (в данном случае уже JavaScript, а не PHP и не Python), но поскольку на практике эту песочницу, насколько я знаю, особо не применяют, то попыток уйти от режима «смотрите как я могу» к чему-то, что можно реально использовать и, далее, к бесконечному затыванию дыр — пока и нет.

Разработчики PHP и Python ведь тоже не идиоты — они несколько лет пытались найти эту точку между бесполезной и дырявой песочницами.

Если не смогли сделать песочницу за пару лет — значит идиоты. Не идиоты — это разработчики wasm, nacl, web workers и тд, которые смогли.


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

Когда нечего возразить — плюнь в спину карму и иди с миром.

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

Под структурой понимаются внешние данные или структуры данных самого языка? Если первое, то никак. Если второе, то вы и не сделали парсер, а воспользовались уже готовым.


Ну и так, к слову, на одних скриптовых языках свет клином не сошёлся.

UFO just landed and posted this here
возможно есть компилируемые языки с eval, но я лично о них не знаю.
Потому что не хотите знаить, очевидно. Вот, пожалуйста.

Да, его вызвать не так просто, как eval в скриптовых языках… но это, скорее, преимущество, а не недостаток.
UFO just landed and posted this here

В .NET есть разные штуки чтобы генерировать промежуточный код в рантайме. Можно компилировать ExpressionTree или генерировать байткод.


Можно скомпилировать DLL из исходного текста и подгрузить в процесс.


В стандартной библиотеке регекспы компилятся.

UFO just landed and posted this here

Такие, как внедрение уязвимостей с иньекцией постороннего кода, ага.

Код типа object[methodName](), где name — строка, полученная извне, вполне валидный для статически типизированного TypeScript. Но требует или тайпгварда, или каста. На практие очень часто только каст или тайпгвард не очень честный, например, проверяет, что строка только.

Ну кому как. Мне так наоборот хочется разделить все числа посильнее, чтобы метры с килограммами сложить было нельзя. Или чтобы id пользователя и id товара были разные типы, несмотря на то что оба номинально int (или string, неважно)

Поддержую. Сам иногда добавляю в свой проэкт DDD Value Object в виде структуры с неявным кастом, даже когда внутри будет только одно поле. Вот пример для Nullable от MS для понимания.
UFO just landed and posted this here
Мне тоже было бы интересно посмотреть на методологию. И если смешивание чисел и строк еще хоть как-то более-менее легко отследить с помощью статического анализа, то вот более не тривиальные ошибки (пример в комменте выше habr.com/ru/post/506088/#comment_21722482) уже гораздо сложнее отследить. А уж чтобы провести частотный анализ по таким группам ошибок… интригующе.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
что в языке надо разделить строковые и числовые операторы.

Это до рождения PHP или после?

До. Эту «гениальную» идею (как и несколько других подобных) Ларри в Perl воплотил. А потом удивлялся ещё — а почему люди с него сбежали, как только смогли. Наверное гениальность не просекли…
UFO just landed and posted this here
А не расскажите какие галлюциногены нужно на ночь принимать чтобы такие «сказочки» во снах видеть?
UFO just landed and posted this here
Технического обоснования нет
Смотря что считать «техническим обоснованием».

На самом деле разница между Perl и Python сформилирована прямо в их девизах: TMTOWTDI против Должен существовать один — и, желательно, только один — очевидный способ сделать это.

Первый подход — идеален для порождения куч дерьма, которые обеспечивают потрясающую Job Security и возможность «вешать лапшу на уши» заказчикам, второй — позволяет добиться безопасности и надёжности (и, разумеется, несмотря на то, что является, формально, девизом Python — куда больше подходит таким языкам как Haskell или Idris).

В успехе Python виноват исключительно Google, «в котором все мечтают работать».
То есть Google махнул палочкой — и Python вырос с 2-3% до 10%, махнул ещё раз — Perl упал с 10% до 1%.

А в успехе Rust тогда кто виноват? Только не нужно говорить, что «20е место — это не успех». Для языка, заточееного под реальную безопасность (а не о разговорах о ней) — 20е место это вполне себе успех.

Ибо в современном мире безопасность — не так, чтобы много кому нужна… а вот разговоры о ней — да, они продаются.
UFO just landed and posted this here
контексты, генераторы yield суффиксные if-else, циклы и генераторы, дцать типов-примитивов (set, tuple) итд
Тем не менее — там регулярно рассматриваются ситуации, когда нарушения этого принципа считаются минусом и обсужлается что-то типа такого.

так что этот принцип не более чем маркетинговая декларация
Ну надо же. То есть в Perl — тоже регулярно обсуждается как убрать всякие «недостатки молодости», избавиться от TMTOWTDI и сделать всё правильно и красиво? Ссылочками не поделитесь?

Я то я, как бы, только про потуги создать Perl 6 знаю — и там натащили в язык столько всякого TMTOWTDI… что мало не покажется.
UFO just landed and posted this here
да этот принцип исповедуется коммюнити Perl, но это не свойство языка.
Да, это свойство коммьюнити. Потому я и не могу сказать — отказ от Perl это техническое решение или нет.

этот принцип относится к любому языку
Ни в коем случае. В большинстве других языков вопрос «как лучше сделать: через A или через B» считается нормальным. Да, на него не всегда удаётся дать однозначный ответ, иногда сама специфика задачи не позволяет сформулировать какое-то одно решение… но это считается нормальной и правильной целью.

В Perl же такие дискуссии, почти всегда, заканчиваются тем, что кто-то произносит TMTOWTDI… и объясняет что сам вопрос неверен: зачем вам, собственно, знать что лучше — используйте то, что вам левая пятка подсказывает в данном конкретном месте.

Есть некоторое количество людей, пытающихся как-то «уменьшить масштаб разрушений» от TMTOWTDI… но сама идея, что подход TMTOWTDI — плох и что TMTOWTDI является злом (хотя иногда и неизбежным, увы)… она в головах апологетов Perl никак не укладывается.

Увы, коммюнити питон далеко от этого принципа.
Нет, конечно. В Python регулярно добавляют вещи, упрощающие программирования. И — при этом приходится отходить от принципа «должен быть только один способ сделать это».

Но когда это происходит — там никогда не забывают о том, что сделав что-то проще они, одновременно, делают и язык сложнее — в том числе для изучения новичками.

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

Взлёт популярности Python — это исключительно Google
Интересно только «как». Так-то у Google ни одного серьёзного проекта нет на Python, кроме TensorFlow, нет поддержки Python ни в Android ни в браузере… что вообще Google такого сделал, чтобы Python популярен стал? В TensorFlow его поиспользовал? Ну так это 2015й год, на графике популярности Python в 2015м ни скачков вверх, ни скачков вниз не наблюдается…

Вот Go — да, тут без поддержки Google явно ничего бы не вышло… но Python? Каким боком тут Google?
UFO just landed and posted this here
UFO just landed and posted this here

Думаю не в этом дело. Очень многие как раз на PHP сбегали с Perl/

UFO just landed and posted this here
Сейчас, чем больше я понимаю про типы, тем меньше понимаю, как описать все типизации, разложить их по полочкам и отличить друг от друга.

Особенно контрастно, что пишет автор, который себя называет «Король разработки» )).

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

Автору: в статье верное замечание, в TS и правда дерьмовая система типизации, нестрогая и полная проблем/исключений, а инструментарий полон багов. Но это не отменяет факт, что для веба пока ничего лучше нет, и что TS нужно обязательно использовать. Хорошо хоть развивается бодро.

Я точно не пытался сказать, что тайпскрипт плох. У его системы типов точно есть большие проблемы, но они есть во всех системах типов. Лично мне больше нравится писать код на тайпскрипте, чем на C# — потому что он выразительнее. А проблемы лечатся.
> имплиситные касты

Строгая типизация все таки не запрещает такие касты, т.к. у вас есть код, которые вы можете посмотреть и по сути это просто синтаксический сахар (как автовывод типов). Тут скорее речь о кастах, которые оговорены в стандарте языка. Целое к строке, строка к булу. Однако в строгой типизации допустимо приведеие int64 к int32 или даже int к double. Вот тут да, граница размывается слегка (совсем слегка)
Так, звучит разумно. Но что должен делать ЯП, что бы считать его строгим/нестрогим?
То-есть, если у меня есть вот такие классы. То для компилятора (или среды исполнения) они будут разными типами.

Нет, это не правда. Проблема вашего непонимания в том, что вы говорите о Классах и Типах как об одном и том же. Но на самом деле Класс и Тип это два совершенно разных понятия. В C# вы определяете именно Классы, а в Typescript вы определяете Типы. Вот что об это пишет легендарная Банда Четырёх (Гамма, Влиссидес, Хелм, Джонсон) в Библии программиста «Design Patterns»:
Важно понимать различие между классом объекта и его типом.
Класс объекта определяет, как объект реализован, то есть внутреннее состояние
и реализацию операций объекта. Напротив, тип относится только к интерфейсу объекта — множеству запросов, на которые объект отвечает. У объекта может быть
много типов, и объекты разных классов могут иметь один и тот же тип.
Разумеется, между классом и типом есть тесная связь. Поскольку класс определяет, какие операции может выполнять объект, то заодно он определяет и его
тип. Когда мы говорим «объект является экземпляром класса», то подразумеваем,
что он поддерживает интерфейс, определяемый этим классом.
© Гамма, Влиссидес, Хелм, Джонсон

Это значит, что термин Тип относится только к множеству запросов, которые можно отправить объекту. Если множество запросов совпадает — значит у объектов Тип будет общим, независимо от того к какому Классу эти объекты принадлежат.
Тип — это имя, используемое для обозначения конкретного интерфейса. Говорят, что объект имеет тип Window, если он готов принимать запросы на выполнение любых операций, определенных в интерфейсе с именем Window. У одного
объекта может быть много типов. Напротив, сильно отличающиеся объекты могут разделять общий тип. Часть интерфейса объекта может быть охарактеризована одним типом, а часть — другим. Два объекта одного и того же типа должны разделять только часть своих интерфейсов.
© Гамма, Влиссидес, Хелм, Джонсон

Поэтому тот факт, что ты можешь использовать Типы с разными именами и общим интерфейсом в одной и той же функции не противоречит тому факту, что ЯП является строго типизированным — ведь это один и тот же Тип согласно всем правилам. Ты ожидал получить строку и в результате получил строку — ни целое число, ни число с плавающей точкой, ни адрес в памяти. Следовательно, типизация строгая — какой интерфейс просил, такой и получил. Это и называется строго типизированный ЯП. Класс и Тип это разные сущности. Если вы в вашем примере на C# будете использовать Интерфейсы вместо Классов, то получите тот же результат, что и в Typescript. По этой же причине нет такого понятия, как «Приведение Классов», а только «Приведение Типов» — потому что объект может иметь только один Класс, но Типов у объекта может быть сколько угодно. Объект не может поменять свой Класс, даже если вы приведёте его к Типу родительского Класса, то он всё равно не изменит свою внутреннюю реализацию (т.е Класс), а только усечёт свой интерфейс (т.е Тип). Тут нет противоречия со строгой типизацией, потому что и у дочернего и у родительского Класса — один Тип. Это вторая парадигма ООП — полиморфизм (один класс => множество типов).
А вот такая вот дичь, от «пацанов на работе»:
Пацаны объяснили мне, что у ts статическая типизация, но не номинативная.

Полная чушь. Нет таких понятий. А структурная типизация это вообще совершено другое. Структурные типы (или же значимые тип — value type) это типы, которое передаются по значению, а не по ссылке. Не слушайте всяких пацанов. Если хотите узнать достоверную информацию, а не очередной бред, читайте книги уважаемых авторов, учёных со степенями в информатике и математике, таких как Банда Четырёх, Дональд Кнут «Искусство программирования», Стивен Макконнелл «Code Complete», книги Вирта и Таненбаума. Там очень подробно разъясняются все эти вопросы.
При этом все статически типизированные ЯП, которые я использовал, дают возможности для динамической типизации. Any в TS, Dynamic в сишарпе. В конце концов тот же тип Object — по идее, я запихиваю в него все что угодно, и говорю, что у меня статически типизированный код. Но строго говоря, это не совсем так.

Динамическая типизация это не когда «я запихиваю в него все что угодно», а когда Тип определяется во время исполнения. Object никак нельзя назвать динамической типизацией, так как он определяется на этапе компиляции. Вы сами об этом же писали чуть выше, а потом сразу же ниже написали глупость. Более того динамическая типизация тоже может быть строй, это уже детали реализации компилятора и среды выполнения.
А вообще нужно в первую очередь понимать, что Типы Данных это всего лишь абстракция. На уровне процессора или даже чуть более высоком уровне языка Ассеблера нет никаких типов. У процессора есть только один тип — машинное слово (можно разделить на байты, слово, двойное слово, но это не суть). То что на языке Ассеблера нет типов совсем не делает его «игрушечным языком». Типы нужны исключительно для удобства и избавления от многих раздражающих случайных ошибок вроде «сложил букву и число», «умножил адресс на строку» или «записал в регистр грязные биты». Любой ЯП это просто текст в файлике, который так или иначе в итоге станет машинным кодом. Нет «серьёзных» и «несерьёзных» языков программирования, не важно где исполняется твой «текст» в браузере, интерпретаторе, виртуальной машине или сохранится в бинарник. За 7 лет рабочего опыта пора уже отучится от таких детских взглядов на программирование и код. Ладно джуны нифига не понимают и только спорят, чей папка круче, Java или C#. Ну сениору уже пора бы более трезво смотреть на вещи.
Нет, это не правда. Проблема вашего непонимания в том, что вы говорите о Классах и Типах как об одном и том же.

С чего вы решили, что автор считает что это одно и то же. Из приведенной цитаты этого не следует.


Если множество запросов совпадает — значит у объектов Тип будет общим, независимо от того к какому Классу эти объекты принадлежат.

Метод void Close() и метод void Close() это один и тот же запрос?


А вот так:


/*  Закрывает окно, потом его можно переоткрыть при помощи Open()
*/
void Close()

/*  Закрывает поток, поток нельзя больше использовать. Изменения сбрасываются в файл на диске.
*/
void Close()

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


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


Нет таких понятий.

https://en.wikipedia.org/wiki/Structural_type_system


Любой ЯП это просто текст в файлике, который так или иначе в итоге станет машинным кодом.

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


Нет «серьёзных» и «несерьёзных» языков программирования,

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


За 7 лет рабочего опыта пора уже отучится от таких детских взглядов на программирование и код.

Вы очень категоричны и неуважительны.

С чего вы решили, что автор считает что это одно и то же. Из приведенной цитаты этого не следует.

С чего вы решили, что из приведенной цитаты этого не следует?
Метод void Close() и метод void Close() это один и тот же запрос?

Очевидно, что да — по принципу подстановки Барбары Лисков. Реализация не имеет значения, когда речь идёт об абстракциях. Тип относится к уровню абстракций, он независим от реализации. На этом основаны три из шести принципов ООП — инкапсуляция, полиморфизм и абстрагирование. Когда вы проектируете систему в UML диаграммах вас не интересует реализация типов, а только их интерфейс. Все сущности с общим интерфейсом это один тип.
en.wikipedia.org/wiki/Structural_type_system

Википедия недостоверный источник информации. В качестве главного рефернса в этой статье приведено обсуждение на форуме, что как бы уже намекает что статья с запашком. В других указанных источниках нет такого термина. В основном источнике — книге Пирса «Types and Programming Languages» используется правильный термин — параметрический полиморфизм:
According to the first view, type variables should be held abstract during typechecking, thus ensuring that a well-typed term will behave properly no matter what concrete types are later substituted for its type variables. For example, term
λf: X→X. λa:X. f (f a);
has type (X→X) →X→X, and, whenever we replace X by T, the instance
λf:T→T. λa:T. f (f a);
is well typed. Holding type variables abstract in this way leads to the idea of parametric polymorphism, in which type variables are used to encode the fact that a term can be used in many concrete contexts with different concrete types.
©Pierce, Benjamin C. (2002). Types and Programming Languages.


Термина структурная типизация в книге нет (как и термина номинативная типизация), следовательно автор статьи сам выдумал это термин или взял с форума. В одном из источников вообще обсуждается система порождения подтипов, их ковариантность и контравариантность. Ковариантность и контравариантность могут служить правильным термином, но только когда его применяют относительно неявного приведения коллекций. Статья фактически основана ни на чём, там даже предупреждение об этом сверху имеется. Информацию нужно проверять.
В любом случае: во всех достоверных источниках, книгах Кнута, Вирта, Таненбаума, Страуструпа и Хейлсберга за термином структурная типизация стоит совершено другое понятие (value type). Не говоря ещё о C#, где все примитивные типы Int16, Int32, Int64 и т.д — это структуры (struct). Поэтому использования термина структурная типизация в другом контексте не оправдано, двусмысленно и методологически неверно. Правильный термин — полиморфизм. Другой вопрос должны ли типы и подтипы определяться явно (наследование интерфейса) или неявно. Тут есть несколько мнений. Математики утверждают, что типы должны порождаться неявно — как в математике, если значение удовлетворяет абстракции и её аксиомы соблюдены (читай — реализован интерфейс), то его можно свободно использовать — например, вместо действительного числа можно спокойно использовать полином, матрицу или комплексное число, если используемая операция определена для каждого из этих множеств и для операции согласованы аксиомы (ассоциативность, коммуникативность и т.п). Некоторые инженеры утверждают, что программист должен сам явно указать, что объекты одного типа (т.е с общим множеством запросов) могут быть взаимозаменяемы, согласно некой иерархии. Две модели называются явным и неявным порождением подтипов (implicit or explicit subtyping).
UFO just landed and posted this here
Это грубая ошибка: тип это не только название свойств и методов, но и семантика поведения. Прикол в том, что LSP, на который Вы ссылаетесь, именно об этом и говорит.
Пусть q(x) является свойством, верным относительно объектов x некоторого типа T.
Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

Нет, не так. Принцип говорит, что в любом месте использования Типа, должна быть возможность использования его Подтипа и при этом не должно возникнуть необходимости менять код вызывающего метода, так чтобы он научился работать с Подтипом. Другими словами вызывающий метод не должен ничего знать о подтипах и у него не должно возникать необходимости явного приведения типа к его подтипу, чтобы работать корректно. Принцип Лисков не накладывает ограничения на реализацию. При этом тип в любом случает будет абстрактен и независим от семантики. Тип не имеет отношение к реализации и семантике. LSP не определяет термин Типа, а только даёт рекомендации «хорошой практики» применения типов. Я сослался на Правило Лисков относительно примера ApeCoder с void Close(), который соответствует этому принципу. В его случае принцип LSP будет соблюдён, так как в обоих случаях Close() описывает финализирующюю операцию, которая будет вызываться Сборщиком Мусора в теле финализирующего метода Dispose() типа IDisposeable (в C#), методом finalize() (в Java), или без сборщика мусора в деструкторе класса (C++), при этом поведение Сборщика Мусора или деструктора не поменяется, вне зависимости закрываете ли вы файл или соединение с базой данных или вообще что-то другое, пишете ли вы в файл или нет. Сборщику Мусора не надо будет знать о существовании подтипа, а только об общем типе (узкий интерфейс IDisposeable, IFinalizeable или что вроде того).
Проще говоря: если есть тип с методом add(x, y), у которого есть свойство возвращать сумму двух чисел, то его нельзя заменить типом с тем же методом, но который возвращает разность двух чисел, несмотря на то, что сигнатуры типов одинаковы.

Нет, не так. Во-первых, вызывающему методу вообще должно быть до лампочки, что ему вернут: сумму или разницу, главное чтобы возвращаемый тип был тот же. Вызывающий метод не должен знать ничего о внутренней логике вызываемого метода. Во-вторых, метод add(x, y) уже возвращает не только сумму, но и разность, при аргументах add(2, -2), так же как метод sub(x, y) будет возвращать сумму при аргументах sub(-2, -2). Если вы назвали метод sub, методом add, то это будет не нарушением LSP, это будет алгоритмическая логическая ошибка (иными словами баг). Даже если метод add(x,y) будет возвращать разность, а не сумму — принцип LSP не будет нарушен, так как нам не придётся переписывать вызывающий метод. Система типов не может (и не должна) отслеживать логические ошибки. Система типов следит только за тем, чтобы вы не могли использовать один тип вместо другого типа (использовать тип интерфейс которого не поддерживается). То есть задача системы типов гарантировать, что в объекте обязательно будет метод add(x,y), а ситуации, что этого метода вдруг не окажется никогда не возникнет. При этом даже, если метод add(x,y) будет реализован с логической ошибкой это не должно повлиять на работу объекта вызывающего этот метод, так как система типов гарантирует, что метод add(x,y) в качестве результата вернёт значение нужного типа.
Нет, не так. Во-первых, вызывающему методу вообще должно быть до лампочки, что ему вернут: сумму или разницу, главное чтобы возвращаемый тип был тот же.

При этом даже, если метод add(x,y) будет реализован с логической ошибкой это не должно повлиять на работу объекта вызывающего этот метод, так как система типов гарантирует, что метод add(x,y) в качестве результата вернёт значение нужного типа.

Всё-таки типами в большинстве языков нельзя описать все полезные требуемые свойства функций/методов. Например, если add — это сумма, как и предполагает вызывающий код, то reduce(add, collection) можно выполнять в любом порядке, в том числе параллельно, и получать одинаковый результат. Если же add выдаёт разность, то это будет неверно — очевидно, такие отличия влияют на работу функции, откуда это дело вызывается.
Метод add по умолчанию уже возвращает и сумму, и разность,: add(2,2) => 4, add(2,-2) => 0. Работа reduce(add, collection) не изменится, если add будет возвращать разницу. Поэтому вызывающий код никак не может предполагать, что add вернёт именно сумму. И вообще вызывающий метод в принципе не должен делать никаких предположений относительно смысла возвращаемого значения, а только относительно множества допустимых возвращаемых значений. Все проверки «от дурака» находятся внутри вызывающего метода, так как всегда ожидается, что вызываемый метод может вернуть недопустимое значение. Работа функции от этого не изменится, так как обработка недопустимых значений и есть часть стандартной работы функции, которая заложена в неё изначально. То что ответ будет неверным, не меняет того факта, что функция верно отработала. Это значит только, что писавший программист допустил логическую ошибку. Логические ошибки в принципе нельзя отловить автоматически, по крайне мере пока не изобрели такой нейросети, которая будет предвосхищать человеческие идеи и находить в них логические ошибки.
То что ответ будет неверным, не меняет того факта, что функция верно отработала.

Действительно: «К пуговицам претензии есть? — Нет!» :)
UFO just landed and posted this here
Серьезно? Похоже, вы прогуливали уроки математики: 2 + -2 = 0; 2 — -2 =4

Нет чувак, похоже это всё таки ты прогуливал уроки математики, ахаах. Сумм(2,-2) это 2 + (-2) = 2 == разность. А 2 — -2 =4 это Разн(2,-2) == будет сумма. Тут ты лоханулся)
UFO just landed and posted this here

В случае переполнения может и вернуть в зависимости от способа записи отрицательных чисел.

В его случае принцип LSP будет соблюдён, так как в обоих случаях Close() описывает финализирующюю операцию

В данном случае это значит, что среди двух методов можно найти что-то общее, а не то, что они тождественны. Так же можно сказать, что они оба методы.


метод sub(x, y) будет возвращать сумму при аргументах sub(-2, -2).

Да он будет возвращать число равно сумме, но других чисел: -2 и 2. Оно так же будет равняться произведению 1 и 0

UFO just landed and posted this here
Такое забавное чувство, когда по отдельности слова вроде смысл имеют, а вместе получается какая-то ерунда.

Я так понимаю, это отсылка к сишарпу, где ковариантность по сути просто говорит, можно ли скастовать Foo[] к IReadOnlyCollection<BaseFooClass> или нет.

UFO just landed and posted this here
Тут нет ни слова про структуру, только про интерфейс. Набор сигнатур не является структурой. Под словом структура и в информатике, и в математике понимают совершено конкретные вещи, которые никак не относятся к набору сигнатур. Когда говорят о структуре подразумевают связи отношений между объектами.
Напротив, тип относится только к интерфейсу объекта — множеству запросов, на которые объект отвечает. У объекта может быть много типов, и объекты разных классов могут иметь один и тот же тип.
© Гамма, Влиссидес, Хелм, Джонсон «Design Patterns»

Тип — это имя, используемое для обозначения конкретного интерфейса. Говорят, что объект имеет тип Window, если он готов принимать запросы на выполнение любых операций, определенных в интерфейсе с именем Window.
© Гамма, Влиссидес, Хелм, Джонсон «Design Patterns»

Если вы создадите ещё один такой же интерфейс с именем View, это будет значить только то, что у вашего типа будет два имени — Window и View. Другими словами вы создадите алиас (кличку) типа, но интерфейс останется тем же. То есть ваш тип будет преставлять из себя два имени, а не одно — тут нет никаких противоречий. Это как например использовать typedef в C/C++ и дать для int алиас number.
С чего вы решили, что автор считает что это одно и то же. Из приведенной цитаты этого не следует.

С чего вы решили, что из приведенной цитаты этого не следует?
Метод void Close() и метод void Close() это один и тот же запрос?

Очевидно, что да — по принципу подстановки Барбары Лисков. Реализация не имеет значения, когда речь идёт об абстракциях. Тип относится к уровню абстракций, он независим от реализации. На этом основаны три из шести принципов ООП — инкапсуляция, полиморфизм и абстрагирование. Когда вы проектируете систему в UML диаграммах вас не интересует реализация типов, а только их интерфейс. Все сущности с общим интерфейсом это один тип.
en.wikipedia.org/wiki/Structural_type_system

Википедия недостоверный источник информации. В качестве главного рефернса в этой статье приведено обсуждение на форуме, что как бы уже намекает что статья с запашком. В других указанных источниках нет такого термина. В основном источнике — книге Пирса «Types and Programming Languages» используется правильный термин — параметрический полиморфизм:
According to the first view, type variables should be held abstract during typechecking, thus ensuring that a well-typed term will behave properly no matter what concrete types are later substituted for its type variables. For example, term
λf: X→X. λa:X. f (f a);
has type (X→X) →X→X, and, whenever we replace X by T, the instance
λf:T→T. λa:T. f (f a);
is well typed. Holding type variables abstract in this way leads to the idea of parametric polymorphism, in which type variables are used to encode the fact that a term can be used in many concrete contexts with different concrete types.
©Pierce, Benjamin C. (2002). Types and Programming Languages.

Термина структурная типизация в книге нет (как и термина номинативная типизация), следовательно автор статьи сам выдумал это термин или взял с форума. В одном из источников вообще обсуждается система порождения подтипов, их ковариантность и контравариантность. Ковариантность и контравариантность могут служить правильным термином, но только когда его применяют относительно неявного приведения коллекций. Статья фактически основана ни на чём, там даже предупреждение об этом сверху имеется. Информацию нужно проверять.
В любом случае: во всех достоверных источниках, книгах Кнута, Вирта, Таненбаума, Страуструпа и Хейлсберга за термином структурная типизация стоит совершено другое понятие (value type). Не говоря ещё о C#, где все примитивные типы Int16, Int32, Int64 и т.д — это структуры (struct). Поэтому использования термина структурная типизация в другом контексте не оправдано, двусмысленно и методологически неверно. Правильный термин — полиморфизм. Другой вопрос должны ли типы и подтипы определяться явно (наследование интерфейса) или неявно. Тут есть несколько мнений. Математики утверждают, что типы должны порождаться неявно — как в математике, если значение удовлетворяет абстракции и её аксиомы соблюдены (читай — реализован интерфейс), то его можно свободно использовать — например, вместо действительного числа можно спокойно использовать полином, матрицу или комплексное число, если используемая операция определена для каждого из этих множеств и для операции согласованы аксиомы (ассоциативность, коммуникативность и т.п). Некоторые инженеры утверждают, что программист должен сам явно указать, что объекты одного типа (т.е с общим множеством запросов) могут быть взаимозаменяемы, согласно некой иерархии. Две модели называются явным и неявным порождением подтипов (implicit or explicit subtyping).
С чего вы решили, что из приведенной цитаты этого не следует?

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

Не совсем понял момент (не знаю TypeScript): Я ответил, что в тайпскрипе я могу написать тип, который описывает число меньше ста, и получу компайл тайм гарантию, что число было проверено. А в сишарпе я так сделать не смогу.

Вот же тип в шарпе который реализует заявленное поведение:
public class NumberLT100
    {
        private int _number = 0;

        public int Number { get{ return _number; } set { _number = value % 100; } }

        public NumberLT100() { }
        public NumberLT100(int num) { Number = num; }

        public static NumberLT100 operator +(NumberLT100 a, NumberLT100 b)
        => new NumberLT100(a.Number + b.Number);

        public static NumberLT100 operator -(NumberLT100 a, NumberLT100 b)
            => a + (-b._number);

        public static NumberLT100 operator *(NumberLT100 a, NumberLT100 b)
            => new NumberLT100(a.Number * b.Number);

        public static NumberLT100 operator /(NumberLT100 a, NumberLT100 b)
        {
            if (b.Number == 0)
            {
                throw new DivideByZeroException();
            }
            return new NumberLT100(a.Number / b.Number);
        }
        
        public static NumberLT100 operator +(NumberLT100 a, int b)
        => new NumberLT100(a.Number + b);

        public static NumberLT100 operator -(NumberLT100 a, int b)
            => a + (-b);

        public static NumberLT100 operator *(NumberLT100 a, int b)
            => new NumberLT100(a.Number * b);

        public static NumberLT100 operator /(NumberLT100 a, int b)
        {
            if (b == 0)
            {
                throw new DivideByZeroException();
            }
            return new NumberLT100(a.Number / b);
        }
    }
Хотя дошло, компайл-тайм это да, тут только реалтайм. С другой стороны, если есть гарантия на время выполнения, то этого обычно и достаточно.

А можно глянуть реализацию этого LessThan?

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

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

Как я понимаю, даже если в исходниках константа задана 99, то проверка всё равно будет в рантайме?

Да. Но это и не для констант сделано же

Вам даже идрис будет это проверять в рантайме. Что поделать, экстрасенсорных библиотек пока не запилили

Он говорит про кейс с компайл тайм известным значением. Зачем его правда на что-то там проверять в компайлтайме — другой вопрос

Ну, это тоже полезные проверки. Например, чтобы можно было написать:


type Vector n = Matrix 1 n

И оно бы работало.


Или такие:


stringOrInt : Bool -> Type
stringOrInt True = String
stringOrInt False = Int

foo : (x : Nat) -> stringOrInt (x < 2)
foo Z = "Hello"
foo (S Z) = "World"
foo (S (S x)) = cast x

Если честно, совсем не понял мысль

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

Только вот компайл-тайм проверок никаких у вас тут нет, компилятор вас никак не защитит. Например, -105 это число меньше 100, но ваш код его не ловит. Почему? Потому что вся логика у вас в голове, компилятор её не проверяет.


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

Вообще по коду никак не получится выйти за пределы (-100;100). Но про компайл-там это да.
В тайпскрипте можно написать так, что он это будет проверять. и без 100 строк кода, а в одну.

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

Утиную типизацию не обязательно противопоставлять номинативной. В том же Rust-e достаточно объявить имплеменацию нужного трейта для желаемого типа. Это очень легко и заставляет программиста явно сообщить что эти типы можно одинаково использовать.
Это номинативная типизация с ее гарантиями, но в удобстве не уступающая утиной.

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


class Dog {
  public name: string

  constructor(name: string) {
    this.name = name;
  }
}

class Cat {
  public name: string

  constructor(name: string) {
    this.name = name;
  }
}

function printName(x: Dog) {
  alert(x.name)
}

printName(new Cat("Miao"))

А в Rust — нет, хоть с трейтами, хоть без них.

Интересная деталь, если свойство name будет приватным, код станет невалидным. В тс и правда много намешано, подумываю написать об этом подробно, но все как-то руки не доходят

Допустимость этого кода привлекательна только тем что можно засунуть кошку в существующий код, принимающий собаку. И не переписывать при этом существующий код (ни кошки ни собаки). Rust я привел в качестве примера потому что там тоже очень лекго такое сделать (объявить кошку собакой и передать ее в код принимающий собаку) не переписывая ни собаку ни кошку.

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

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

struct Dog {
    age: i32
}

trait DogWithAge {
    fn getAge(self: &Self) -> i32;
}

impl DogWithAge for Dog {
    fn getAge(self: &Dog) -> i32 {
        self.age
    }
}

struct Cat {
    age: i32
}

trait CatWithAge {
    fn getAge(self: &Self) -> i32;
}

impl CatWithAge for Cat {
    fn getAge(self: &Cat) -> i32 {
        self.age
    }
}

fn printName(x: &DogWithAge) {
    println!("{}", x.getAge())
}

impl DogWithAge for Cat {
    fn getAge(self: &Cat) -> i32 {
        self.age
    }
}

fn main() {
    printName(&Cat{age: 5});
}


Под «кошку собакой объявить» я имел в виду вот это:
impl DogWithAge for Cat {
    fn getAge(self: &Cat) -> i32 {
        self.age
    }
}


Пример конечно не эквивалентен, но идея была такая, почему Вы придрались, я не понял.

В самом идее написать impl DogWithAge for Cat скрыта ошибка.

И где тут структурная типизация? Вы передали DogWithAge в метод который ожидает DogWithAge.


Вот если бы вы могли написать:


let x : Box<dyn CatWithAge> = Box::new(Cat {age: 5});
DogWithAge::get_age(*x);

Тогда конечно да. Но вы не можете.


Это классическая номинативная типизация, DogWithAge::getAge ожидает объект типа DogWithAge и никакой другой.

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

Это утверждение, что апельсины удобнее, чем красные объекты. Несвязанные вещи.

Система типов это инструмент для написания кода, а инструменты можно сравнивать по удобству.

Нет, вы говорили что это утиная типизация, в первом из ваших комментариев.

Вот моя цитата из 1го комментария: "Это номинативная типизация с ее гарантиями, но в удобстве не уступающая утиной." Как из нее можно было сделать такой вывод?

Давайте посмотрим вашу цитату:


Утиную типизацию не обязательно противопоставлять номинативной.

Окей, а что такое утиная типизация? Погуглим, что на эту тему считают источники:


static vs dynamic describes when types are checked (more or less at compile time or at execution time)

nominal vs structural describes when two types are considered the same.

(And there are variations, the best known is the variant of structural typing which considers only what is used instead of the whole type known as duck typing).

То есть номинальная и утиные типизация противопоставляются по-определению. Про удобство тут ни слова, речь про то, что их не обязательно противопоставлять — но оказывается, что на самом-то деле — обязательно.

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

Если посмотреть на динамическую типизацию с точки зрения статической, можно считать все динамические типы одним статическим. Так что противоречия нет, можно внутри статически типизированной программы иметь динамический код.
«обращу мудрость мудрецов ваших — в глупость!» (с)

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

Не существует академически исчерпывающего ответа на то где какой тип типизации потому, что сама типизация это средство а не цель, бля*ь, когда вы уже об этом вспомните и если в том-же ЖС «писать как надо» то будет «как надо» и за три года ванильного ЖС от самого забристого фронта до глубин Node и Koa, десктопных экспериментов с electron и всякими игро/2d-тридэшностями от pixi до babylon я нигде не столкнулся с ПРЕДМЕТНОЙ необходимостью в объектной модели, просто разноси функционал по-файликам, индексируй, и сочетай как и где тебе удобно и свое и чужое и тупо не бойся этой химеры про «приведение типов».

Ошибки связанные с неправильным поведением фреймворка (в широком смысле этого слова растянутого на объектные модели всего с чем работаешь от DOM браузера до нода и ФС) вылавливаются мнтуитивно спинным мозгом еще до того как ты начинаешь всерьез подключать логику мозга головного.

И за все это время проблемы возникали ИМЕННО с ООП в ОМ фреймворков, даже VUE!

От чего в конце концов я вообще перестал юзать фронтенд — фреймворки, проще в разы написать кастомное свое на какой-нибудь bulma/pug а через год-пять-когда понfдобится — просто переписать то что устарело (а оно, ска, устареет и вас не спросит даже если золотом покрыть!)

Но вы как в том анекдоте про холостяка, который никогда ничего не готовит потому что все начинается с «возьмите чистую тарелку» — так и вы начинаете любое решение с «а какие тут типы» !?

Давсемна*рать давно! какие там типы, чтобы ты ни написал завтра никому не будет нужно а то что нужно — будет переписано тобой же ибо прогресс и личностный в том числе им так не только в софте кстати.

А мир предметной области никогда, НИКОГДА,! Н И К О Г Д А! ты слышишь!?
не будет, следовать прогрызенной тобой канве типов и кастомер завтра захочет быть емплоейм а послезавтра опять кастомером и каждый новый день будет приносить юзкейсы не влазящие в твою ОМ в какой-бы системе типов ты ее не наваял и сколь бы долго ты ее не рожал «все предусмотрев». И попадая в прод твой код будет обрастать костылями перпендикулярно кладущими и на твою ОМ и тип выбранной типизации и на все гудпрактис ООП и не ООП вместе взятые!

РЕАЛЬНЫЙ мир продакшна это затыкание милиона маленьких дыр в одной большой плотине и каждый следующий раз прорвет именно там где «вот вообще было не должно»

И все что делают сейчас «джава капитаны» это стоят по жопу в воде и делают вид что так и задумано, а виноват во всем ваш c*аный ЖС!

ЖС — костыльный пулемет, возводящий костыль в ранг мировоззрения и методологии и пока вы сретесь за то какой там тип типизации трушнее и постижимее ЖС держит на своих костыльных плечах уже вообще все!
пока вы сретесь за то какой там тип типизации трушнее и постижимее ЖС держит на своих костыльных плечах уже вообще все!

Проверил, на моём компьютере из всех программ на JS работает примерно ничего.

Articles