Комментарии 437

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

Да, интересно и раскрыта объемная тема в простых словах. Но для меня главный вопрос — куда делась предыдущая статься про 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. Какая мощная штука оказалась.

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

а чем вам generic классы не угодили?
Так в си шарпе тоже можно создать проперти любого(T) типа. А еще есть тип dynamic которому можно присвоить все что угодно, другой вопрос, нужно ли это, потому-что работать это будет примерно со скоростью JavaScript
Так. Это все верно конечно, но как это противоречит тому что написал я?

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

Но в C# есть специальные костыли, например методы Add, Dispose, Deconstruct и т.д. Получается в некоторых специальных местах он структурно типизированный.

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


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

В 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)В сторонний класс реализацию интерфейса не запихнуть.


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

У С# же есть интерефейсы. Разве нельзя ограничивать по ним?

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

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

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


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# можно

А вы часто пишите код с операторами? Я вот за восемь лет работы, только в книжках видел как оператор использовать, на практике не разу не пригодилось, как и goto
посмотрел зп F# разрабов у нас в фирме(вакансии), надо что-ли освоить его=)

Это не только про операторы.
например такое возможно в фшарпе.
Функция, которая достаёт из любого типа поле 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);
}

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

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

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


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


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

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


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


Вот F# — номинативно типизированный ЯП. Но. Но. В нем можно написать функцию, которая работает с чем угодно, у чего есть такое-то поле, такого-то типа. Более того, я могу весь свой код писать в таком стиле — это будет структурно типизированный код на номинативно типизированном F#. Как с этим быть?

Есть такая тема, как row polymorphism, и это очень клёвая тема. Не знаю, есть ли она в F# (я не знаю F#), но в этом несчастном хаскеле её очень не хватает.

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


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

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

Я просил пример паттерн матичнга по типам, который сможет работать без информации о типах

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

Стоп, вы про паттен-матчинг по типам? А в каком языке (кроме Idris 2) вы это можете делать? В хаскеле по типам вы матчиться не можете.


А я, если что, говорил о


data Foo = F1 | F2 Int | F3 Bool
data Bar = B1 Double | B2 Foo

f1 :: Foo -> Bool
f1 F1 = ...
f1 ...

f2 :: Bar -> Int
f2 B1 = ...
f2 ...

Обе функции могут смотреть в условный int по адресу, на который указывает аргумент. Для первой функции значение 0 будет означать F1, а для второй то же самое значение может означать B1. Где ж тут информация о типе? Она тут только о номере конструктора.

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

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));
    }
}


Пример отсюда

Любопытно, спасибо!


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

Согласен: если речь идёт о функции, которая принимает object и сверяет его тип на соответствие конкретным типам, такой код явно пахнет.

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

type A = B of int | C of int

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

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

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

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

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

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

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

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


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

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

Вовсе нет:


notId : {a : Type} -> a -> a
notId {a = Nat} _ = 0
notId {a = Bool} b = not b
notId n = n

даёт


% idris2
     ____    __     _         ___
    /  _/___/ /____(_)____   |__ \
    / // __  / ___/ / ___/   __/ /     Version 0.2.0-4aa1c1f44
  _/ // /_/ / /  / (__  )   / __/      https://www.idris-lang.org
 /___/\__,_/_/  /_/____/   /____/      Type :? for help

Welcome to Idris 2.  Enjoy yourself!
Main> :l Dyna.idr
1/1: Building Dyna (Dyna.idr)
Loaded file Dyna.idr
Main> notId (the Nat 10)
0
Main> notId False
True
Main> notId (the Int 10)
10
Main> notId "meh"
"meh"
Main> :total notId
Main.notId is total

Паттерн-матчинг? Да. По типам? Да. Без универсумов? Да.


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

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

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




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

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


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

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

Если начать считать вторые случаи — то в список языков с паттерн-матчингом по типам надо внести С++ (специализация шаблонов работает как паттерн-матчинг)

Ну C++ не ориентируется на целостность и цельность с точки зрения всяких лямбда-кубов, про него лучше вообще забыть (по крайней мере, в таких дискуссиях).


Rust (реализации трейтов) и Haskell (инстансы тайпклассов, ограниченно).

Про раст я знаю недостаточно, а хаскель с тайпклассами — так эти тайпклассы — это всего лишь ещё один параметр функции. Когда вы пишете foo :: TypeClass tc => ..., то вы на самом деле неявно имеете у функции ещё один параметр со словарём функций для tc, и всё. Внутри функции при этом вы паттерн-матчиться на конкретный инстанс тайпкласса не можете.


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

Более того, можно проще без всяких дженериков (пишу из головы, код может не компилироваться, и его надо посыпать всякими там OVERLAPPING, но идея понятна, надеюсь):


class Stupid a where
  stupidId :: a -> a

instance Stupid Bool where
  stupidId = not

instance Stupid a where
  stupidId = id

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


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

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

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

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


То есть, это эквивалентно классу вроде


class RuntimeId a where
  getId :: proxy a -> Int

instance RuntimeId Char where
  getId _ = 0
instance RuntimeId MyType where
  getId _ = 100500 -- как-то гарантируем уникальность

и последующим вещам вроде


withRuntimeId :: RuntimeId a => a -> String
withRuntimeId _ =
  case getId (Proxy :: Proxy a) of
       0 -> "yay got int"
       1 -> ...
       100500 -> "yay got my type"

Кстати, интересно, что в кишках cast использует unsafeCoerce:


eqTypeRep :: forall k1 k2 (a :: k1) (b :: k2).
             TypeRep a -> TypeRep b -> Maybe (a :~~: b)
eqTypeRep a b
  | sameTypeRep a b = Just (unsafeCoerce# HRefl)
  | otherwise       = Nothing

а вот это вот sameTypeRep, считайте, сравнивает эти самые метки.


Короче, снова функции и словари.

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

Ха-ха:


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

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


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

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


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


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

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

Ха-ха:

Не имеете права. Надо написать notId :: Stupid a => a -> a. И вот вам ваш словарь.


использовании Overlapping instances

С учётом того, что там таки везде нужен дополнительный параметр, описывающий поведение, не уверен.


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

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

Не имеете права. Надо написать notId :: Stupid a => a -> a. И вот вам ваш словарь.

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


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

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

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

Да, вы правы.


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

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

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


Как и интуитивно ожидалось, там надо включить IncoherentInstances (на что в хаскель-сообществе смотрят очень косо, и лично для меня это почти всегда знак, что я делаю что-то сильно не то):


Такой код:


{-# LANGUAGE FlexibleInstances #-}

class Stupid a where
  notId :: a -> a

instance Stupid a where
  notId = id
instance Stupid Bool where
  notId = not

stupidId :: a -> a
stupidId = notId

Без IncoherentInstances:


    • Overlapping instances for Stupid a arising from a use of ‘notId’
      Matching instances:
        instance Stupid a -- Defined at Main.hs:6:10
        instance Stupid Bool -- Defined at Main.hs:9:10
      (The choice depends on the instantiation of ‘a’
       To pick the first instance above, use IncoherentInstances
       when compiling the other instance declarations)
    • In the expression: notId
      In an equation for ‘stupidId’: stupidId = notId
   |
13 | stupidId = notId
   |            ^^^^^

Интересно, что с констрейнтом Stupid a оно акцептится (что разумно — выбор нужного инстанса делегируется вызывающему коду).


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

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

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

Как использовать — да спокойно, делегируя выбор инстанса выше и используя {-# OVERLAPS #-},


например
{-# LANGUAGE FlexibleInstances #-}

class Stupid a where
  notId :: a -> a

instance Stupid a where
  notId = id

instance {-# OVERLAPS #-} Stupid Bool where
  notId = not

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

main :: IO ()
main = do
  print $ stupidId True
  print $ stupidId "meh"

Теперь констрейнт с stupidId вы снять не можете, но main допустим и ведёт себя как ожидалось (или не ожидалось, хз).


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

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


Окей, вы включили IncoherentInstances и написали stupid :: a -> a ; stupid = notId. У меня не получилось добиться того, чтобы stupid True было False (что разумно — выбор инстанса происходит при компиляции stupidId, а не при её инстанциировании, при её инстанциировании тело может быть вообще недоступно). Получилось ли у вас?

Получилось ли у вас?

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


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

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

А в восьмой версии языка добавили и 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) вы это можете делать?

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

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

а размеченное объединение выражается через простое объединение пар

Но я всё равно же матчусь по элементам t : T, где T — объединение. Из этого не следует, что я могу матчиться по любым элементам t : *, где * такой, что T : *.

Но я всё равно же матчусь по элементам 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 парами.

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


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


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

все равно плохая идея

О, Коннор МакБрайд :)


Не знаю, этот его ответ был до его работы по QTT или после. Одно из следствий QTT — можно матчиться по типам, которые в сигнатуре функции аннотированы как runtime-relevant (или какое там правильное название). То есть, в каком-то смысле можно говорить, что вы поднимаете признак параметричности на уровень типов.


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


notId : {a : Type} -> a -> a
notId {a = Nat} _ = 0
notId {a = Bool} b = not b
notId n = n

Если убрать {a : Type} из сигнатуры, то матчиться уже будет нельзя. Причём это всё работает точно так же, как и паттерн-матчинг по обычным значениям в случае, например,


doSmth : {n : Nat} -> Vect n a -> ...

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

О, Коннор МакБрайд :)

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


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

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


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

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

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

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


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

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

«row polymorphism» — это records из Elm? Всплывающее в поиске не очень доходчиво…

Насколько я могу судить по беглому гуглу и примерам (так как не знаю Elm) — похоже, да.

Есть такая тема, как row polymorphism, и это очень клёвая тема. Не знаю, есть ли она в F# (я не знаю F#), но в этом несчастном хаскеле её очень не хватает.
Расскажите, пожалуйста, почему raw polymorphism не хватает? Часто встричаю утверждение что его не хватает в Haskell, но почему не объясняется. Разница между ad-hoc и raw в моём текущем представление только в том что в первом нужно генерировать больше бинароного кода, во втором нужно делать виртуальные таблицы методов, и как следствие делать некую рефлексию, при этом не понятно какие возможности по верх добавит второй?

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


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

А как вы с каноничным ad-hoc (в смысле наличия перегрузки функций) напишете функцию, которая работает с любым рекордом, в котором есть поля foo :: Int и bar :: Double?

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

Зато это можно гарантировать в 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
Ну это все же номинативная по сути типизация. Потому что два одинаковых интерфейса с разным именем — это разные интерфейсы. Хотя да, гибкость, которая из коробки есть в структурных япах, в сишарпе достигается именно интерфейсами
Это да… Поэтому я и использовал слово «эмулировать».
Хотя, в шарпе всё же есть интересные вещи похожие на структурную типизацию. Например метод Dispose.
О том и речь. Что любой ЯП будет очень сложно описать бинарными терминами вроде «номинативная типизация»
Так и мир окружающий не описать бинарными терминами. Добро/зло, чёрное/белое, сильный/слабый… даже — мужчина/женщина :-)
Но вот в описании япов обычно пишут, C# — язык со строгой статической типизацией. Что по-хорошему не совсем так, а иногда и совсем не так

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

Да чёрт с ней, с типизацией! Мне вот пытались как-то объяснить как битность процессора «посчитать». Ну вот почему 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++ возьмём. Это — вот как? С одной стороны такое вот законно:
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 можно нагуглить или самому написать)
Причем теоретически, я могу заставить тайпскрипт валидировать полное соответствие — как тогда мне называть его модель типизации? Понятия не имею.
НЛО прилетело и опубликовало эту надпись здесь
Ну кому как. Мне так наоборот хочется разделить все числа посильнее, чтобы метры с килограммами сложить было нельзя. Или чтобы id пользователя и id товара были разные типы, несмотря на то что оба номинально int (или string, неважно)

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

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

Я могу только представить какая это была бы боль в структурно типизированном языке или вообще языке без типов. Но к счастью язык был статически типизируемым и все что понадобилось — это пара предоставляемых IDE рефакторов плюс обновить несколько математических функций.
НЛО прилетело и опубликовало эту надпись здесь

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


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


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


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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь
Если что — у си слабая типизация. Он почти как жабаскрипт, только c
Это как раз подтверждает, что реально нужно более сильное разделение между типами, чем просто операторы для строк и чисел. В Си оператора сложения строк вообще нет, но это не влечёт отсутствие (или почти отсутствие) ошибок, которые можно было бы легко решить типами.
НЛО прилетело и опубликовало эту надпись здесь
В чём проблема-то вообще?
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"

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

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

Если функция s дана в виде


s : (v : Speed) -> (a : Acc) -> (t : Secs) -> Meters

где


Speed : Type
Speed = Meters // Secs

Acc : Type
Acc = Speed // Secs

то я набросаю типы вроде


sStartsAt0 : s v a 0 = 0

sTimeLinear : (t1, t2 : Secs) -> s v 0 t1 + s v 0 t2 = s v 0 (t1 + t2)

sSpeedLinear : (v1, v2 : Speed) -> s v1 0 t + s v2 0 t = s (v1 + v2) 0 t

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

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


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

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


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

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь
"многие не любят" — это что-то странное

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь
Если мок инициализируется перед каждым запуском теста, то мы заранее знаем, какими должны быть его вызовы и какими ответы в этом тесте. В чём тут сложность?

Интересно, как по-вашему живут все те компании, которые предпочитают типы тестам и при этом пишут что-то сложнее сортировок.


но 0xd34df00d предпочёл продолжать оставаться на уровне программирования примитивов. а это говорит "мне неинтересно"

На самом деле на уровне программирования вещей для программирования примитивов, а там есть, где развернуться.


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

НЛО прилетело и опубликовало эту надпись здесь
Вы мне рассказывали что якобы работаете в такой, но когда я спросил что делаете конкретно: спрятались за NDA
дескать работодатель из америки тут на хабре увидит и уволит

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


Впрочем, что, например, последнее место было хедж-фондом с торгами на бирже, я не особо скрываю.

берём юнит, который содержит код, ходящий в http или в Database.
его тест — юниттест или не юниттест?

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


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

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


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

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

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

НЛО прилетело и опубликовало эту надпись здесь
Вы просто неправильно определили профессию rsync. Он — невероятно компетентен в своей профессии. Просто это — не написание программ и не решение каких-то задач, а объяснение всем остальным, что они «ничего не понимают в колбасных обрезках».

Для этого — что-то там «понимать» или там «определять» — не только не нужно, но даже вредно. Ибо мешает набрасывать лапшу на уши.
НЛО прилетело и опубликовало эту надпись здесь
что вы и продемонстрировали в своей недавней статье про работу в яндексе?

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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


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


UPD:


Ещё раз: юниттест — это тест, тестирующий поведение кода внутри юнита.

Правильно, но чужая система не ваш юнит.

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


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

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


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


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

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

НЛО прилетело и опубликовало эту надпись здесь

Смотря какой тест.


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


Юнит-тест ли это или нет?

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
все вышеприведённые примеры (спутник упал, самолёт перевернуло, самолёт чуть ни упал, итп) приведены в аргумент «хорошо писать на языках с типами», но все эти примеры написаны именно на языках с типами
Ну если вы знаете примеры самолётов с софтом на нетипизированных языках и можете аргументированно показать что им разницы операторов между целыми числами и строками хватило — то, пожалуйста, можете статью написать.

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

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

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


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

>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.
Давайте все программы вот так писать:
Дорого так писать очень. Но вообще — можно и так.

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

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

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

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

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

Ну вот так.

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

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

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

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

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

В идеале — тем, кто может такого персонажа уволить. И всё, собственно.
НЛО прилетело и опубликовало эту надпись здесь
Да у вас снова как минимум половина доводов просто бредовые.
языки низкого уровня — традиционно с типами

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

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

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

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

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

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

Естественно не помогли, написано же, «никаких проверок типов».

код алгоритма очищен от ритуальных ненужностей


Простите!

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;
};


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


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

НЛО прилетело и опубликовало эту надпись здесь
если объект Date определит привод себя к чиселке — то может быть сравниваем с чиселками

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

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

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

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

Действительно, а американцев тут будем игнорировать.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Да, извините, перепутал порядок. Но суть-то от этого не меняется: есть множество способов перевода даты в число, или даты в строку — и поэтому сравнение в духе date == int смысла не имеет.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
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 а между ними одна секунда

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

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

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

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

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

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

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

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

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

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

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

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

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

О чём-то можно будет говорить после банкротства той самой Теслы. Ибо пока существует достаточно людей, готовых платить на неработающий автопилот как за работающий… ни качественные программы, ни, соотвественно, типы и ФП — действительно никому не нужны.
НЛО прилетело и опубликовало эту надпись здесь

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

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

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

Какое отношение управление памятью и компиляция имеет к типизации?


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

НЛО прилетело и опубликовало эту надпись здесь

Ладно, закроем глаза на очевидное противоречие этого коммента вашему предыдущему.


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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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


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

НЛО прилетело и опубликовало эту надпись здесь
выбор X, Y обычно делают вообще не люди, а обстоятельства: на рынке труда много дешёвых спецов по языку X

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

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


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

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

НЛО прилетело и опубликовало эту надпись здесь

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


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

НЛО прилетело и опубликовало эту надпись здесь
Если линтер написать невозможно => компилятор тоже написать невозможно.

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


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

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

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


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

а типы разве во всех случаях это устанавливают?

False negative'ов о том, что какая-то функция не используется, или что функци из импортированного модуля не используются, у меня в хаскеле не было. Правда, в первом случае нет кроссмодульного анализа, но это скорее вопрос к тулингу — нет причин запустить одну и ту же процедуру на всех модулях и сделать выводы.


ну а совершённую логическую ошибку никакой тип не находит

Тип


sumZeroLeftId : (b : Int) -> sum 0 b = b

находит.


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

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


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

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

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

Вся логика rsync, по большому счёту, к этому сводится (хотя он прямо в этих терминах вам этого не скажет).
НЛО прилетело и опубликовало эту надпись здесь

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


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


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


В итоге, код которые "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 для экспериментов.
НЛО прилетело и опубликовало эту надпись здесь
Или, если это не попытка узреть лик Ларри Уолла — то что вы за задачу решаете, что требования к ней меняются каждые 1-5 минут?
Процедурная графика. Там вообще строгих требований быть не может. Если у меня там цепочка аффинных преобразований и подобного, накосячить (например, с их порядком) так, что система типов не отловит — раз плюнуть.

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

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

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

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

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

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

Где как раз и живут все эти 2 миллиарда строк и откуда собираются почти все продукты Гугла.
сильно сомневаюсь что например Google имеет хоть один проект на 100 млн строк кода.

Откуда у них такие объемы? Такое только в яндекса наверное.
Мне не надо каждые 5 минут 100 миллионов строк. Мне надо, чтобы я что-то написал, запустил, посмотрел, откатил/изменил/оставил. Интерпретируемые языки дают это делать, причём удобно, потому что они именно под это заточены. Вполне хватит их.
Интерпретируемые языки дают это делать, причём удобно, потому что они именно под это заточены.
TypeScript — вполне себе интерпретируемый, но под это не заточен. Или Dart.

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

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


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

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

У меня прямо сейчас открыт хаскелевский репл, где я после каких-то модификаций кода пишу :r и дальше играюсь в репле. Загрузить все 20 модулей моего проекта (которые компиляются в релизе секунд 20) занимает пару секунд, перезагрузка только изменившихся по :r — чаще меньше секунды.


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

Ну а я «играюсь» прямо в коде.

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

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

После всех этих разговоров про repl я вспомнил и пошёл читать про Julia, и похоже она под мои задачи и стиль программирования подходит просто идеально.

Idea + IntelliJ-Haskell (по наводке PsyHaSTe — раньше пользовался vim + coc + hie, vim, конечно, мощнее как редактор, но hie жрёт память как не в себя).

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


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


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

НЛО прилетело и опубликовало эту надпись здесь
Речь идёт вот об этой задаче.

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


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

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

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

НЛО прилетело и опубликовало эту надпись здесь
Не, eval, по крайней мере в не урезанном виде, не может существовать вместе с компайлтайм проверками.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
На компилируемом языке по хорошему скорость самого парсинга будет упираться в IO и без евалов.
А так вы рассказываете что парсите одним проходом, а последующий евал будет работать быстро, так в комплируемых языках одного прохода бы уже возможно хватило :) ну типа комон, кодогенерация для парсинга для скорости?
стоп что?
для парсинга для скорости евал?
что что? :-D
Я знаю о этой проблеме яваскрипта и считаю сам язык горбатый донельзя, там выполняемые через евал выражения, могут работать быстрее чем написанные в исходном коде.
Но это не достоинство евал или яваскрипта, это его убогость и горбатость.

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

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

Да но там это не будет иметь такого смысла, это на JS таким костылем можно получить +30%(попутно поломав и без того убогие линтеры и проверки IDE)
И это у меня вызывает приступ дикого смеха, что в JS такие наивные костыли работают быстрее исходного кода :)

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

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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


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

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


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

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

НЛО прилетело и опубликовало эту надпись здесь
или при помощи eval сделегировали эту работу более быстрому by design интерпретатору языка

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


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

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

НЛО прилетело и опубликовало эту надпись здесь

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


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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

Как конкретный пример с одной прошлой работы — я писал компилятор для одного предметно-специфичного языка. Соответственно, ему по сети приходит программа на этом языке с просьбой её сохранить, а потом сваливаются данные, которые через эту программу надо прогнать. Там было несколько разных бекендов, от тупого интепретатора (который отлично подходит для отладки) до генерации LLVM IR и этакого JIT'а.


Желать eval как-то не приходилось. Из общих соображений — едва ли оно было бы существенно быстрее тупого интерпретатора.


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

сделегировали эту работу более быстрому by design интерпретатору языка

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

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

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

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

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

У меня есть куча скриптов на 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».
НЛО прилетело и опубликовало эту надпись здесь
Дыру в безопасности мы получаем, если в исполняемый код попадают входящие данные as is.

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

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

entropy

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

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 и тд, которые смогли.


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

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

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

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


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

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

Да, его вызвать не так просто, как eval в скриптовых языках… но это, скорее, преимущество, а не недостаток.
НЛО прилетело и опубликовало эту надпись здесь

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


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


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

ну вот берём скриптовый язык.
он by design медленнее компилируемого.

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


расскажите о "нормальных языках" скриптовых и без eval

Зачем обязательно скриптовых?

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

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

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

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

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

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

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

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

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

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

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

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

Я то я, как бы, только про потуги создать Perl 6 знаю — и там натащили в язык столько всякого TMTOWTDI… что мало не покажется.
НЛО прилетело и опубликовало эту надпись здесь
да этот принцип исповедуется коммюнити 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?
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь