LINUX.ORG.RU

clojure после common lisp - насколько омерзительно и в чём именно?

 , ,


2

2

Всё ещё думаю о возможности заняться разработкой на clojure. Но ява же тормозная - меня бесит скорость сборки. Вот боюсь, как бы кложа не оказалась слишком уж тормозной.

Расскажите о своих ощущениях (из серии «собираюсь жениться - отговорите»).

★★★★★

Ответ на: комментарий от alienclaster

но 1) скорее, он все же выберет не сишарп и 2) если шарп это условие нанимателя

Я же написал реалистичное условие: уникальная библиотека.

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

Тогда статические типы начинают мешать. Потому что привычки делать проверки у программистов на статических языках нет и выбор типа делается не правильный, а из доступных. Много видел программ на Си, где проверяется, что enum аргумент действительно из допустимого диапазона?

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

Значит это будет просто статически типизированная часть программы.

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

monk ★★★★★
()
Ответ на: комментарий от alienclaster

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

Тот код, на котором анализ в питоне будет неполным, в C# или будет невозможен или будет использовать dynamic, из-за чего очевидный доступ по точке просто отвалится напрочь. В то время как в питоне или 1С выдаст объединение методов возможных типов.

monk ★★★★★
()
Ответ на: комментарий от monk

Я же написал реалистичное условие: уникальная библиотека.

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

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

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

Много видел программ на Си, где проверяется, что enum аргумент действительно из допустимого диапазона?

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

alienclaster ★★★
()
Ответ на: комментарий от monk

Динамический язык даёт больше свободы, так как позволяет в качестве интерфейсов указывать «отсортированный массив», «непустой список», «чётное число от 0 до 100», …

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

Речь о контрактах в Racket, которые позволяют такое легко реализовывать? Тогда ладно, хотя я не уверен, что оно принципиально отличается от асертов. Ну кроме наглядности.

Но разве в большинстве динамических языков не будет просто def foo(sorted_list)?

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

Тот код, на котором анализ в питоне будет неполным, в C# или будет невозможен или будет использовать dynamic, из-за чего очевидный доступ по точке просто отвалится напрочь.

Да от анализатора это все будет зависеть (от конкретной реализации). В худшем случае будет так же как питоне.

В то время как в питоне или 1С выдаст объединение методов возможных типов.

Что помешает сделать то же самое для статически типизированного языка на том же уровне? (как худший случай)

alienclaster ★★★
()
Ответ на: комментарий от DarkEld3r

Почему со статической типизацией нельзя такие интерфейсы использовать?

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

Всё перечисленное можно хоть на С++ сделать

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

про какой-нибудь Idris и не говорю.

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

alienclaster ★★★
()
Ответ на: комментарий от DarkEld3r

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

Для Idris можешь сделать тип для калькулятора из градусов Цельция в градусы Фаренгейта. Входящий тип - вещественное число не менее -273,15. А для C++ как?

Почему со статической типизацией нельзя такие интерфейсы использовать?

Потому что у интерфейса получается два типа: один из языка, другой реальный. А дальше варианты:

  • просто описать в документации и сделать проверку: в статических типах чаще смотрят на сигнатуру, чем читают документацию. История с memcpy как пример.

  • в C++ можно сделать класс с конструктором от типа из языка и проверкой при создании объекта. Получим лишнее усложнение программы. Почти никогда не видел, чтобы так делали.

  • расширить реальный тип до того, который есть в языке. Как сделано в примере.

Но разве в большинстве динамических языков не будет просто def foo(sorted_list)?

Будет. Но рядом будет описание и привычка программистов на этом языке к чтению этого описания. Вот посмотри описание типа функции subseq: http://www.lispworks.com/documentation/HyperSpec/Body/f_subseq.htm. И представь, как её опишут в любом языке со статической типизацией.

monk ★★★★★
()
Ответ на: комментарий от alienclaster

Что помешает сделать то же самое для статически типизированного языка на том же уровне? (как худший случай)

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

monk ★★★★★
()
Ответ на: комментарий от alienclaster

Можно, но они будут в рантайме.

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

Кстати в D тоже есть контракты, так что это не прерогатива динамически типизированных языков.

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

Ну почему? «Не пустой список» можно и на С++ изобразить. Другое дело, что так (обычно) никто делать не будет.

С помощью какого инструмента, метапрограммирования на темплейтах?

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

struct Node<T> {
    Node* next;
    T value;
}

struct List<T> {
    Node<T> head;
}

Ты хорошо знаешь идрис, агду, coq?

Нет. Вообще не знаю. Хотя Idris освоить хочу, может когда-нибудь руки и дойдут.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

А для C++ как?

Ну как… как обычно:

struct Celsius { ... }

impl Celsius {
    fn new(val: double) -> Option<Self> {
        if val < 273,15 {
            None
        } else {
            Self { val }
        }
    }
}

Это правда раст, но не суть. Ну и разве в Idris оно не примерно так же будет? Разницу между «формально доказано и проверяется компиляторов» против «ну мы тут тесты написали…» понимаю, но мы же об интерфейсах говорим. Да и надёжности оно всё-таки добавляет.

Kстати, именно для C++ уже полно написано подобных библиотек для физических величин. Так что аргумент «сделать можно, но так делать никто не будет» отпадает потому как даже не надо самому это всё городить. В бусте вон даже есть обобщённая библиотека под это дело, которая как раз свои произвольные величины позволяет определять. Правда я ей не пользовался, не знаю насколько она удобна.

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

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

Вот посмотри описание типа функции subseq: http://www.lispworks.com/documentation/HyperSpec/Body/f_subseq.htm. И представь, как её опишут в любом языке со статической типизацией.

Не понял пример. Что в этой функции богатых типов требует? end не должен быть меньше start и всё?

Но вообще как-то так: https://doc.rust-lang.org/std/ops/trait.Index.html

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

struct Celsius { … }

То есть вместо c2f(100) надо будет писать c2f(new Celsius(100)). И внутри функции ещё раз проверять, не прилетело ли None.

Причём тут даже хуже, чем наивное решение, так как если прилетело None, то сообщить, какое именно число прилетело, функция не сможет.

И много такого кода в реальных программах?

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

Отличный пример: https://www.boost.org/doc/libs/1_37_0/boost/units/base_units/temperature/conversions.hpp

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

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

Тут скорее вопрос про IDE. Для статического языка я увижу тип, для динамического — текстовое описание «типа» из тайпхинта.

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

Не понял пример. Что в этой функции богатых типов требует? end не должен быть меньше start и всё?

Тут всего два типа. И bounding index designator – не int.

proper sequence = a sequence which is not an improper list; that is, a vector or a proper list.

proper list = A list terminated by the empty list. (The empty list is a proper list.)

bounding index designator (for a sequence) = one of two objects that, taken together as an ordered pair, behave as a designator for bounding indices of the sequence; that is, they denote bounding indices of the sequence, and are either: an integer (denoting itself) and nil (denoting the length of the sequence), or two integers (each denoting themselves).

monk ★★★★★
()
Ответ на: комментарий от monk

Кстати, у динамических языков есть ещё один бонус. Пусть в статическом языке ты ввёл типы proper_list и improper_list. Но в динамическом я могу написать

(setf a '(1 2 3)) ; proper list
(setf (cdddr a) 4) ; теперь a improper list (1 2 3 . 4)

Как сменить тип в статическом языке у уже созданного объекта?

monk ★★★★★
()
Ответ на: комментарий от monk

И внутри функции ещё раз проверять, не прилетело ли None.

Это ещё зачем? Функция должна принимать строго Celsius - такой у неё контракт. Проверка должна быть там, где мы данные получаем (как можно раньше) - там и контекста для вывода и/или исправления ошибки будет больше.

И много такого кода в реальных программах?

Достаточно.

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

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

Возможно, но ты ведь согласен, что сделать это и в С++ можно? В бусте (как обычно) сделали максимально обобщённо. И я по прежнему считаю, что лучше так, чем double kelvin: хотя бы защита от сложения разных единиц будет.

Тут скорее вопрос про IDE. Для статического языка я увижу тип, для динамического — текстовое описание «типа» из тайпхинта.

Не уверен, что понял. Мне IDEA вполне себе показывает описание (документацию) методов, если я на них курсор навожу, а в написанном коде видны имена параметров (как они объявлены в функции).

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

Как сменить тип в статическом языке у уже созданного объекта?

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

Признаюсь: мысль по прежнему не понял.

И bounding index designator – не int.

and are either: an integer (denoting itself) and nil (denoting the length of the sequence), or two integers (each denoting themselves).

Понял ещё меньше.

Но вообще последовательность в типизированном языке может быть выражена через итератор: тогда не нужно делать различие «a vector or a proper list» (итератор можно получить у любого контейнера).

Индексы - да, будут интами. Хотя интерфейс в духе растового, как по мне, выглядит вполне приятно и типов хватает:

sequence.skip(3).take(10);
DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Это ещё зачем? Функция должна принимать строго Celsius - такой у неё контракт.

Затем, что создание объекта Celsius ничего не гарантирует:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c1663d759ac37fb805a157b494fccab0

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

Нет могу вообще в setf a не трогать

(setf a '(1 2 3)) ; proper list
(setf b (cdr a)) ; в b '(2 3)
(setf (cddr b) 4) ; теперь a improper list (1 2 3 . 4)

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

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

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

Хотя интерфейс в духе растового, как по мне, выглядит вполне приятно и типов хватает:

Только cons (то есть pair) в такой ситуации должен быть наследником sequence…

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

Какой итератор должен быть для структуры на cons’ах? maplist, maptree (и в этом случае порядок в ширину, в глубину?).

И что должен вернуть subseq если ему дадут дерево с итератором?

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

Вот ещё про динамические типы (по мотивам проблемы круга и эллипса)

В Racket можно использовать методы типов из GLS

(defmethod (scale! (r <ellipse>) (p double?) (q double?))
   ...)

(define <circle> (and? <ellipse> equal-axis?))

(defmethod (display-radius (r <circle>))
   ...)

(define d (make-ellispe 1 2))
(scale! d 2 1) ;; здесь d стал кругом
(display-radius d)
(scale! d 2 1) ;; здесь d больше не круг
(display-radius d) ;; здесь будет ошибка "метод не найден"

В Common Lisp можно в рамках ООП менять класс объекта: https://en.wikipedia.org/wiki/Circle%E2%80%93ellipse_problem#Convert_the_Circle_into_an_Ellipse

monk ★★★★★
()
Ответ на: комментарий от monk

Что помешает сделать то же самое для статически типизированного языка на том же уровне? (как худший случай)

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

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

alienclaster ★★★
()
Ответ на: комментарий от monk

Затем, что создание объекта Celsius ничего не гарантирует:

Так ты написал совсем другой (и странный) код. Нормальный будет выглядеть вот так:

struct Celsius(f64);
struct Fahrenheit(f64);

impl Celsius {
    fn new(val: f64) -> Option<Self> {
        if val < 273.15 {
            Some(Self(val))
        } else {
            None
        }
    }
    
    fn to_fahrenheit(&self) -> Fahrenheit {
        Fahrenheit((5.0/9.0) * (self.0 + 32.0))
    }
}

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

Как сделать, чтобы ссылка на новый тип совпадала со ссылкой на старый?

Ты ведь и сам знаешь, что никак?..

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

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

Какой итератор должен быть для структуры на cons’ах?

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

Но вообще не вижу ничего удивительного в том, что у разных языков будут разные подходы. Мы ведь начали с того как типизировать функцию subseq: в другом языке она может выглядеть совсем иначе. У нас большая часть разговора о том, что «в лиспе (или динамике) можно сделать Х, а в С++/хаскеле/статике нельзя». Ладно когда закоренелые сишники, впервые увидевшие раст, начинают негодовать видя «ограничения», но у тебя-то весьма широкий кругозор. Сам же о разнице между Common Lisp и Racket рассказывал (и да мне подход последнего нравится намного больше). Поговорить о преимуществах и недостатках языков интересно, но хочется видеть именно какие-то интересные возможности, а не трюкачество в духе while( *dest++ = *src++). Впрочем, не исключено, что это я отказываюсь видеть преимущества.

maptree (и в этом случае порядок в ширину, в глубину?)

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

И что должен вернуть subseq если ему дадут дерево с итератором?

Hовый итератор.

В расте именно итераторы используются как общий интерфейс «последовательности»:

let vec = vec![(1, 'a'), (2, 'e'), (3, 'z')];
let map: BTreeMap<_, _> = vec.into_iter().collect();
let vec: Vec<_> = map.into_iter().map(|(i, _)| i).collect();
DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

(display-radius d) ;; здесь будет ошибка «метод не найден»

Ошибка ведь будет в рантайме?

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

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Нормальный будет выглядеть вот так:

Так вообще не ругается:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f00e45053f2f75bbf3a631e828c0fdd0

Или приведи правильный main()

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

Паника на unwrap внутри библиотеки не совсем то, чего ожидает пользователь библиотеки. Если же вместо unwrap делать проверку на None, то с таким же успехом можно было делать проверку на 273.15 и не городить огород.

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

Если она ожидает нормальный, то в момент выполнения (check-type x list), а в SBCL также в случае (declare (type list x)).

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

Я же привёл пример. Как в Rust определить взаимоотношения между кругом и эллипсом? При том, что для круга могут быть свои функции.

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

Там поддержка монад в языке спасает. Рецепт не для всех.

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

Мы ведь начали с того как типизировать функцию subseq: в другом языке она может выглядеть совсем иначе. У нас большая часть разговора о том, что «в лиспе (или динамике) можно сделать Х, а в С++/хаскеле/статике нельзя».

Функция subseq была как иллюстрация того, что в динамическом языке типы API ограничены исключительно фантазией разработчика. Поэтому массово используются «обозначение функции» (символ или функция), «обозначение границы» (конечная может быть nil или число не более длины последовательности, начальная не более конечной), «обозначение класса» (символ или класс) и прочее. В типизированных языках разработчик мыслит категориями доступных типов. Поэтому в boost может быть отрицательная температура в кельвинах (нету неотрицательного вещественного типа), а в Haskell оператор доступа по позиции !! принимает отрицательный номер позиции.

В расте именно итераторы используются как общий интерфейс «последовательности»:

Не понял. Из любой последовательности subseq сделает массив? В лиспе в API subseq тип результата совпадает с типом последовательности.

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

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

Кроме того, типы динамического языка (проверяемые через check-type/declare) для конкретной программы часто можно гарантировать даже статически (пройдя все ветки выполнения). Также как в Си в куске кода

int a[10];

...
a[i] = 5
if (i > 15) { ... }

проверка i > 15 и весь код в ней будет выкинут, так как компилятор статически видит, что i не больше 9.

monk ★★★★★
()
Ответ на: комментарий от monk

Так вообще не ругается:

Дело в том, что в расте видимость (публичность) можно (нужно) ограничивать не на уровне типов, а нa уровне модулей.

Поэтому код целиком будет выглядеть вот так:

mod degree {
    pub struct Celsius(f64);
    
    #[derive(Debug)]
    pub struct Fahrenheit(f64);
    
    impl Celsius {
        pub fn new(val: f64) -> Option<Self> {
            if val > -273.15 {
                Some(Self(val))
            } else {
                None
            }
        }
        
        pub fn to_fahrenheit(&self) -> Fahrenheit {
            Fahrenheit((5.0/9.0) * (self.0 + 32.0))
        }
    }
}

pub use degree::*;

fn main()  {
    // Получаем откуда-то "сырые" данные:
    let val = 0.;
    // Преобразуем в температуру и обрабатываем потенциальные ошибки.
    let temperature = Celsius::new(val).unwrap();
    // Работаеm с типизированными данными.
    println!("{:?}", temperature.to_fahrenheit());
}

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

Да, но всё-таки это лучше, чем молча выдавать мусор.

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

Я же привёл пример. Как в Rust определить взаимоотношения между кругом и эллипсом? При том, что для круга могут быть свои функции.

Ответил ниже.

Там поддержка монад в языке спасает.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

Что помешает сделать то же самое для статически типизированного языка на том же уровне? (как худший случай)

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

Это опять вопрос реализации анализатора для языка, в которым нет Any (и такого анализатора хватает). Ничего не помешает статическому анализатору динамических языков доставать дополнительную информацию из декларации типов в статическом языке. Т.е. в _худшем_ случае будет как в питоне.

alienclaster ★★★
()
Ответ на: комментарий от monk

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

Формулировка кажется слишком категоричной, но согласен с тем, что в среднем проекте заморачиваться не будут. С этим я, вроде, вообще не спорил?.. Утверждаю только, что «сделать (многое) всё-таки можно» и «иногда делают».

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

А вот тут уже несколько скептически отношусь. И кажется, что тут есть некоторая подмена понятий: в одном случае мы говорим о типе, который статически проверяется, пусть и не полностью соответствует предметной области. А во втором в лучшем случае о рантайм проверках вида (check-type x list). Но как будто в статически типизированных языках не пишут асертов вида index > 0 && index < len. Не говоря уже о том, что типы, которые разработчик на динамическом языке нафантазировал, могут оставаться только у него в голове.

а в Haskell оператор доступа по позиции !! принимает отрицательный номер позиции

Вот это меня, кстати, удивляет. Ладно в хаскеле: всё-таки язык весьма старый и там есть ряд исторических кривостей. С некоторыми из них борются (скажем можно заменить Prelude и получить меньше частичных функций), с другими видимо проще смириться. Но вот в Idris, насколько я смог бегло нагуглить, хоть и есть Nat как беззнаковый аналог для Integer, но для Int ничего такого нет. Может «не нужно», но хотел бы послушать почему.

При этом в расте для все целочисельные типы в четырёх вариантах присутствуют: i8, u8, NonZeroI8, NonZeroU8. Вероятно, потому что два последних типа позволяют определённые оптимизации использовать.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Я не настоящий сварщик, но разве монады и иммутабельность вообще связаны?

Разумеется. Возьмём программу на Haskell

res = do
  ref <- newSTRef 0
  replicateM_ 1000000 $ modifySTRef ref (+1)
  readSTRef ref

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

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r

Почему со статической типизацией нельзя такие интерфейсы использовать?

Можно, но они будут в рантайме.

Так ведь контракты (насколько я понимаю, как они работают в Racket) точно так же будут проверятся в рантайме?

Да, в рантайме. Хотя с помощью макросов, наверное, можно и в компайлтайме даже что-то придумать.

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

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

Кстати в D тоже есть контракты, так что это не прерогатива динамически типизированных языков.

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

Ну почему? «Не пустой список» можно и на С++ изобразить. Другое дело, что так (обычно) никто делать не будет.

Угадай почему. И «непустой список» это самый примитив вообще.

С помощью какого инструмента, метапрограммирования на темплейтах?

Чтобы изобразить примитивный список никакого метапрограммирования не надо.

А чтобы изобразить сложные типы?

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

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

alienclaster ★★★
()
Ответ на: комментарий от monk

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

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

Кроме того, типы динамического языка (проверяемые через check-type/declare) для конкретной программы часто можно гарантировать даже статически

На мой взгляд это звучит так же убедительно как заверения, что если С/С++ обложить статическими анализаторами, санитайзерами и т.д., то результат ничем не будет отличаться от раста. Вроде да, но нет.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

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

И наоборот, если что-то приходится в языке делать разными типами, возникает ощущение, что это разные вещи. Например, для программистов на Haskell и Java int и Integer - разные вещи. А для программистов на динамических языках – одна. И наоборот, для программистов на статических языках температура в цельциях, длина объекта и географическая широта — один тип. Несмотря на то, что диапазон возможных значений в реальности существенно отличается.

monk ★★★★★
()
Ответ на: комментарий от DarkEld3r

Racket рассказывал (и да мне подход последнего нравится намного больше)

В Racket я сначала пишу алгоритм, а потом смотрю для какого типа он пригоден. В Haskell я сначала придумываю типы, а потом пишу алгоритм в рамках выбранных типов. Часто разница существенная.

monk ★★★★★
()
Ответ на: комментарий от monk

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

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

Что мешает делать это в лоб без монад? Ну создастся миллион новых объектов, ну будет тормозить, ну и что?

С тезисом, что в хаскеле монады позволяют сохранить «красоту модели», но при этом не (слишком) жертвовать эффективностью согласен. С тем, что не может быть иммутабельного языка без монад - нет.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

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

Программная модель «отдаленная от реальности» - это как минимум баг архитектура, а в целом - просто неверно работающая программа, т.е. мусор.

Или о том, что строгий компилятор отвергает некоторые корректные программы? Эту цену я готов платить.

Жоска.

На мой взгляд это звучит так же убедительно как заверения, что если С/С++ обложить статическими анализаторами, санитайзерами и т.д., то результат ничем не будет отличаться от раста. Вроде да, но нет.

А чем по-твоему является Rust как не С++ с немного измененным синтаксисом «обложенный анализаторами, санитайзерами и прочими верификаторами»?

alienclaster ★★★
()
Ответ на: комментарий от monk

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

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

alienclaster ★★★
()
Ответ на: комментарий от DarkEld3r

Что мешает делать это в лоб без монад? Ну создастся миллион новых объектов, ну будет тормозить, ну и что?

Да, я неправ. В Scheme тоже можно писать без set!

(let loop ([n 1000000] [res 0] [ref 0])
  (if (== n 0)
      res
      (loop (- n 1) (add1 res) ref))))

Чуть менее красиво разве что.

monk ★★★★★
()
Ответ на: комментарий от alienclaster

Я всё-таки не соглашусь, что типизация бесполезна при наличии контрактов.

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

«Продакшен» всё-таки не стоит на месте. На мой взгляд, мейнстримные языки вполне себе развиваются в правильную сторону (ну кроме Go), пусть и медленно. Думаю, что интерес к более мощным системам типов будет рости. Ну посмотрим со временем.

DarkEld3r ★★★★★
()
Ответ на: комментарий от monk

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

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

template <typename Value, typename Tag>
class Tagged;


struct АTag final{};
struct BTag final{};
...

using А = Tagged<double, ATag>;
using В = Tagged<double, ВTag>;
...

using Х = boost::variant<А, В, ...>;
using ХS = Vector<X>;

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

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

Всё-таки «разные типы» и «правильные ограничения типов» - это не одно и то же.

DarkEld3r ★★★★★
()
Последнее исправление: DarkEld3r (всего исправлений: 2)
Ответ на: комментарий от monk

В Racket я сначала пишу алгоритм, а потом смотрю для какого типа он пригоден. В Haskell я сначала придумываю типы, а потом пишу алгоритм в рамках выбранных типов. Часто разница существенная.

Разница есть, не спорю. Осталось доказать какой вариант лучше. (:

Как по мне, самое интересное начинается когда код написан, но надо внести правки. С типами (и чем они строже, тем проще) мне всё понятно: изменяем тип, получаем список мест, которые надо поправить. Дальше в зависимости от сути изменений. Если же это динамика (или какой-нибудь слишком общий тип), то остаётся полагаться на тесты. Так?

DarkEld3r ★★★★★
()
Ответ на: комментарий от alienclaster

А чем по-твоему является Rust как не С++ с немного измененным синтаксисом «обложенный анализаторами, санитайзерами и прочими верификаторами»?

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

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

DarkEld3r ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.