имхо, даже не разбираясь в лиспе, я понимаю, что макросы это круто, и с их помощью можно резко понизить сложность написания большой програмы и модульность даже с учетом динамики. Если бы в лиспе не было макросов он бы был не нужен, а так пусть себе существует, плохо разве только то, что он неадекватов плодит, которые не разбираясь в предмете на другие языки лезут.
что везде - то точно, но за лиспом какие-то сверхбуйные, нет бы сидели себе примус починяли, втолковывали другим, что те неправильно лисп понимают и объясняли как это делать на лиспе правильно, если другие не по делу наезжают.
Хаскелисты буйнее будут. Лисперы по крайней мере к другим не лезут, а вот хаскелисты чуть что - сразу тут как тут, срывать покровы и делиться своим сакральным знанием.
Это про неразмеченные объединения в смысле теории множеств (или объединения / fiber sum подобъектов в топосах), для двух типов A и B тип объединение A ∪ B это наименьший, в смысле предпорядка типов по отношению подтипирования, тип такой, что, разумным образом, A <: C и B <: C.
Pred : Set → Set₁
Pred A = A → Set
_∈_ : {A : Set} → A → Pred A → Set
x ∈ P = P x
_∈′_ : {A : Set} → Maybe A → Pred A → Set
nothing ∈′ _ = ⊥
just x ∈′ P = x ∈ P
record _⊆_ (A B : Set) : Set where
field
sup : A → B
sub : B → Maybe A
open _⊆_
_∪_ : {A B C : Set} ⦃ ↓a : A ⊆ C ⦄ ⦃ ↓b : B ⊆ C ⦄ → Pred A → Pred B → Pred C
_∪_ ⦃ ↓a ⦄ ⦃ ↓b ⦄ P Q = λ c → sub ↓a c ∈′ P ⊎ sub ↓b c ∈′ Q
если взять два ADT AB = {a, b} и BC = {b, c} с простейшим критерием населённости для Pred (отображение в ⊤), объединение Pred AB ∪ Pred BC неоднозначно и зависит от выбора типа C и функций задающих подтипирование AB <: C и BC <: С, то есть от C и equality для элементов AB, BC и C. Если взять C = {a, b, c} и посчитать равными только AB.b и BC.c, то можно получить с помощью _∪_ предикат применение которого к элементам C будет давать населённые ⊤ ⊎ ⊥, ⊤ ⊎ ⊤ и ⊥ ⊎ ⊤.
То есть в отличии от размеченных объединений ADT неразмеченные объединения неоднозначны, в общем случае — они могут быть однозначны для a ~ b => a ∪ b ~ a ~ b, например, или для «типов-множеств» с разными «подтипами-подмножествами» и наперёд заданным equality для элементов, например — bool, int, unsigned, integer, double, множества и прочие коллекции чисел, строк, символов, прочих сравнимых на равенство вещей и т.п. Typed Racket в этом смысле что-то предлагает, но я не смотрел.
можно пожалуйста ссылки, где я наезжаю на лисп? Я только спросил, как там делаются некоторые вещи, ответы получил, ответы меня устроили, остальные мои посты борьба с некомпетентностью в отношениии haskell.
А я и забыл что в схеме нет nil и функции на него не завязаны. А как там принято делать optional values и when?
Но случаеВ, когда вы хотите передать Nothing _не бывает_
Бывает, null object pattern, NULL в C, nullptr в C++, Nothing в Haskell или None в *ML, F# или Scala, nil (+ when) в CL — код видит что, например, ему не дали соединение к бакенду, поэтому что-то писать туда не нужно, если бы дали — он бы писал. Это простейшая форма DI/IoC по конфигурации кода.
Чего ты у меня спрашиваешь?
Я привёл валидные сигнатуры, ты заменил их на что-то непонятное.
через исключения надо протаскивать обработку ошибок
Часто — да, но Either/Maybe не имеют отношения к обработке ошибок, каждый их конструктор описывает вполне штатное (не исключительное) состояние которое нужно обрабатывать — Left — пошли в одну сторону control flow, Right — в другую. Этот Either в ООП соответствует ситуации L <: P, R <: P — повсеместная ситуация.
Они будут иметь отношение к обработке боее-менее исключительных ситуаций когда _хочется_ их обрабатывать не с помощью исключений, а с помощью retrun codes, в том чисел в виде вариантных ADT с информацией привязанной к каждому «коду» (варианту).
обычно так quasimoto делает, но он дельные вещи пишет, и в лиспе разбирается, не думаю, что его к обычным хацкелистам можно причислять.
Да есть тут на ЛОРе буйные хаскелисты, некоторые из них особо буйные, но не хочу их называть. Часто лезут не по делу со своими домыслами, почти как некоторые вчерашние студенты, недавно изучившие scala.
Prelude Text.Printf> :i PrintfType
class PrintfType t where
Text.Printf.spr :: String -> [Text.Printf.UPrintf] -> t
-- Defined in `Text.Printf'
instance [safe] IsChar c => PrintfType [c]
-- Defined in `Text.Printf'
instance [safe] PrintfType (IO a) -- Defined in `Text.Printf'
instance [safe] (PrintfArg a, PrintfType r) => PrintfType (a -> r)
-- Defined in `Text.Printf
А я и забыл что в схеме нет nil и функции на него не завязаны. А как там принято делать optional values и when?
Ну там есть nil, есть #f. Для optional обычно ф-я выглядит так: (f arg1 arg2 ... argn [optionalarg #f]), optionalarg- собственно значение, которое будет выступать в качестве null либо, если это лямбда, то будет вызвана эта лямбда (которая обычно кидает ексепшн). Для when и такого типа вещей есть void.
Бывает, null object pattern, NULL в C, nullptr в C++, Nothing в Haskell или None в *ML, F# или Scala, nil
Ну тут везде нету передачи.
код видит что, например, ему не дали соединение к бакенду, поэтому что-то писать туда не нужно, если бы дали — он бы писал.
Ну вот и надо ексцепшен кидать, если не дали :)
Я привёл валидные сигнатуры, ты заменил их на что-то непонятное.
У меня они так же валидны как у тебя. Еще раз - я только убрал either/maybe конструкторы.
Часто — да, но Either/Maybe не имеют отношения к обработке ошибок, каждый их конструктор описывает вполне штатное (не исключительное) состояние которое нужно обрабатывать — Left — пошли в одну сторону control flow, Right — в другую.
Ну так это если бы их использовали в таком качестве - то ок, но их ведь используют как замену ексцепшенам.
любая функция в haskell это функция от одного аргумента, в данном случае printf это функция от аргумента string возращающая функцию от нужного числа элементов, которое определяется этой строкой.
f :: a -> b, где b == c -> d это фунция скольки аргументов 1 или двух? Хинт: f :: a -> c -> d \equiv f :: a -> (c -> d).
в лиспе есть другие средства увеличения модулярности кода и упрощения возрастающей сложности программы в зависимости от её размера?
Это называется декомпозиция, которая, как говаривал Страуструп, требует ума, опыта и вкуса. Если говорить о Common Lisp, то я выделяю следующие инструменты (которые могут быть использованны в целях декомпозиции):
Функции (включая замыкания)
Структуры данных (структуры, классы, различные виды списков и т.д.)
Generic-фукнции (которые следует рассматривать отдельно и от функций, и от классов)
Макросы
Динамические переменные
Рестарты
Пакеты
Макросы не более, чем один из доступных инструментов. Ну а в такой постановке вопроса («упрощения возрастающей сложности программы в зависимости от её размера») их роль и вовсе сомнительна.
остальные мои посты борьба с некомпетентностью в отношениии haskell.
Согласен.
В целом я не спорю, что на Haskell проще сделать некоторые виды проверок (те, которые ложатся на систему типов). Спорю с тем, что на нём труднее сделать случайную ошибку (мой первый пример был как раз про это: не обработал проверку не-число, узнаешь только когда получишь ошибочные данные, причём через падение программы).
В CL основной метод борьбы — отладка. Главная задача программиста — на некорректную ситуацию бросить исключение. Даже если оно не будет обработано, в отладчике будет видно состояние программы, в котором эта ситуация произошла. Haskell (и даже Racket) подразумевает, что программист обязан все возможные ошибочные ситуации предусмотреть заранее. Я почти уверен, что в жизни это невозможно. «Программ без ошибок не бывает» (c)
мощность abcd (2, 3 или 4) зависит от выбора p и q которые задают это самое equality (через сравнение чисел) для элементов ab и cd. В более абстрактном случае достаточно задания моников abcd -> 1 + ab и abcd -> 1 + cd (тоже 2, 3 и 4, при условии минимальности — _все_ элементы abcd имеют образы в ab _или_ cd).
но .head уже бросает исключение, как в Scheme и Haskell, хотя есть и .headOption, как аналогичные функции из Safe в хаскеле.
Ну вот и надо ексцепшен кидать, если не дали :)
Зачем? Идёт штатная работа:
(defun some-work (... parameters ... &optional backend)
... use parameters ...
... create context ...
... do some work ...
(when backend
... use context, parameters ...
... work with backend ...
)
... etc. ...
)
или
{-# LANGUAGE CPP #-}
import Data.Foldable
#define runWhen(x) for_ x $ \x -> do
work :: Maybe Backend -> ...
work backend ... = do {
...
runWhen (backend) {
...
}
...
}
если полезный backend не передаётся и вместо него приходит nil/Nothing, то просто соответствующая часть работы не делается — так запланировано, если передаётся — то делается, причём сделана может быть только в данном контексте. Исключения вообще и не нужны тут и не эмулируются (то есть Maybe находит иное применение). Ещё вместо неделания может быть какое-то дефолтное делание.
У меня они так же валидны как у тебя.
У меня они были на хаскеле синтаксис которого стандартизирован, у тебя они стали (Int Double) что особого смысла уже не имеет.
Еще раз - я только убрал either/maybe конструкторы.
Ещё раз — в функцию прилетает Left вещь1 или Right вещь2, что зафиксировано в типе как Either Вещь1 Вещь2, либо из неё вылетает Left хорошаяВещь или Right неМенееХорошаяВещь (или менее) типа Either ХорошаяВещь НеМенееХорошаяВещь (и тут). Ты пытаешься выкосить из логики языка дизъюнкцию.
но их ведь используют как замену ексцепшенам
Если их используют в простых функциях — не вижу проблем, так они и должны работать, смысла усложнять управление нет, нелокальные исключения начинают играть роль только в бОльших масштабах. Если речь про MaybeT/ErrorT то, да, это что-то альтернативное исключением, но само по себе отдельная история.
потому что нештатная работа. Получили исключительную ситуацию - бросили исключение.
У меня они были на хаскеле синтаксис которого стандартизирован, у тебя они стали (Int Double) что особого смысла уже не имеет.
У меня они на том же языке, что и у тебя. еще раз - я просто убрал конструкторы, это все. С каких пор тапл из int и double смысла не имеет - неясно.
Ещё раз — в функцию прилетает Left вещь1 или Right вещь2, что зафиксировано в типе как Either Вещь1 Вещь2, либо из неё вылетает Left хорошаяВещь или Right неМенееХорошаяВещь (или менее) типа Either ХорошаяВещь НеМенееХорошаяВещь (и тут).
Когда так - то все хорошо. Но в подавляющем большинстве случаев это используют вместо исключений, типа - прилетает хорошая вещь или КРОВЬКИШКИГОВНОРАСПИДОРАСИЛО. Если бы все использовали в первом стиле, то просто не нужна была бы монада.
Но на практике оно задано вполне конкретным образом.
На практике оно может быть не задано совсем
data AB = A | B deriving ( Eq )
data BC = C | D deriving ( Eq )
внутритиповое есть, межтипового — нет.
abstract class Tree[T]
case class Empty[T]() extends Tree[T]
case class Node[T](value: T) extends Tree[T]
case class Pair[T](left: Tree[T], right: Tree[T]) extends Tree[T]
так как Tree[T] extends Any — equals есть, но его можно override несчётным количеством способов (количество разных инъекций из списков в деревья / разных путей на дереве, например).
в последнем случае — передача ажно Option[ Option[ U ] ] :) (т.е. Option[ TaskResult[ U ] ]).
Идёт штатная работа
так запланировано
потому что нештатная работа. Получили исключительную ситуацию - бросили исключение.
Мда... Ты бы написал, что ли, шаблон кода — как ты себе представляешь вменяемое бросание исключения (когда исключительной ситуации и нет вообще) в данном случае.
С каких пор тапл из int и double смысла не имеет - неясно.
Кортеж записывается как (Int, Double), а не (Int Double) — запятую ставим. Ну и я не хочу Int _и_ Double (кортеж, произведение), я хочу Int _или_ Double (вариант, сумма), как результат разбора «[\d+|\d+\.\d*]», например, другой вариант для такого результата — Maybe (Either Int (Int, Int)) (то есть функтор 1 -> 1 + Int + Int * Int).
В целом я не спорю, что на Haskell проще сделать некоторые виды проверок (те, которые ложатся на систему типов).
Спорю с тем, что на нём труднее сделать случайную ошибку (мой первый пример был как раз про это: не обработал проверку не-число, узнаешь только когда получишь ошибочные данные, причём через падение программы).
я бы не сказал, что это ошибка очень случайная, но да, это может быть неожиданно для того, кто решил просто попробовать поиграться с ghci и Prelude. Как уже было сказано эта ошибка ортогональна типам, и относится к тотальности - нетотальности функций, кстати в документации к readIO, и read написано, что функция может бросать исключения если выражение не парсится.
В CL основной метод борьбы — отладка. Главная задача программиста — на некорректную ситуацию бросить исключение.
Основное методо разработки программы на haskell - проектирование типов, так сказать Type Driven Development. Немного ортогонально, неправда ли? При этом типы дают доп консистентность программы, и некоторые гарантии, а отладку и тестирование никто не отменял, т.к., например, программа:
plus :: (Num a, Eq a) => a -> a -> a
plus a b | a == 1 = b
| otherwise = a + b
является корректной по типу, но при этом её поведение будет некорректно, это не проверяется иначе как тестами.
Haskell (и даже Racket) подразумевает, что программист обязан все возможные ошибочные ситуации предусмотреть заранее.
сомнительное утверждение, честно. Что-то вроде в Lisp-е программист должен рассмотреть все возможные ситуции (но может это сделать лениво), в haskell типы существуют для того, чтобы сузить множество возможных ситуаций. Поэтому haskell человек должен предусмотреть, что в функции попадают только те типы, с которым функция может работать.
в CL да и да и любой динамике есть доп фичи, например:
readLn >>= print . (+1), будет работать изкоробки для любого типа являющего числом (Num a), т.е. для которого определена операция (+), в отличии от этого haskell выведет в данном случае тип в дефолтный и работать будет только Integer. :)
так как Tree[T] extends Any — equals есть, но его можно override несчётным количеством способов (количество разных инъекций из списков в деревья / разных путей на дереве, например).
При чем тут внутриязыковое Equal вообще? Ты придумал какую-то хуйню, которая не имеет отношения к реальному подтипированию и сейчас о ней говоришь. В реальности неразмеченное объединение задано вполне однозначно. Можешь привести контрпример в котором будет неоднозначность? Нет, не можешь. Ну так о чем разговор?
При этом типы дают доп консистентность программы, и некоторые гарантии, а отладку и тестирование никто не отменял
Так о чем речь и идет. Затраты на борьбу с системой типов есть, а профита она не дает, т.к. программу потом все равно надо будет отлаживать и столько же времени, сколько и без типов.
Поэтому haskell человек должен предусмотреть, что в функции попадают только те типы, с которым функция может работать.
не знаю что такое «борьба с системой типов», при понимании инструмента это помощь от компилятора
Это когда я хочу написать ф-ю, но компилятор мне это сделать не дает, т.к. чекер не может вывести ее корректность. В результате приходится лепить кривые костыли.
очевидно, что это утверждение неверно.
Ну как же неверно? Ты же сам сказал - писать тесты и отлаживать логику все ранво надо, типы ее не покрывают.
Так о чем речь и идет, необходимость предусматривать есть, а лежит она на человеке, а не на компиляторе, и гарантий нет.
Не понял о чем ты. Ничего на человеке не лежит, ничего предусматривать не надо. Все точно так же как в хаскеле, никаких отличий в отладке. Те же тесты, те же ошибки, все то же.
и вообще, наткнулся тут на пост хороший, очень советую к прочтению:
ничего что там написано вообще к теме разговора не относится.
Это когда я хочу написать ф-ю, но компилятор мне это сделать не дает, т.к. чекер не может вывести ее корректность. В результате приходится лепить кривые костыли.
читай первый пункт ссылки, которую я дал, про выразительность языков.
т.к. программу потом все равно надо будет отлаживать и столько же времени, сколько и без типов.
Ну как же неверно? Ты же сам сказал - писать тесты и отлаживать логику все ранво надо, типы ее не покрывают.
данные утверждения не эквивалентны.
ничего что там написано вообще к теме разговора не относится.
тот пост полностью покрывает всю аргументацию, против которой мне тут приходится выступать.
читай первый пункт ссылки, которую я дал, про выразительность языков.
Факт остается фактом - мне приходится тратить лишние усилия. Как ты это не объясняй. Затраты - есть, профита - нет.
данные утверждения не эквивалентны.
Почему же не эквивалентны? Вполне.
тот пост полностью покрывает всю аргументацию, против которой мне тут приходится выступать.
Он ни одного аргумента не покрывает. Ну то есть вообще ни одного. Потому что, повторюсь, никак не связан с темой текущего разговора. Мы говорим о статической типизации, а пост к ней никак не относится.
Факт остается фактом - мне приходится тратить лишние усилия. Как ты это не объясняй. Затраты - есть, профита - нет.
значит _ты_ не умеешь статическую типизацию, я вот не умею лисп, поэтому я не ору что лисп отстой и не нужен, в отличии от.
Почему же не эквивалентны? Вполне.
я боюсь, что дальшейший разговор перестает быть предметным.
Он ни одного аргумента не покрывает. Ну то есть вообще ни одного. Потому что, повторюсь, никак не связан с темой текущего разговора. Мы говорим о статической типизации, а пост к ней никак не относится.
Пост относится к абстрактной проблеме спора о парадигмах, статика vs динамика это одна из таких тем. Мне жаль, что ты не смог этого понять, или смог, но решил потроллить.
Ты не понял. Я прекрасно знаю как сделать соответствующий work-around и добиться того, чего я хочу. Проблема в том, что сам факт этого work-around'a требует дополнительных затрат. А профит где?
я боюсь, что дальшейший разговор перестает быть предметным.
Почему же, он вполне предметен. Мы обсуждаем возможные преимущества статики. Вот я написал приложение на статическом ЯП и на динамическом ЯП. И в том и в том случае у меня будет совершенно одинаковый набор тестов. В чем тогда преимущество статики, если все, что я тестировал в динамике, мне и в статике надо тестировать?
Пост относится к абстрактной проблеме спора о парадигмах, статика vs динамика это одна из таких тем.
Если бы ты внимательно прочитал пост, то понял бы, что он не относится к спору о парадигмах. Перечитай, не позорься. Это пост о субъективизме определенных понятий.
рофит в том, что этот work-around с большой вероятностью не нужен.
Да нет, нужен. Вот например я хочу обработать список, который может содержать несколько примитивных типов. В динамике я беру и обрабатываю, в статике - мне надо будет лепить тип-враппер и врапать все значения, когда я их в этот список кладу. Лишняя работа налицо.
оценка преимущества одного из подходов static vs dynamic typing это прекрасный пример субективного понятия.
Тот пост был о субъективизме трех вполне конкретных понятий.