LINUX.ORG.RU

[haskell] есть ли что-то типа reflection? (недемократизм языка?)

 


0

0

Обычная фраза: deriving (Read, Show, Eq, Ord)

Компилятор умеет выводить инстансы этих typeclass-ов. А что делать, если хочется их выводить чуть-чуть по-другому или добавить еще один, который автоматически бы выводился? (Для этого, возможно, подошло что-то похожее на reflection, патчить компилятор не предлагайте)

__________________________________________________

Недемократизм языка в моем понимании -- это

1. Частичная реализация некой фичи и одновременно отстутствие инструмента для расширения реализации этой фичи. Например в яве оператор + для сложения целых, вещественных, строк и все: комплексные, кватернионы, вектора, матрицы... складывай уже через add.

2. Невозможность смены конкретной реализации фичи (зачастую связано с п.1). Например, невозможность создать в рамках с++ свой virtual dispatch (для того, что сделать множественный virtual dispatch, т.е. п.1). В принципе, п.2 до некоторой степени неизбежен, так как иногда намного проще сделать фичу, чем API для написания фич).

__________________________________________________

Более глобальный вопрос -- а насколько хаскель демократичен? (для обеспечения демократизма разрешается использовать ключи компилятора, template haskell, ...)

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

> Ну на мой, пока еще наивный взгляд, разный результат -- это явные грабли. А откуда они проистекают?

Из monomorphism restriction и default rules. В принципе, есть сильное течение в пользу того, чтобы в следующей версии стандарта сделать NoMonomorphismRestriction дефолтным.

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

[code] > Prelude Data.Complex> (0 :+ 1) ** 2.7 > (-0.4539904997395469) :+ (-0.8910065241883678) [/code]

Ну вот то, что это работает - недоработка явная.

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

> Ну вот то, что это работает - недоработка явная.

Да еще и правильно! - точно недоработка. Ты действительно что ли в ТФКП никогда корни из комплексных чисел не вычислял?

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

Да, анонимус, че-то не получается у тебя хороший пример привести.

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

Второй вариант плох из-за того, что неясно R и C -- это множестава или типы.

Ну так предложи хороший.

И даже я думаю, а не стать ли на время мне на твою сторону и не привести ли свой пример.

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

>Ну вот то, что это работает - недоработка явная.

Так и запишем: работающая программа на хаскелле - явная недоработка.

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

> Первый вариант -- отдавать год когда числом, а когда строкой (надеюсь это был ты) -- очевидно кривой дизайн.

> Второй вариант плох из-за того, что неясно R и C -- это множестава или типы.

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

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

1.0 + squareRoot 4 - выполнится принципиально быстрее, т.к. он вычислится без конвертаций в комплексные числа или боксовый тип.

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

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

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

> I think the problem cannot be fully solved, especially not within the Haskell 98 numeric type classes. There is no satisfying implementation of (**) even for Float and Double.

Грустно.

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

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

> Ты действительно что ли в ТФКП никогда корни из комплексных чисел не вычислял?

Либо постоянно оговаривая, какую ветку я имею в виду (и разбираясь с проблемами, которые из-за этого возникают).

Либо не из комплексных чисел, а из элементов некоторого накрытия C (то есть, римановой поверхности).

Miguel ★★★★★
()

Мой пример.

Допустим мы пишем однопроходный парсер (на плюсах). У нас есть абстрактный класс AST_Node и его потомок класс Literal.

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

LiteralThatStartsOnDigit* parse_stream_that_start_on_digit(....) {....}

Возможено такое:

Variant<Number,Money,Some,Very,Long,List,Of,Implemented,Literals> parse_stream_that_start_on_digit(....) {....}

но когда мы захотим добавить Angle( т.е. 177°23'44" ) нам придется добавлять его дважды -- в код внутри функции и -- что очень тупо -- в Variant<...>

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

при том, что parse_stream_that_start_on_digit будет выглядеть примерно так:

switch(*char_after_end) {
  case '$': return new Money(start,end);
  case 'i': return new Imaginary(start,end);
  case 'e': return new Float(start);
  case '°': return new Angle(start);
}
www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от jtootf

>>Так и запишем: неправильно работающая программа на хаскелле - явная недоработка.

>fixed ;)

Это про darcs?

anonymous
()

Например, есть модный паттерн матчинг, а сделать что-то вроде _{n} (как в регулярных выражениях) нельзя. Зачем он тогда такой убогий нужен?

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

теперь, почему не написать Literal* parse_stream_that_start_on_digit(....) {....} ?

Потому, что это, вообще говоря, неправда.

Например, если мы будем делать pattern matching (не шибко красивый, да, но возможный) -- для Variant<Number,...,Angle> компилятор сможет проверить его полноту, а вот для Literal-а это придется делать вручную (нам не нужно рассматривать все варианты Literal-а, например String).

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

> А так профит в большей производительности - нам не надо боксировать типы туда-сюда.

> 1.0 + squareRoot 4 - выполнится принципиально быстрее, т.к. он вычислится без конвертаций в комплексные числа или боксовый тип.

если речь идет о посчете компилятором константного выражения 1.0 + squareRoot 4 -- то скорость в общем-то пофиг

а если о рантайме -- то ерунда какая-то, тебе все равно придется отдавать тайптег (пусть даже 0 или 1) и само значение C или R, так что боксинг налицо.

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

> Нет, там стопроцентно типы, просто записал хреново в первый раз, а jtootf сразу же и набросился. Множества они в математике, в программировании - там только типы.

Я так понимаю, что нам нужен вывод компилятором дизъюнктного объединения множеств^W типов, и разница в том, что тебя устраивает такой вывод по умолчанию с варнигом, а я считаю, что должно быть ключевое слово (иначе ошибка).

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

> Я так понимаю, что нам нужен вывод компилятором дизъюнктного объединения множеств^W типов, и разница в том, что тебя устраивает такой вывод по умолчанию с варнигом, а я считаю, что должно быть ключевое слово (иначе ошибка).

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

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

> если речь идет о посчете компилятором константного выражения 1.0 + squareRoot 4 -- то скорость в общем-то пофиг

Конечно, не о константном.

> а если о рантайме -- то ерунда какая-то, тебе все равно придется отдавать тайптег (пусть даже 0 или 1) и само значение C или R, так что боксинг налицо.

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

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

> Я так понимаю, что нам нужен вывод компилятором дизъюнктного объединения множеств^W типов, и разница в том, что тебя устраивает такой вывод по умолчанию с варнигом, а я считаю, что должно быть ключевое слово (иначе ошибка).

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

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

Хотя стоп, фигню спорол. Сложностей в подобной фиче будет немеряное количество. Например: [code] <our-cool-variant-prefix> f 1 = 0.5 f 2 = 1 f 3 = "уйди на фиг" [/code] Вопрос: f должно иметь тип а) [code] f :: (Num a, Num b, Fractional c) => a -> Variant b c String [/code] или б) [code] f :: (Num a, Fractional c) => a -> Variant c String [/code] ? Учитывая, что Fractional - подкласс Num.

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

Хотя стоп, фигню спорол. Сложностей в подобной фиче будет немеряное количество. Например:

<our-cool-variant-prefix> f 1 = 0.5
                          f 2 = 1
                          f 3 = "уйди на фиг"
Вопрос: f должно иметь тип а)
f :: (Num a, Num b, Fractional c) => a -> Variant b c String
или б)
f :: (Num a, Fractional c) => a -> Variant c String
? Учитывая, что Fractional - подкласс Num.

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

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

Ну варнинг или ошибка — это да, должно настраиваться.

А вот насчет ключевого слова все намного интереснее. Твой пример вполне тайпчекается в плюсах, а дизъюнктное объединение — это что-то новое.

#include <iostream>
#include <cmath>

struct Complex
{
  float x;
  float y;
  Complex(float x): x(x) {}
  Complex(float x, float y): x(x),y(y) {}
};

float s(float x) { return sqrt(x); }

Complex f(float x) { return x>=0 ? s(x) : Complex(0,s(-x)); }

int main()
{
  Complex z=f(-4);
  std::cout << z.x << ' ' << z.y << '\n';
  return 0;
}
www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Miguel

> Сложностей в подобной фиче будет немеряное количество.

Да, лучше с простого начать.

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

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

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

> Твой пример вполне тайпчекается в плюсах

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

Кстати, у тебя ошибка - должно быть Complex(float x): x(x), y(0) {}

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

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

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

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

Ну после того, как ты явно указал преобразование типов и привел все к комплексным типам - то получился не мой вариант, а вариант от Miguel', который конечно же будет тайпчекится. Кстати, у тебя ошибка - должно быть Complex(float x): x(x), y(0) {}

Хорошо, вот тебе без явного преобразования:

// выхлоп:
// Complex
// Complex

#include <iostream>
#include <cmath>

struct Complex
{
  float x;
  float y;
  Complex(float x): x(x),y(0) {}
  Complex(float x, float y): x(x),y(y) {}
};

template<class T> void print_type_of(T x) { std::cout << "unknown\n"; }
template<> void print_type_of(float x)    { std::cout << "float\n"; }
template<> void print_type_of(Complex x)  { std::cout << "Complex\n"; }

float s(float x) { return sqrt(x); }

void f(float x) {  print_type_of(  x>=0 ? s(x) : Complex(0,s(-x))  );  }

int main()
{
  f(4);
  f(-1);
  return 0;
}
www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Miguel

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

1. эта проблема будет локализуема во времени компиляции или может попасть в рантайм?

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

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

> В твоём примере всё вообще делается так: Literal = SOD LiteralThatStartsOnDigit | NSOD LiteralThatStartsOnNonDigit

LiteralThatStartsOnNonDigit это вообще артефакт

Ну допустим даже

Literal = SOD LiteralThatStartsOnDigit | SL StringLiteral

на практике я бы так и поступил. Придется долго объяснять, чем это плохо.

________________________________________

Рассмотрим такой вот код:

сonst int a=3;
const int b=5;
const int c=8; // it must be a+b

Очевидно, что правильно писать const int c=a+b;

Точно так же, в нашем случае (с++) LiteralThatStartsOnDigit не является независимым объектом, а является производным, точно как c=a+b является производным. В доказательство рассмотрим случай, когда Money меняет синтаксис с 500$ на $500. Тогда Money надо исключить из функции parse_stream_that_start_on_digit (это мы очевидно сделаем), но запросто можем забыть исключить его из потомков LiteralThatStartsOnDigit, и компилятор об этом не напомнит -- у нас будет Money* parse_money(...) {...}.

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

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

Я же не сказал, что это единственный вариант.

Можно, например, так: [code] class IsLiteral a where ... instance IsLiteral Number where ... instance IsLiteral Money where ... instance IsLiteral StringLiteral where ...

data Literal where Literal :: IsLiteral a => a -> Literal

class IsLiteral a => IsLiteralStartsOnDigit a where ... instance IsLiteralStartsOnDigit Number where ... instance IsLiteralStartsOnDigit Money where ...

data LiteralStartsOnDigit where LiteralStartsOnDigit :: IsLiteralStartsOnDigit a => a -> LiteralStartsOnDigit

forgetFirstDigit :: LiteralStartsOnDigit -> Literal forgetFirstDigit (LiteralStartsOnDigit x) = Literal x [/code] Если очень нужно, можно вообще закрыть классы IsLiteral и IsLiteralStartsFromDigit, например, так: [code] class IsLiteral a where matchLiteral :: p Number -> p Money -> p StringLiteral -> p a instance IsLiteral Money where matchLiteral _ pm _ = pm [/code]

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

Я же не сказал, что это единственный вариант.

Можно, например, так:

class IsLiteral a where
    ...
instance IsLiteral Number where ...
instance IsLiteral Money where ...
instance IsLiteral StringLiteral where ...

data Literal where
    Literal :: IsLiteral a => a -> Literal

class IsLiteral a => IsLiteralStartsOnDigit a where
    ...
instance IsLiteralStartsOnDigit Number where ...
instance IsLiteralStartsOnDigit Money where ...

data LiteralStartsOnDigit where
    LiteralStartsOnDigit :: IsLiteralStartsOnDigit a => a -> LiteralStartsOnDigit

forgetFirstDigit :: LiteralStartsOnDigit -> Literal
forgetFirstDigit (LiteralStartsOnDigit x) = Literal x
Если очень нужно, можно вообще закрыть классы IsLiteral и IsLiteralStartsFromDigit, например, так:
class IsLiteral a where
    matchLiteral :: p Number -> p Money -> p StringLiteral -> p a
instance IsLiteral Money where
    matchLiteral _ pm _ = pm

Miguel ★★★★★
()
Ответ на: комментарий от Miguel
class IsLiteral a => IsLiteralStartsOnDigit a where
    ...
instance IsLiteralStartsOnDigit Number where ...
instance IsLiteralStartsOnDigit Money where ...

да-да, я тоже хотел предложить, что то cool-keyword должно генерить вот такой кусок кода, но потом задумался, что оно должно сгенерить в where ... ?

в случае иерархии классов в с++ все понятно — нам нужен (текстуально) пустой класс LiteralStartsOnDigit, а здесь? тоже пустые инстансы? или как?

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

но потом задумался, что оно должно сгенерить в where ... ?

Ну, например, то самое закрытие класса.

Это довольно стандартная хаскельная техника: если нужен класс, у которого инстансами являются только Int и Float, то пишем

class IntOrFloat a where matchIntOrFloat :: p Int -> p Float -> p a
instance IntOrFloat Int where matchIntOrFloat pi _ = pi
instance IntOrFloat Float where matchIntOrFloat _ pf = pf
Потом, если нам захочется что-то сделать с произвольным инстансом такого типа, достаточно написать, что мы будем делать с Int и что с Float. Например, если мы хотим бинарную операцию, которая для Int-ов будет сложением, а для Float-ов - умножением:
newtype BinOp a = BinOp {applyBinOp :: a -> a -> a}
operation :: IntOrFloat a => a -> a -> a
operation = applyBinOp $ matchIntOrFloat (BinOp plus) (BinOp mult) where
    plus :: Int -> Int -> Int
    plus x y = x + y
    mult :: Float -> Float -> Float
    mult x y = x * y
Аннотации типов я, конечно, пишу только для ясности.

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

и в результате придется писать тип, так?

<our-cool-variant-prefix> f 1 = 1 :: Int
                          f 2 = 0.5 :: Float
                          f 3 = "не знаю..." :: String

в связи с этим мне больше нравится подход, когда 1 — это не объект неясного инстанса тайпкласса Num, а объект класса Натуральное (который подкласс Целого, который подкласс Рационального, ... и эту цепочку можно расширять в непривычную для ООП сторону *над*классов)

какова альтернатива в хаскеле?

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

Кстати, с++ (хотя бы частично) поддерживает такой "надклассинг". Если конструктор класса К2 принимает единственный аргумент класса К1, то он практически становится надклассом К1.

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

> и в результате придется писать тип, так?

Один из вариантов.

Другой вариант: [code] class BlaBlaBla x where -- я задолбался имена классов придумывать matchBlaBlaBla :: (forall a. Num a => p a) -> (forall a. Fractional a => p a) -> p String -> p x [/code] Но тогда в инстансах можно будет писать [code] instance BlaBlaBla Float where matchBlaBlaBla pNum _ _ = pNum [/code] а можно [code] instance BlaBlaBla Float where matchBlaBlaBla _ pFractional _ = pFractional [/code] Короче говоря, надо будет для Float-а (и для многих других) явно указывать - мы хотим, чтобы для него всё считалось как для Num, или как для Fractional?

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

Ну когда ж я привыкну к лоркоду???

и в результате придется писать тип, так?

Один из вариантов.

Другой вариант:

class BlaBlaBla x where -- я задолбался имена классов придумывать
    matchBlaBlaBla :: (forall a. Num a => p a) ->
                      (forall a. Fractional a => p a) ->
                      p String ->
                      p x
Но тогда в инстансах можно будет писать
instance BlaBlaBla Float where matchBlaBlaBla pNum _ _ = pNum
а можно
instance BlaBlaBla Float where matchBlaBlaBla _ pFractional _ = pFractional
Короче говоря, надо будет для Float-а (и для многих других) явно указывать - мы хотим, чтобы для него всё считалось как для Num, или как для Fractional?

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

Ухх, сколько нафлудили.

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

Сам код, который реализует нужные мне изменения при чтении, заработал довольно быстро. Но нужна была и соответствующая печать. Для sbcl я нашёл обходной путь, для ECL сделал патч, CCL и так заработал, CLISP мне не особо интересен, но там наверняка нужно будет тоже патчить на уровне C. Но сложность была в том, что я в основном работаю с лиспворксом, а его исходники недоступны. Поэтому, я вооружился тремя функциями:

apropos (получить функции/классы/переменные, в названии которых содержится подстрока)
disassemble (получить ассемблерный код заданной функции)
(setf symbol-function) (подменить на лету любую, в т.ч., и встроенную функцию, там, где она не inline, на другую).

В конце концов, я нашёл в недрах лиспворкса функцию io::output-symbol, которую и задекорировал (я написал специальную функцию decorate-function, поскольку голыми руками править symbol-function опасно).

Потом мне понадобилось изменить некоторые встроенные функции редактора. Если бы это было SLIME, всё было бы легко, но мне нужнее сейчас lispworks, поэтому пришлось воспользоваться теми же инструментами. Будучи пожизненным ламером, я запутался в собственном коде. Вроде бы, сегодня к вечеру, у меня всё же более-менее сносно заработал completion (хотя и с изъяном) и поиск определения в исохдниках.

Естественно, что эксперименты я ставил динамически, не выходя из лиспа. Выходил я в следующих случаях:
1. рекурсивное переполнение стека и прочая жуткая порча
2. внутреннее чувство дискомфорта и ощущение, что что-то вышло из-под контроля.

К чему я всё это пишу? К тому, что хаскелистам подобное счастье вряд ли доступно. Ведь я же правильно понимаю, что в хаскеле невозможно заменить тело почти любой функции в рантайме на другое? Или я отстал от жизни? Кроме того, статическая типизация будет сильно мешать строить такие абстракции как decorate-function, которая позволяет задекорировать функцию с произвольной сигнатурой.

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

> К тому, что хаскелистам подобное счастье вряд ли доступно.

К счастью, да.

> Однако, я провёл два из трёх этих дней в отладке.

Был бы хаскелл - ничего отлаживать не пришлось :) Написал, скомпилировал - и готово.

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

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

> Кроме того, статическая типизация будет сильно мешать строить такие абстракции

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

> decorate-function, которая позволяет задекорировать функцию с произвольной сигнатурой.

Можно.

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

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

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

> А как же тогда можно рассуждать о корректности программы, если ты делаешь такие финты в ран-тайме?

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

> А как же тогда можно рассуждать о корректности программы, если ты делаешь такие финты в ран-тайме? Как на это посмотрит мой строгий хаскелло-тайпчекер?

Ну, начнём с того, что можно подменять функцию на другую _с_такой_же_ сигнатурой, не нарушая строгости типизации. Хотя пользы от этого мало: пусть f вызывает g, и больше g нигде не вызывается (но может вызываться из REPL). Допустим, нам нужно поменять сигнатуру g и тело f, так, что оно будет смотреть на новое тело g. Зачем тогда нужна именно статическая типизация? Нам что, перезапускать всю программу ради одной жалкой функции?

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

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

пусть f вызывает g, и больше g нигде не вызывается (но может вызываться из REPL). Допустим, нам нужно поменять сигнатуру g и тело f, так, что оно будет смотреть на новое тело g. Зачем тогда нужна именно статическая типизация? Нам что, перезапускать всю программу ради одной жалкой функции?

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

в случае хаскеля все сложнее, т.к. он ленив по умолчанию.

т.е. f может использовать g, и это означает, что посредине работающей программы куча g связана в thunk-ах, и просто сказать «а вот с этого момента все вызовы g будут типизированы по-новому» мы не можем, нам надо как-то избавиться от старых g, и в общем случае мы этого сделать не можем, т.к. вычисление *всех* старых g может быть бесконечно во времени (и это не баг, а фича!).

возможно, то, что я написал, больше страшилка, чем типичный случай (и для реалистично примера может придется ввести не одну, а две-три функции f1, f2, f3), но к хаскелю это более относится, чем к остальным языкам с опциональной ленью — т.к. он ленив по умолчанию.

теперь о типизации и обертках.


int count=0;

template<class R> R g(R) {....}

char* f(int, char*) {....}
double f(double, double) {....}

template<class T1, class T2, class R> R shell( T1 a, T2 b )
{
  count++;
  return g(f(a,b));
}

char* s=shell(1,"asdf");
double x=shell(0.1,0.2);

template<class T1, class T2, class R> R shell_2( T1 a, T2 b, R f(T1,T2) )
{
  count++;
  return g(f(a,b));
}

чтобы обертывать произвольное число аргументов, нужен уже с++0х.

в хаскеле думаю затруднений с оберткой не будет, разве что случаи

1. произвольного числа аргументов

2. необходимости сделать обернутую функцию нечистой — типа как мой пример count++; — и в результате обернутую функцию нельзя будет вызывать вместо необернутой (либо использовать черную магию и нарушать чистоту, но я пока не умею)

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

> template<class T1, class T2, class R> R shell_2( T1 a, T2 b, R f(T1,T2) )

Насколько я понял ден73, он имеет ввиду обертку для вообще _любой_ функции, с любой сигнатурой. Насколько мне известно, в си++ такого нельзя, разве что только с помощью void*.

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

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

Да. Я знаю далеко не все языки, но, по моим наблюдениям, такие средства почему-то отсутствуют во всех языках, за очень небольшим количеством исключений: SQL, Common Lisp, bash. К удивлению, "динамические" языки ничего хорошего тут предоставить не могут. Даже в Питоне всё не так гладко, как надо, хотя "динамизм" вроде как является его главным рекламируемым преимуществом.

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

Здесь нет ничего уникального для Хаскеля. В других языках f и g могут находиться на стеке (и не один раз). В лиспе этот вопрос решается просто: на стеке остаётся старое тело, а новое используется для новых вызовов. Хотя там всё тоже не так просто, например, тело может быть сохранено в переменной. Так что, если рассматривать такую возможность с точки зрения теории, она опасна. Однако, в моей _повседневной_ практике она исключительно полезна и, наверное, если бы не она, я бы всё же бросил лисп. А так - не получается, т.к. нечем заменить.

> Насколько я понял ден73, он имеет ввиду обертку для вообще _любой_ функции, с любой сигнатурой

Да. При этом, мой код имеет объём около килобайта, вместе с примерами.
Сам код приводить не буду, но вот пример использования:

(defun decorated-find-package (fn name)
(or (funcall fn name) (warn "Ой!")))

(decorate-function 'find-package #'decorated-find-package)

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



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

>den73

Привет. Извини, что оффтопик, но как чем там кончилось дело с C-Lisp языками? А то тут http://lambda-the-ultimate.org/node/3281 есть ссылка на L: http://www.nongnu.org/l-lang/manual/documentation.html#L-in-brief

>L is:


>A compiled language with a C-like syntax, and Lisp-like macros.

>It is an extensible programming language : even the syntax is modifiable at run-time.

..
>So, L can be seen both as:

>C with stronger typing + extensible compiler support (macros, parser, expanders) + fully expression-based.

or Lisp with low-level capabilities, static typing, support for custom syntaxes, and type-aware macros.

что ты об этом L думаешь и чем кончились твои поиски?

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

Здравствуй, anonymous.

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

Есть пока что промежуточные итоги:
1. мне больше всего нравится стиль деклараций типов, принятый в процедурных языках, т.е., декларация типа и значения вместе. Мне не нравится, как это сделано в окамле и лиспе.
2. я не хотел бы погрязнуть в процессе. Мне интересен язык, дающий прикладные преимущества. Мне 36 лет, я старею и ещё через несколько лет мне совсем тяжело будет заставить себя что-то программировать. Мне уже сегодня нужен язык достаточно развитый и зрелый. Добрые коллеги давали мне много ссылок на разные языки. Мне понравился boo и мне понравился haxe. Но оба они - недостататочно зрелые. А boo существует только под платформу .net. Кроме того, они не обладают возможностью интерактивной _пере_компиляции существующих функций, и _пере_определения существующих объектов, а это - критичное и (почти) уникальное преимущество лиспа.

В общем, я пришёл к следующим практическим выводам: там, где есть свобода выбора, я выбираю лисп. На счастье, такая свобода у меня во многом на сегодня есть. Я сделал несколько упрощений и улучшений, ещё одно улучшение на подходе. Возможно, мне удастся избавиться от главных головных болей, связанных с лиспом, без потери удобства его средств разработки. Я придумал макрос proga, который убирает где-то около половины лишних скобок и уровней вложенности лиспа, позволяя переформулировать основные конструкции. Например, вместо
(let ((a b))
__(flet ((g (x) (print x)))
____(g a)))

я пишу гораздо более лаконичное
(proga
__(let a b)
__(flet g (x) (print x))
__(g a))

Также я научился менять синтаксис лиспа очень удобным способом. У лиспа есть проблема - ограниченность пространства для модификации "читателя". Чтобы встроить иной синтаксис в лисп, нужно или назначать этот синтаксис буквам (а букв слишком мало), или использовать некрасивые конструкции. Сегодня я могу написать в командной строке следующее:
(proga
__(let tbl 'company)
__(sql:select count(*) from :~tbl;
____)
__)

и получаю возможность прямо встраивать SQL в лисп, без каких-либо изменений синтаксиса SQL. А в этот SQL я могу снова встроить лисп, и тем самым генерировать запрос на основе шаблона. Мой опыт показал, что все попытки обернуть синтаксис SQL во что либо оказываются, в конечном итоге, менее удобными, чем просто писать SQL,
поскольку выражение не получается менее лаконичным, или теряется слишком большая часть возможностей SQL, или, вместо решения своих прикладных задач я вынужден тратить основное время на поддержку интерфейса между лиспом и SQL. То же касалось и виденных мной попыток генерить JavaScript из лиспа. Получаемый "лиспоподобный" javaScript просто и тупо менее читаем, чем обычный javaScript,хотя бы из-за того, что в javaScript больше разных видов ограничителей строк. В случае C ситуация была бы чем-то проще. Но, всё же, мне представляется более правильным и простым писать код на С со вставками на лисповом препроцессоре.

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

Остался незакрытым вопрос безсмысленного (с точки зрения программиста на С) повторения имён типов в коде следующего рода:
(let ((s (make-struct1))) (cons struct1-field1 struct1-field2))
Здесь программист на лиспе вынужден написать слово struct1 три раза, а программист на С написал бы его только один раз. Не существует общего способа победить эту серьёзную проблему (сильно снижающую читабельность кода, а значит, снижающую производительность труда), не меняя что-то в самом лиспе. Но я придумал, что и как изменить, нужно опробовать.

Также я заметил, что любой более-менее зрелый язык программирования имеет исходный код размером от 0,5 до нескольких Мб. Оценив ситуацию, я решил, что мне, скорее всего, просто не хватит мотивации написать свой язык и это не будет выгодно, поскольку трудозатраты на поддержку исходника такого объёма обязательно будут высоки.

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

L интересный, но по мануалу в самых интересных местах расставлено not implemented :-)

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

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

хе-хе

мой опыт показывал то же самое :-)

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

возможно, полезными будут ссылки в окончании топика http://www.linux.org.ru/jump-message.jsp?msgid=4187249&cid=4191586

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

Есть несколько языков, примерно похожих на то, что ты ищешь -- goo, scheme/gambit, Dylan в плане интеграции C/Lisp; L или Pfront из MBase; Nimrod/Boo/Geanie/Deelight, если искать в направлении похожей семантики, AST макросов и компиляции в Си, но с синтаксисом питона.
Понятно, что это всё поделия разной степени зрелости, с разными основными идеями и целями, поэтому и хотелось бы увидеть цели/критерии поисков, чтобы лучше понимать, что мы ищем.
Возможно, нужные фичи будут не в одном языке, а в нескольких, и мы ищем какой-то "метаязык", объединяющий нужные фичи. Такой метаязык можно построить из базового, если есть AST макросы или фреймворком вроде тех же PEG, OMeta, и т.п.
Возможно, нужен как раз язык "всё в одном", с батарейками. И важнее то, что уже есть, а не то, что можно построить в принципе.

Хотелось бы увидеть что-то вроде критериев поиска, с % важности приоритетов фич:
1. Краткость.
2. Встраиваемость в Си. Легкость FFI.
3. Расширяемость, "демократичность языка" (с)ТС
(макросы, преобразования AST, подключаемые грамматики и т.п.)
4. Система типов, требования к ней, вывод типов, статическая/динамическая и т.п.
5. Синтаксис.
6. Батарейки, уже готовые (с учётом готовых в 2 си-батареек)

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

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

В разрезе фич 1..6 и их приоритетов уже можно искать нечто конкретное.
Например, можно прокомментировать твой пост:
> 1. стиль деклараций типов, принятый в процедурных языках, т.е., декларация типа и значения вместе.


Как в L описание Cocytus, 3.3.1. Схожая идея и в Lush, Goo, Gambit -- в местах интеграции с Си. Или в Dylan,в dexprs, SST ( skeleton syntax tree, общая форма AST ) или в FFI Dylan, Factor (понимает #include Си файлов, автоматически генерирует FFI стабы язык<->Си)

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


вот для этого я и спрашиваю про критерии, важность отдельных фич!! Грубо говоря, что важнее, 6 или легкое-прозрачное-автогенерируемое 2?

>возможностью интерактивной _пере_компиляции существующих функций, и _пере_определения существующих объектов


насколько тут нужна поддержка от самого языка, семантики, а не "компиляция на лету в dll/so и подгрузка скомпилированного" ? Например, Dylan, ECL (embedded CL), Gambit, goo, Lush, PicoLisp -- варианты с компиляцией в Си и подгрузкой скомпилированного. MBase, boo, haxe -- варианты с reflection и поддержкой со стороны VM. На D через DDL или на любом языке Х через LLVM или `C тоже можно реализовать динамическую подгрузку, без средств ни языка ни VM, только toolchain.

>придумал макрос proga, который убирает где-то около половины лишних скобок и уровней вложенности лиспа

Фича1, или то, что Пол Грехем в своём Arc называет brevity. Удобный синтаксис -- важен, и при этом он может быть расширямым, разница SST/AST в Dylan, CST/AST в L, тот же сахар в Arc, Dylan или Nimrod.

>вместо решения своих прикладных задач я вынужден тратить основное время на поддержку интерфейса между лиспом и SQL


здесь напрашивается LINQ/Scala QL и его полезность по сравнению с просто embedded SQL.

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

>В случае C ситуация была бы чем-то проще.
например, в Goo или в Gambit Scheme или в Gwydion Dylan или в Lush можно встраивать Си-код в блоках #{ ... }

>пространств имён. В лиспе этот процесс весьма трудоёмок и чреват "подставами".


в Dylan есть модули/библиотеки с практически похожей семантикой пакетов, но без некоторых подводных камней. С другой стороны, если мы встраиваем в С++, можно изначально попробовать использовать его using namespace. С третьей стороны, приходим к пониманию необходимости описания системы модулей в метаязыке, какая-то простая модель вроде пакетов или сложнее вроде пространств имён.

>повторения имён типов в коде .. не меняя что-то в самом лиспе.


макрос, который генерирует строки struct1##X ? with-slots?
нечто вроде (. java-class-method args) в Clojure?
AST макросы вроде http://force7.de/nimrod/manual.html#expression-macros ? Расширяемые грамматики с блоками вроде peg".." в http://force7.de/nimrod/pegs.html , SST блоки в Dylan XXX .. end XXX, определённые define XXX name .. end XXX name ?
Почему этот пример важнен, фичи 1,3 ?

>любой более-менее зрелый язык программирования имеет исходный код размером от 0,5 до нескольких Мб


однако, есть отклонения. С++ = 30..100 Мб, лисп = 5..30 Мб.
Dylan = 90 Mb, из которых большая часть browse database, PCH; Gwydion Dylan = 6 Mb; goo = 2Мб исходников, с почти той же семантикой, что и Dylan.
Nimrod = 5.30 Mb, Python = 20..40 Mb, Vala/Genie=1.86Mb, Delight= 1mb..17Mb

Это схожие языки/среды, со схожими синтаксисом, семантикой. Однако, отчего такие разбросы в размерах?

>поскольку трудозатраты на поддержку исходника такого объёма обязательно будут высоки.


тут можно привести OMeta и расширяемых грамматик как пример удачного использования: http://www.moserware.com/2008/04/towards-moores-law-software-part-3-of-3.html

Это не просто DSL, это произвольно расширяемый синтаксис, с ОО наследованием, перекрытием таких DSL.

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

А мы тут про Хаскель,да.. вот и интересует, какое требование из 1..6 (Краткость? Открытость языка? Системы типов? Встраивоемость?) может относиться в этом контексте "идеальный язык для таких-то целей" в пользу хаскеля?

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