LINUX.ORG.RU

Racket 8.0

 , ,


0

2

Вышла новая мажорная версия языка программирования Racket, основанного на Scheme и нацеленного на создание пользователем собственных DSL.

  • Завершён переход на среду исполнения Chez Scheme — таким образом удалось уменьшить объём генерируемого кода на величину от 10 до 30%, а также значительно ускорить выполнение программ и повысить эффективность.
  • Переписан движок среды тестирования программ.

>>> Подробности

★★★

Проверено: atsym ()

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

Конструктор реализовал, осталось дело за малым: инстанс Contravariant (Op a), даже не прошу все инстансы. Дело в том, что без инстансов, твой тип - пшик. Вся сила hs в готовых инстансах. Но даже, если каким-то чудом тебе это удастся, то всё равно толку будет не много, тк монадические законы не соблюдаются.

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

Так о том и речь, что типы не особо мешают, а только помогают.

Бывают языки типа паскаля или старого C++ (до введения auto). Там короткие программы со сложными типами писать грустно.

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

Понятие инстанс существует только в Haskell.

Если брать реализацию

instance Contravariant (Op a) where
  contramap f g = Op (getOp g . f)

То так и будет

(: contramap (All (a b) (-> a b) (Op b) (Op a)))
(define (contramap f g)
  (Op (compose (Op-get-op g) f)))
monk ★★★★★ ()
Ответ на: комментарий от anonymous

Даже в ts, где вроде бы они есть, на самом деле - это типы, придуманные от балды <T extends unknown[]>

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

Очень многим они мешают.

Ну я говорю за себя.

DarkEld3r ★★★★★ ()
Ответ на: комментарий от monk
class Contravariant f where
  contramap :: (a -> b) -> f b -> f a
  (>$) :: b -> f b -> f a 

Реализуя Contravariant для Op, реализуем только contramap ++ бонусом имеем бесплатную::(>$) реализацию

(>$) :: b -> f b -> f a 

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

Рэкет (пылесос) стоит так слабо, что с первого щелчка вышибло ум у старика как и у .ts (как альтернатива purescript (очень слабые доки) ++ scalajs ); .ml и .scala с третьего (третья категория из них имеет наиболее сильные типы, но классы разрушают всю монадическую систему).

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

типы не особо мешают, а только помогают

Мешают, когда данные в тип не укладываются.

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

(print (+ 5 
          (vector-ref (read-json (open-input-file "data.json"))
                      3)))

?

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

Реализуя Contravariant для Op, реализуем только contramap ++ бонусом имеем бесплатную::(>$) реализацию

Так она однострочник:

(define >$ (compose contramap const))

Сама система типов в Haskell достаточно ограниченная, но именно эта ограниченность позволяет для каждого значения однозначно определять тип (одно значение в нём не может принадлежать разным типам). А это, в свою очередь, позволяет делать диспетчеризацию по типам значений.

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

знакомая песня, когда-то она звучала так: «только FoxPro форэва, и никаких с++ и паскалей!»

Нет, это скорее обратная ситуация.

Но даже так не похоже: использовать более C++ древнее C++11 можно разве что вынужденно.

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

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

Applicative g => Biapplicative (Joker g :: * -> * -> *)

а потом посмотрим, как он прогнётся под монадический закон!!

a *>> b ≡ bimap (const id) (const id) <<$>> a <<*>> b

и про бонусные начисления из пенсионного фонда не забывай

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

Например, вот так:

println!("{}", 5 + from_str::<Value>(&read_to_string("data.json")?)?[3].as_u64().ok_or("invalid number")?);

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

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

Мешают, когда данные в тип не укладываются.

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

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

from_str::(&read_to_string(«data.json»)?)?[3]

from_str::? Это точно работает? А как оно получает массив (или нечто с индексом)?

Если у нас устраивает, что где-то строка (слишком общий тип), то и ладно.

Это хорошо, когда всё укладывается в строку. Но ведь есть ещё массивы, соответствия, объекты GUI…

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

Было:
Функция Обработка(Данные)
...

Стало
Функция Обработка(Данные0)
  Если ТипЗнч(Данные0) = Тип("Структура") Тогда
    Данные = Данные0.Старые;
  Иначе
    Данные = Данные0;
  КонецЕсли
...
  Если ТипЗнч(Данные0) = Тип("Структура") Тогда
    ВыполнитьДопонительноеДействие(Данные0.Дополнительные);
  КонецЕсли;
...

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

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

А статика заставила бы чисто сделать.

Как? Эта функция вызывается из пары десятков мест. «Данные» имеют некий сложный тип (массив кортежей ещё чего-то).

Предположим, до изменений тип был Обработка :: Data -> Boolean

Как сделать, чтобы из трёх мест можно было запустить с типом Обработка :: (Data, String) -> Boolean?

Или «чисто» = скопировать целиком функцию Обработка в Обработка2, ,поправить в Обработка2 тип и две строки? А потом не забывать отслеживать все комиты, чтобы все изменения в Обработке переносить в Обработку2.

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

from_str::? Это точно работает? А как оно получает массив (или нечто с индексом)?

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

Это хорошо, когда всё укладывается в строку. Но ведь есть ещё массивы, соответствия, объекты GUI…

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

Если бы 1С был типизированный, то пришлось бы копипастить функцию

Вероятно, не понял пример, но меня он не убедил. (:

Написать такой ужас можно и с типами:

enum Data {
    SomeData,
    OtherData,
}

fn foo(data: Data) {
    match Data {
        ...
    }
}

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

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

Эта функция вызывается из пары десятков мест.

Ага и потом все эти десятки мест обрастут проверками типов.

Не понимаю почему нельзя добавить функцию ДополнительнаяОбработка, которая принимала бы (Data, String) делал бы дополнительные действия и вызывала обычную обработку.

Ну или я всё-таки не понял твою мысль.

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

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

  • data.json, корнем в котором не является массив – это ограничение вводит vector-ref
  • data.json, третьим элементом корня которого является не число – это ограничение вводит +

Конечно, в статике мы бы на первый раз скастовали ввод в условный number[], но даже если предположить, что другие элементы массива могут иметь другой тип, то ситуация не изменится: тип других элементов попросту не будет иметь значения. number[] будет корректно отражать фактическое поведение программы.

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

Монадические законы выполняются -> грязи нет.

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

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

Монадические законы выполняются -> грязи нет.

Хаскель не следит за выполнением монадических законов. Значит грязный?

Хватит уже нюхать дерьмо

Не учи других, что нюхать, пока сам в дерьме.

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

hs это реализация монадических законов для мира агрессии.

Это не реализация монадичесих законов. Это всего лишь реализация категории hask (вроде так называется), как языка. А монадические законы более-менее реализованы силами разработчиков только для некоторых типов, а не самим языком.

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

Ага и потом все эти десятки мест обрастут проверками типов.

Поэтому они должны иметь возможность вызывать функцию со старым типом.

Не понимаю почему нельзя добавить функцию ДополнительнаяОбработка, которая принимала бы (Data, String) делал бы дополнительные действия и вызывала обычную обработку.

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

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

А мы разве конкретно в этой ситуации получаем какие-то профиты от динамики?

Да. Мы избегаем необходимости писать .as_u64().ok_or(…).

Понятно, что data.json не произвольный.

Конечно, в статике мы бы на первый раз скастовали ввод в условный number[], но даже если предположить, что другие элементы массива могут иметь другой тип, то ситуация не изменится: тип других элементов попросту не будет иметь значения

Так он же их сначала прочитает. И вылетит на ошибке проверки типа. Поэтому придётся не number[], а некий jsvalue[] делать.

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

Учат в школе КГБ (нейрогенератор следит за тобой, чтобы не перегрелся), здесь используется декларативный подход. Вот-вот твое дерьмо закончится и тогда ты будешь на коленях умолять (спасителя, который про тебя забыл), чтобы Alonzo лично проломал твой гнилой череп.

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

А каким образом вылет на .ok_or(…) будет отличаться в плане фактических последствий (не считая распаковки, см. ниже) от вылета на (+), которому пришло явно не число? Круто, конечно, не набирать N символов на клавиатуре, количество которых пусть и варьируется, но не сильно – оно всегда будет относительно низким (... :: Int[], или ... as number[], например), но это не то что бы аргумент.

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

придётся не number[], а некий jsvalue[] делать

Это в тех языках, где для работы с JSON нужно положить значение в контейнер, типа Haskell и OCaML. Работа с JSON, аналогичная соответствующему пакету в Racket, вполне себе делается в той же Scala:

import nl.typeset.sonofjson._

val person = = parse("""{ "name" : { "first": "John", "last": "Doe" } }""")
val name: String = person.name.first

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

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

Вот это выпадает из стилистики повествования:

здесь используется декларативный подход

Есть еще над чем работать.

Лучше генерь инженерные вопросы про охлаждение переливанием горячего самогона для наилучшего вкуса чешуекрылых в 2047.

anonymous ()
Ответ на: комментарий от Princesska

А каким образом вылет на .ok_or(…) будет отличаться в плане фактических последствий (не считая распаковки, см. ниже) от вылета на (+), которому пришло явно не число?

Никаким. Но если поставить тип number[], то вылетит, если, например первый элемент массива не число. А (+) проверяет только свой аргумент.

а некий jsvalue[] делать … вполне себе делается в той же Scala:

В ней JValue

  case class JArray(elements: mutable.Buffer[JValue]) extends JValue

Но синтаксически, согласен, более симпатично.

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

аналогичная соответствующему пакету в Racket, вполне себе делается в той же Scala

Из-за того, что это не массив, а свой тип не сработает, например,

val person = parse("[1,2,3]").map(_ * 2)

Хотя если бы возвращался Array, то сработало бы.

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

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

Ладно, но меня не оставляет ощущение, что такой код попахивает.

И всё равно не уверен, что типизация тут проблемы создаёт. Если в языке есть опцинальные аргументы, то просто переделываем обработка(Data) в обработка(Data, String = ""). Менять все остальные места, где функция вызывается не нужно.

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

Если я правильно понимаю, то после этого все вызовы foo, вида foo(bar(…)?) придётся переписывать в foo(SomeData(bar(…)?)). Так?

В таком варианте - да. Но можно извернуться как-то так:

struct SomeData {}

enum Data {
    D(SomeData),
    S(String)
}

impl From<SomeData> for Data {
    fn from(d: SomeData) -> Self {
        Data::D(d)
    }
}

impl From<String> for Data {
    fn from(s: String) -> Self {
        Data::S(s)
    }
}

fn foo<T: Into<Data>>(data: T) {
    todo!()
}

И тогда в месте вызова ничего менять не надо.

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

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

Старый тип Data, новый тип (U Data (Pairof Data String)) и всё будет работать не хуже, чем в 1С.

Но сигнатуру функции таки придётся поменять? (:

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

DarkEld3r ★★★★★ ()