Pull to refresh

Comments 119

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

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

пока не разгорелся очередной скандал связанный с дискриминацией людей с фамилиями

Вот если бы она была темнокожей, то да… А так — тьфу, полгода никто так и не пошевелился.

Надо девушке выйти замуж за парня с фамилией False.
PS: Языки без строгой типизации лучше говорили они…

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

А проблема — в кривых руках разработчика, который вместо использования параметров вставляет данные в тело SQL-запроса (и огребает на этом кучу проблем).
Ждем очередную новость: «Сотрудник Apple с фамилией Total получил самую большую зарплату в мировой истории»

Просто так не получится — кроме фамилии Total у него и имя должно быть Value!


:)

У нас вроде как нельзя сменить ФИО на что-то экзотическое.
Но за взятку если очень хочется, то можно!
«Запрещается запись имени ребенка, которое состоит из цифр, буквенно-цифровых обозначений, числительных, символов и не являющихся буквами знаков, за исключением знака „дефис“, или их любой комбинации либо содержит бранные слова, указания на ранги, должности, титулы», — так звучат изменения в статью 18 закона «Об актах гражданского состояния»

Так что в Сумму Итого вполне себе можно переименоваться. Причем судя по всему есть баг, что это относится к имени ребенка при рождении, а в дальнейшем случаются и должности в имени например «Президент России Викторовна»
lenta.ru/news/2021/02/20/prezident_rossii
— Здравствуйте, я хочу поменять имя.
— Конечно. Как Вас зовут сейчас?
— Пётр Говно.
— О, понятно. А на что хотите поменять?
— Хочу быть Виталиком…
Нет, просто налоги. Не уплаченные за N лет.
Скорее «Сотрудник Газпрома с фамилией Итого».
Вот да, тоже подумал, что девушка с фамилией дроптейбл снесла бы все серверы айклауда)
Ооооо, вот этот перевод, где именно «Дропик»!

На Хабре все время приводят тупой вариант, где мама говорит «Да, мы зовем его Робин-Брось-Таблицу» — а что, нормальное же имя, как для переводчика?

Вот, собственно:

реально же тупо
image

Может и тупо, но с "Дропика" я кринжанул далеко не так сильно, как с Робина. Глобализация, все дела, заимствования вроде "Эдвард-руки-ножницы" и «Двейн "Скала" Джонсон» уже не так сильно смущают, а вот чудаковатое "Дропик" — это откровенно грустно. Да и в целом перевод с xkcd.ru выглядит профессиональней.

Она ещё хорошо отделалась. А вот если бы где-то в гос. учреждении, или в банке — беда, пиши пропало.

Тогда возникли бы проблемы с 1С
Кодировка — системная, и NoOrdChk.prm в корень базы. Я не помню всех сегодняшних дел, но этот десятилетний костыль до сих пор в голове :)
Вот она, слабая динамическая типизация, вот она!
приведение типов при разборе данных формы? дичь дикая… Это же самое первое место, где могут быть инъекции

Вряд ли, со слабой динамической типизацией она просто на каком-то шаге стала бы "Rachel 1".


Это фамилия, тут вообще на всех шагах должна быть строка, поэтому видимо дело в протоколе обмена. В JSON логические значения по синтаксису отличаются от строк, скорее всего у них там кривой парсинг XML.
<IsVerified>True</IsVerified>
<LastName>True</LastName>


Вроде бы через XSD это как-то решается.

скорее всего у них там кривой парсинг XML.

Ну, вообще в XML только строки и ничего более. И при парсинге надо приводить текст ноды к желаемому типу. Т.е. программист должен заранее знать, что нода с именем IsVerified — хранит булево значение, а LastName — строковое.

Это если у вас статическая типизация, и вы каждую схему вручную обрабатываете. А с динамической типизацией можно сделать универсальную функцию parseXml(), которая будет true/false парсить в bool, числа в int, а все остальное в строку, и складывать в ассоциативный map "название поля — значение". Видимо они так и сделали, возможно для совместимости с JSON.

В статью скриншот добавили, ошибка еще глупее оказалась, там специальная проверка на строку "true".

Точно, сразу как-то не заметил, что true с маленькой буквы и в кавычках

Такую функцию и со статической типизацией можно сделать.

Можно, но это сложнее, и вы не сможете просто писать string name = xmlObject.name; bool isVerified = xmlObject.isVerified;

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

Что-то типа такого.


<?php
declare(strict_types=1);

function parseXml(string $xml)
{
    $xmlObj = new stdClass();

    $xml = trim($xml);

    preg_match('#^\<(\w*)\>#smu', $xml, $matches);
    $rootName = $matches[1];

    preg_match("#^\\<{$rootName}\\>(.*)\\<\\/{$rootName}\\>#smu", $xml, $matches);
    $content = trim($matches[1]);

    while (true) {
        preg_match('#^\<(\w*)\>#smu', $content, $matches);
        $elementName = $matches[1];

        preg_match("#^\\<{$elementName}\\>(.*)\\<\\/{$elementName}\\>#smu", $content, $matches);
        $value = trim($matches[1]);

        if (preg_match('#^\d+$#', $value)) {
            $value = (int)$value;
        } elseif (preg_match('#^true|false$#', $value)) {
            $value = ($value === 'true' ? true : false);
        }

        $xmlObj->$elementName = $value;
        $content = trim(substr($content, strlen($matches[0])));

        if ($content === '') break;
    }

    return $xmlObj;
}

$xml = '
    <root>
      <id>123</id>
      <firstName>test</firstName>
      <lastName>true</lastName>
      <isVerified>true</isVerified>
    </root>
';

$xmlObj = parseXml($xml);

var_dump($xmlObj);
/*
class stdClass#1 (4) {
  public $id => int(123)
  public $firstName => string(4) "test"
  public $lastName => bool(true)
  public $isVerified => bool(true)
}
*/

class User
{
    public string $lastName;
    public bool $isVerified;
}

$user = new User();
$user->isVerified = $xmlObj->isVerified;
$user->lastName = $xmlObj->lastName;

// PHP Fatal error:  Uncaught TypeError: Typed property User::$lastName must be string, bool used

Но у вас же вон написано string, bool, и так далее!

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

Ну так и в языке со статической типизацией у вас будет что-то такое:


data Node = TextNode Text
          | NumberNode Number
          | CDataNode Blob
          ...

parseXML :: ByteString -> XML
parseXML = ...

findNode :: XML -> Selector -> Maybe Node
findNode = ...

doSmth :: XML -> Maybe Number
doSmth xml =
  case (findNode "foo/bar", findNode "baz/quux") of
       (Just (NumberNode n1), Just (NumberNode n2)) -> Just (n1 + n2)
       _ -> Nothing

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


Впрочем, и настолько выразительные системы типов не нужны: я могу в своём менее выразительном хаскеле написать


import qualified Data.Aeson as A

data MyData = MyData
  { lastName :: String
  , isVerified :: Bool
  } deriving (Generic, A.FromJSON, A.ToJSON)

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

Ну так и в языке со статической типизацией у вас будет что-то такое

Ну напишите что-то такое на C++, C# или Java.


data Node = TextNode Text | NumberNode Number | CDataNode Blob

А где тут string, int и boolean, чтобы можно было задавать значения полям класса с такими типами?
И вы как-то реализацию parseXML многозначительно за многоточием спрятали, и вообще она у вас не используется.


я могу в своём менее выразительном хаскеле написать
A.FromJSON

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


MyData { lastName :: String, isVerified :: Bool }

Но это, как я понимаю, и есть описанный предварительно специальный класс. В моем примере класс User нужен просто для демонстрации, без него парсинг тоже нормально работает. Вместо него $xmlObj->lastName можно было передать в функцию, принимающую string.

Ну напишите что-то такое на C++, C# или Java.

После того, как вы перепишете ваш пример на питоне и сохраните рантайм-проверки аннотаций типов :]


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


А где тут string, int и boolean, чтобы можно было задавать значения полям класса с такими типами?

Ну вместо String там Text, например. Можно и булей напихать. Мне было лень писать все варианты, да и с XML я всерьёз последний раз работал очень давно.


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

Это просто type-driven development. Смысл в том, что у вас есть какая-то функция parseXML, которая берёт байты и возвращает какой-то непрозрачный (уау инкапсуляция в ФП) объект (хотя мне надо было написать, конечно, -> Either ParseError XML), не зная ничего о конкретных типах данных в нодах. Дальше есть функция findNode, которая берёт этот объект и какой-то способ указать на интересующую ноду (XPath, например, или CSS-селекторы, неважно) и возвращает эту ноду (или Nothing), снова всё ещё не зная ничего о конкретных типах. А вот дальше вы проверяете конкретный тип конкретной ноды в конкретном контексте, который вам нужен, и, если всё хорошо, и вы ожидаете там числа, и там на самом деле числа, вы их складываете, а иначе делаете что-нибудь другое.


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

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


В моем примере класс User нужен просто для демонстрации, без него парсинг тоже нормально работает.

Парсинг и в Value (аналог Node из примера выше) работает точно той же функцией.

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

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


Ну вместо String там Text, например.

А, ок, я подумал, что TextNode это тип, а Text это название поля.


А вот дальше вы проверяете конкретный тип конкретной ноды в конкретном контексте, который вам нужен

Ну а в моем примере не надо проверять, в том и смысл.


Такие проверки


case (findNode "foo/bar", findNode "baz/quux") of
       (Just (NumberNode n1), Just (NumberNode n2)) -> Just (n1 + n2)

это и есть "можно, но это сложнее", и вы не можете просто писать
int sum = findNode(xml, "foo/bar") + findNode(xml, "baz/quux").
В C++ будут такие же проверки типа ноды
if (node1->type == TYPE_NUMBER && node2->type == TYPE_NUMBER) sum = (*(int*)node1->pValue) + (*(int*)node2->pValue);.
Я именно их и подразумевал. И поэтому проще заранее знать "какие ноды что хранят" и "приводить текст ноды к желаемому типу" из строки по месту
int sum = toInt(xml->getNodeValue("foo/bar")) + toInt(xml->getNodeValue("baz/quux")).


Так в том и суть: её написать можно, даже в языке со статической типизацией,

Такую, чтобы не писать везде "case of Just NumberNode" нельзя. В том и была суть. Такие проверки это фактически и есть реализация динамической типизации.
Это не "парсить в int/string/bool и складывать в ассоциативный map", а "парсить в NodeType и складывать в ассоциативный map<string, NodeType>". Со вторым я и не спорил, что это возможно.


это ж не предоставляемый компилятором интринсик.

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

Ну а в моем примере не надо проверять, в том и смысл.

Да, и при этом в компилтайме вы не узнаете, что вы делаете ерунду.


В C++ будут такие же проверки типа ноды

Именно что не такие же. В C++ компилятор не ударит меня по рукам за


if (node->type == TYPE_NUMBER)
  useAsBool(*(bool*)node->pData))

В примере выше же это сделать физически невозможно.


И поэтому проще заранее знать "какие ноды что хранят" и "приводить текст ноды к желаемому типу" из строки по месту

Ну, да. Так это вроде не противоречит моему исходному тезису, разве нет?


Такую, чтобы не писать везде "case of Just NumberNode" нельзя. В том и была суть. Такие проверки это фактически и есть реализация динамической типизации.

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


Это не "парсить в int/string/bool и складывать в ассоциативный map", а "парсить в NodeType и складывать в ассоциативный map<string, NodeType>". Со вторым я и не спорил, что это возможно.

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


Schema : Type
Schema = String -> Maybe Type

-- реализуется так:
exampleSchema : Schema
exampleSchema "foo/bar" = Just Int
exampleSchema "foo/baz" = Just Bool
exampleSchema _ = Nothing

SchematizedXML : Schema -> Type
SchematizedXML schema = Map String (\s => schema s)

parseXML : ByteString -> (s : Schema) -> Either ParseError (SchematizedXML s)
parseXML = ...

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

Да, и при этом в компилтайме вы не узнаете, что вы делаете ерунду.

В вашем коде проверка case of NumberNode выполняется в рантайме, так же как и в PHP. В плане проверки типов тут вообще разницы нет. Она будет только если lastName будет иметь тип, отличный от string | int | bool, тогда да, в Haskell компилятор скажет, что такого типа нет в списке возможных типов ноды, поэтому присвоить значение нельзя. Но мы же не об этом случае говорим.


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

Ну вот в PHP его можно не писать, и все равно иметь проверки типов во всех местах использования.


Так это вроде не противоречит моему исходному тезису, разве нет?

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

В вашем коде проверка case of NumberNode выполняется в рантайме, так же как и в PHP.

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


Ну вот в PHP его можно не писать, и все равно иметь проверки типов во всех местах использования.

Но вы там явно расставляете аннотации string/bool/etc, разве нет?


Но со статической именно в таком виде сделать нельзя

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


В конце концов, можно написать несколько функций типа asString :: Node -> Maybe Text, и тогда код может выглядеть как


doSomething :: XML -> Maybe Result
doSomething xml = do
  userName <- asString =<< findNode "foo/bar" xml
  isVerified <- asBool =<< findNode "baz/quux" xml
  ...

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

Но вы там явно расставляете аннотации string/bool/etc, разве нет?

Ну да, проверка типов же подразумевает что типы указаны в 2 местах, и мы их сравниваем. Только писать "тип данных вроде того, что выше" не нужно, объектом такого типа данных является сам xmlObject. Никакого объединенного типа NodeType = string|int|bool в коде программиста не существует, он работает сразу с конкретными типами.


можно написать несколько функций типа asString :: Node -> Maybe Text
isVerified <- asBool =<< findNode "baz/quux" xml

Это и есть "при парсинге надо приводить текст ноды к желаемому типу. Т.е. программист должен заранее знать, что нода с именем IsVerified — хранит булево значение, а LastName — строковое".


Чем она плоха?

Тем, что тип значений не string или bool, а NodeType, который type union всех возможных, и поэтому конкретный тип надо проверять вручную через case of.
Я же говорил не о том, что парсить XML со статической типизацией вообще невозможно, а что нельзя это сделать так же просто и наглядно, как с динамической.


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


declare(strict_types=1);

class User {
    public string $lastName;
}

function checkLastName(string $lastName) {
    return true;
}

function run(object $jsonObj) {
    $user = new User();
    $user->lastName = $jsonObj->lastName;
    checkLastName($jsonObj->lastName);
}

function parseJson(string $json): object {
    return json_decode($json);
}

$json = '{
    "id": 123,
    "firstName": "test",
    "lastName": true,
    "isVerified": true
}';
$jsonObj = parseJson($json);

run($jsonObj);

// TypeError: Typed property User::$lastName must be string, bool used 

Напишите полностью аналогичный код на Haskell. Критерии — parseJson() возвращает объект, он передается в другую функцию, его свойство используется и при установке свойства в объекте User, и при передаче в функцию checkLastName(), без повторного парсинга или объявления промежуточного класса с известной структурой. Структура User отличается от структуры JSON, а ошибка из статьи воспроизводится, так как в JSON lastName имеет тип bool. До checkLastName() в этом коде выполнение не доходит, но если закомментировать предыдущую строчку, там тоже будет ошибка типов.
Так будет проще сравнить объем кода и где какие проверки.

Никакого объединенного типа NodeType = string|int|bool в коде программиста не существует, он работает сразу с конкретными типами.

Как и я в последнем предыдущем примере. У меня там NodeType нигде не упоминается.


При этом я могу написать что-то вроде


findNode "quux/bar" xml >>= \n -> msum [asString n, toISO8601 <$> asDateTime n]

чтобы попытаться проинтерпретировать ноду как строку, а, если не получится, как дату, и сконвертировать в ISO8601 (получив снова строку). Как вы это сделаете на php?


Это и есть "при парсинге надо приводить текст ноды к желаемому типу.

Именно что не при парсинге, а после. XML ­в примерах выше — это тип, представляющий уже распаршенный XML, парсер по нему ещё раз не прогоняется. findNode берёт этот распаршенный XML (как DOM, например), находит там нужную ноду и возвращает её, по большому счёту всего лишь бегая по уже построенному дереву.


Т.е. программист должен заранее знать, что нода с именем IsVerified — хранит булево значение, а LastName — строковое".

В момент использования, да. Что не отличается от соответствующего знания вашего php-программиста в момент написания класса User.


Тем, что тип значений не string или bool, а NodeType, который type union всех возможных, и поэтому конкретный тип надо проверять вручную через case of.

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


Напишите полностью аналогичный код на Haskell. Критерии — parseJson() возвращает объект, он передается в другую функцию, его свойство используется и при установке свойства в объекте User, и при передаче в функцию checkLastName(), без повторного парсинга или объявления промежуточного класса с известной структурой.

Убрал аннотации типов, где компилятор может однозначно вывести их сам, и импорты модулей (чтобы их длиной не меряться), но позволил себе добавить параметр в run:


data User = User { lastName :: T.Text }

checkLastName t = T.length t > 10

run keyName obj = do
  lastName <- maybeToRight "key not found or has wrong type" $ obj ^? key keyName . _String
  let user = User lastName
  pure $ checkLastName lastName

parseJson :: BSL.ByteString -> Either String A.Value
parseJson = A.eitherDecode

json :: BSL.ByteString
json = [i|
{
    "id": 123,
    "firstName": "test",
    "lastName": true,
    "isVerified": true
}
|]

Результат:


λ> parseJson json >>= run "firstName"
Right False
λ> parseJson json >>= run "lastName"
Left "key not found or has wrong type"

Если хочется сделать сообщение об ошибке более говорящим, проще всего разбить на два этапа, переписав функцию run примерно так:


run keyName obj = do
  lastNameObj <- maybeToRight [i|key #{keyName} not found|] $ obj ^? key keyName
  lastName <- maybeToRight [i|key #{keyName} has unexpected type|] $ lastNameObj ^? _String
  let user = User lastName
  pure $ checkLastName lastName

и в итоге будет:


λ> parseJson json >>= run "firstName"
Right False
λ> parseJson json >>= run "lastName"
Left "key lastName has unexpected type"
λ> parseJson json >>= run "foobar"
Left "key foobar not found"

Впрочем, на хаскеле так никто не пишет: если у вас есть определённая схема в точке использования (вот этот вот User) то можно использовать ту же библиотечную eitherDecode, чтобы парсить напрямую в него, и эта функция при этом о пользователе, очевидно, всё ещё ничего не знает (я не пересобирал библиотеку для добавления User, честно-честно):


data User = User { lastName :: T.Text } deriving (Generic, A.FromJSON)

checkLastName t = T.length t > 10

run user = checkLastName $ lastName user

в результате:


λ> run <$> A.eitherDecode "{ \"lastName\": true }"
Left "Error in $.lastName: parsing Text failed, expected String, but encountered Boolean"
λ> run <$> A.eitherDecode "{ \"lastName\": 123 }"
Left "Error in $.lastName: parsing Text failed, expected String, but encountered Number"
λ> run <$> A.eitherDecode "{ \"lastName\": \"meh\" }"
Right False
λ> run <$> A.eitherDecode "{ \"firstName\": \"meh\" }"
Left "Error in $: parsing User(User) failed, key \"lastName\" not found"

В самом деле, зачем вам в вашем клиентском (для библиотеки) коде сначала руками парсить XML или жсон в какой-то объект, потом руками из него что-то доставать, если можно сразу распарсить в нужную структуру? Это на порядки удобнее.


До checkLastName() в этом коде выполнение не доходит, но если закомментировать предыдущую строчку, там тоже будет ошибка типов.

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

попытаться проинтерпретировать ноду как строку, а, если не получится, как дату
Как вы это сделаете на php?

Так в PHP не надо это делать, в дереве xml уже готовые для использования типы, а не обобщенный NodeType. Я не очень понял почему нода может быть не строкой, если в xml изначально все значения тегов строковые, но если вы подразумеваете, что там после разбора будет int timestamp, который можно конвертировать в дату, то на PHP будет такая же проверка типов.


$value = is_string($xml->quux->bar) ? $xml->quux->bar : (new DateTime('@' . $xml->quux->bar))->format('Y-m-d H:i:s');

Реализация findNode для конкретного значения ничем не отличается от задания специального класса-схемы с геттером, я и говорю, что в PHP это не нужно.


Именно что не при парсинге, а после.

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


Что не отличается от соответствующего знания вашего php-программиста в момент написания класса User.

Отличается. В момент написания класса User вообще может еще не быть никакой работы с XML. В том утверждении речь идет про ноды в XML, а не про свойства класса. И класс User не нужен, я могу просто сделать var_dump(), и он покажет, что в одном свойстве string, а в другом bool.


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

Так в том и смысл. Технические детали скрыты от программиста, так же как машинные команды или счетчик в foreach ($list as $element). Зачем мне работать с обобщенным NodeType, если мне нужны строки и инты. Язык с динамической типизацией скрывает детали реализации, которые со статической программист пишет сам.


В самом деле, зачем вам в вашем клиентском (для библиотеки) коде сначала руками парсить XML или жсон в какой-то объект, потом руками из него что-то доставать, если можно сразу распарсить в нужную структуру?

Затем что не всегда в точке парсинга есть нужная структура, а в точке использования структуры зависимость для парсинга. И если так делать, то слишком много зависимостей по коду надо раскидывать. А так у нас в место использования передается ассоциативный массив, который вы можете хоть парсингом XML получить, хоть парсингом JSON, хоть вручную в тестах захардкодить.


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

Это было просто пояснение про работу этого кода. Но я не понимаю, что вы имеете в виду. У вас точно так же ошибка возникает в рантайме, когда вы запускаете parseJson json >>= run "lastName", и выдает только ошибку про первую строчку Left "key lastName has unexpected type".


lastName <- maybeToRight "key not found or has wrong type" $ obj ^? key keyName . _String

Ну вот вы и нарушили критерий "его свойство используется и при установке свойства в объекте User, и при передаче в функцию checkLastName()". Вы используете не свойство объекта, а промежуточную переменную. Причем сделали кастинг к заранее известному типу string, это нарушает другой критерий "без объявления промежуточного класса с известной структурой". Формально это не класс конечно, а можно сказать только одно поле, но принципиально это ничего не меняет.


Полный эквивалент был бы такой.


let user = User (obj ^? key keyName)
pure $ checkLastName (obj ^? key keyName)

Вот как раз про это я и говорил, что в языках со статической типизацией так написать нельзя.


lastName <- maybeToRight [i|key #{keyName} has unexpected type|] $ lastNameObj ^? _String

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

То есть, если подытожить, проблема не в том, что обобщённую функцию парсинга написать нельзя, а в том, что надо будет руками написать string не только в объявлении функции, работающей с данными, но и в месте, где результат парсинга передаётся в эту функцию?

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


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


Ну и саму реализацию функции парсинга можно отметить. Со статической типизацией ее объем будет гораздо больше.

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

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


Ну и саму реализацию функции парсинга можно отметить. Со статической типизацией ее объем будет гораздо больше.

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

В C++ будут такие же проверки типа ноды
if (node1->type == TYPE_NUMBER && node2->type == TYPE_NUMBER) sum = (*(int*)node1->pValue) + (*(int*)node2->pValue);.
Я именно их и подразумевал. И поэтому проще заранее знать «какие ноды что хранят» и «приводить текст ноды к желаемому типу» из строки по месту
int sum = toInt(xml->getNodeValue(«foo/bar»)) + toInt(xml->getNodeValue(«baz/quux»)).

В c++ это тоже реализуется елементрано. Достаточно лишь немного углубиться в ООП.
Например: создаём абстрактный класс Node, с множеством методов:
asString, asBool, asInt, и так далее.
Далле создаём наследуемые классы для каждого типа данных (StringNode, BoolNode,...), и в них реализовываем преобразования в соответствующих методах. Если преобразовать указаную ноду в тот или иной тип невозможно, то посылаем исключение. В результате вышеописаный код можно изобразить так:
try{
    int sum = node1->asInt() + node2->asInt();
    ...
}catch(...){
    cout << "Type error";
}

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

Это не то же самое: по типу функции asInt совершенно не видно, что она может бросить исключение.

Это уже касается документации. Это лишь пример, как можно свести проверки типов в языках с статической типизацией к минимуму.
Кто-то в шутку сказал, что самым быстрым решением для Рэйчел будет выйти замуж и взять фамилию супруга, но предостерегли, что могут быть проблемы, если у супруга будет фамилия «Null» или «Drop Table».


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


Интересно, а почему она связывалась с представителями Apple, а не с представителями суда? Деньги снимают, но услугу не оказывают. Ладно у нас судиться не любят и не хотят, а там-то… Глядишь, после иска сразу и баг бы нашли, и починили бы в момент.
Юрист стоит 200$ в час, не все хотят покупать такой лотерейный билетик.
Вот тут немного не соглашусь. Фамилия правильная? Правильная, вот паспорт (или что у них там вместо), в нём написано. Вот договор оказания услуг. Вот факты списания платы по договору. А вот факт неоказания услуг по вине исполнителя – вот ошибка, вот обращение в ТП, вот ни фига не устранено и оплаченной услугой пользоваться невозможно. Так что было бы желание, а судебные издержки оплачивает ответчик.

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

Вроде как в США с оплатой судебных издержек всё гораздо сложнее. И в большинстве случаев они либо вычитаются из суммы штрафа (который наврядли будет сильно больше, чем стоимость тарифа на полгода в iCloud), либо оплачивается подзащитным независимо от результатов решения.
Действительно, а вдруг и в суде софт с криворуко использованной слабой типизацией, и все иски от фамилли «True» автоматически считаются выигранными… Вот тогда у неё от женихов отбоя не будет!
Интересно, а почему она связывалась с представителями Apple, а не с представителями суда?

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

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

Эппл иногда реально пугает. С одной стороны, фокус на безопасность и конфеденциальность, с другой — такие детские ошибки.

Фокус на безопасность, это когда у них серер лёг и никакое ПО на "своём" Mac'е запустить было нельзя, т.к. на каждый клик надо разрешение?

Да, ошибка реально детская. Что за индусы там код ваяли?

Кто-то может пожалуйста обьяснить что выполняет код на втором скриншоте?
Это отлинтенный обфусцировнный код, но кое-что понять можно. Перед нами методcreateFromResponse класса AuthenticatedUserDetails, который собственно, создает объект из данных реквеста, копируя из него определенные поля, указанные в f и m (dsid, lastname, firstname, и т.п.). И возращает этот новый объект.
Собственно все поля успешно присваиваются полем, кроме особого случая, когда эта поле равно строке 'true' (тогда присвоить true).
Насколько я могу судить строка "true" строго сравнивается со строкой "True". Вроде, никакого .toLowerCase() там нет… Похоже, что ошибка не в этом месте.
Ошибка весьма вероятно именно там где ей указали, т.к. она скорее всего вводит свою фамилию с маленькой буквы (либо lower срабатывает где-то раньше). Это видно по сообщению ошибки:
Type error: cannot set value ‘true’ to property ‘lastName‘
Вероятно, но не очевидно :)

Вот совсем не очевидно — буквально вчера очепятался в yml для пайплайна гитлаба, вместо reports:junit: написал reports:jUnit:. В ошибке получил "неопознанный ключ 'junit'"

До PHP ещё далеко.
Uncaught TypeError: Return value of func() must be an instance of boolean, boolean returned

Заголовок спойлера
(function (): boolean {
    return false;
})();

must be an instance of boolean, boolean returned

Функция должна вернуть объект boolean, а не скалярное булево значение. У php всегда свой путь и нужно указывать bool, а не boolean)
Лайфхак для Рэчел: написать в фамилии кириллическую «е»! :) Да и T сгодится )
Специально искал камменты как этот, чтобы не дублировать! Т.к. первый мой воркэраунд был этот!
Вот сделаешь так, забудешь через пару дней, а потом создатель сервиса выкатит очередную фичу, вроде покупки авиабилетов по данным аккаунта. И через пару лет безпроблемной жизни, при пересадке в какой-нибудь далёкой стране, данные в билете не сойдутся (по мнению компьютера) с данными паспорта. Будешь потом объяснять бдительному пограничнику что всё в порядке и он просто при поиске тебя в списке пассажиров должен набирать одну букву по-русски.
В авиабилетах все равно только латиница допустима, так что фейл будет уже на этапе покупки авиабилета.
А вообще, до трех ошибок в фамилии-имени относительно паспортных данных считается допустимым.
ID, другой ID, имя, фамилия, полное имя, локаль, код языка, AppleID… что из этого вообще может принимать булево значение? Зачем их вообще на «true» проверять?
Зачем их вообще на «true» проверять?

Оно само.

Для совместимости с легаси кодом наверно. Там же универсальный код, работает для любых свойств и объектов, видимо когда-то в каком-то свойстве true передавалось строкой.

Судя по коду, если вводить фамилию с большой буквы, то все будет норм
Главное в таком случае, чтобы на бэкэнде Пайтон не оказался, а то из огня да в полымя /s
Самое ужасное, что понадобилась публикация в интернете и сотрудник iCloud, который её заметил, чтобы пофиксить этот баг. А иначе полгода бы превратились в 10 лет.
Это показывает то, что поддержка в Эппл находится в таком состоянии, что любая нестандартная незаскриптованная проблема, особенно единичная просто не может найти свой путь к разработчикам. И это проблема не только Эппл.

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


1) Всем все по фиг. Серьезно работая в поддержке через 3 месяца ты уже ненавидишь людей и тебе на них плевать. А при попытках передать запрос выше от него тупо избавляются и говорят "иди нафиг"
2) Низкая квалификация. Люди с высокой в ТП долго не задерживаются. Они просто не вывозят это.
3) KPI и прочие радостные метрики, где запрос нужно решить за 15 секунд. Времени на подумать нет.


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

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

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


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

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

У самурая нет цели. Есть только путь)
Если вам честно интересно. То основной смысл, это снизить поток уровня "совсем бред" Где людям нужно просто поныть в свободные уши и психиатр до "бред вменяемый".


Квалификация же в этом случае меняется с "у меня есть 5 шаблонных ответов" на "у меня есть 10 шаблонных ответов".


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


Ещё раз ни один нормальный инженер не будет работать на поддержке в любом её виде. Если такой попадается это скорее исключение чем правило. А не инженер будет подобные вопросы игнорить и заруливать на шаблоны ответов. При этом совершенно искренне не понимая "А что он докопался".


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


Есть только один способ решить эту проблему. Это понизить зарплату айтишников до 10-30 тысяч рублей в России в среднем. (если говорить за наши компании) Так что бы инженерам было не западло работать с клиентами и что бы другой работы не было.


Я же уверен что если вам предложу работать в поддержке на любой линии. Тысяч на 70 даже (а это весьма много). Вы откажитесь как и 99% специалистов на хабре.

Похоже, нормальные ответы получить можно там где тонн бреда не ожидается, например можно создать issue в репозитории NGINX UNIT на GitHub и в рабочее время получить адекватный ответ, но там именно не ждут толп «домохозяек» и поток вопросов весьма небольшой (по сравнению с поддержкой массового продукта)

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


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

Ну GitHub как бы официальный канал для жалоб, вопросов и предложений. Просто вебсерверы обслуживает мало неквалифицированных людей
Ещё раз ни один нормальный инженер не будет работать на поддержке в любом её виде. Если такой попадается это скорее исключение чем правило. А не инженер будет подобные вопросы игнорить и заруливать на шаблоны ответов. При этом совершенно искренне не понимая «А что он докопался»
Инженер не должен «сидеть в техподдержке» в смысле разговора с пользователями. Задача техподдержки — оформить багрепорт для решения проблемы инженером. И не знаю, где и кем вы работали, но всю мою карьеру разработчика мне регулярно приходится заниматься багами, заведёнными техподдержкой по факту обращений пользователей.

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


Даже взять эту статью. Человек который поймет что проблема в базе и типах данных. Это уже даже близко не уровень поддержки. Такие люди скорее всего за копейки на этой работе работать не будут. На фига им это если с минимальными знаниями можно устроиться джуном и получать в разы больше с меньшим выносом мозга.

Найти место в коде — это уже часть решения. А техподдержка должна уметь просто заполнить стандартные поля «что делаем, что происходит, что должно происходить».
Спасибо, про шрифты не знал, любопытно!
Подобные баги указывают на возможность для SQL инъекций.
Если яблочники этого не понимают то это очень печально.
То есть среди всех миллионов пользователей айфонов, нашелся только один человек с фамилией True?
Хм, ну знаете, я пока тоже не встречал людей с фамилиями: Истина, Ложь (и Клади тоже), Правда и даже Проверка.
Вообще, True — это ещё и «верно»/«верный»/«правдивый».
Ну, и это вы просто говорите на языке менее производном от латыни, а так-то вы наверняка хотя бы одну Веру знаете.
А не обязательно иметь такую фамилию, чтоб огрести проблем на ровном месте.Достаточно быть Струевым

lpt без номера — не системное устройство — они нумеруются lpt1:..lptN: (как и comN:)
А вот con и prn — вполне себе ненумеруемые...

Ей надо было в баг баунти зарегистрироваться…
Видимо не смогла, ошибка ))

Напомнило фильм Идиократия, где одному парню присвоили имя You Sure .

На реддите предлагают ей добавить к фамилии .toString()
Only those users with full accounts are able to leave comments. Log in, please.