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, ...)

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

>Может, ты ещё и определение скажешь?

По мне, так f a b и f [a b] всё наглядно демонстрируют. Кстати, частичное применение наоборот пример того, что сломает представление нескольких аргументов списком.

>При чём тут это?

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

aninamous
()

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

[code] Prelude> let a = 1 Prelude> let b = 2.0 Prelude> a + b

<interactive>:1:4: Couldn't match expected type `Integer' against inferred type `Double' In the second argument of `(+)', namely `b' In the expression: a + b In the definition of `it': it = a + b [/code]

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

Кстати, *где* такое нужно? У тебя есть реальный список примеров?

Еще один простейший пример. Функция взятия квадратного корня.

squareRoot x | x >= 0 = sqrt x
             | x <0 =  0 :+ (sqrt x)

Т.е. для чисел больших нуля используется sqrt x - стандартный квадратный корень из хаскелля, который с отрицательными значениями x не работает; а для чисел меньше нуля создается соответствующее комплексное число (кстати, на счет readability - кто бы мог подумать читая этот код, что :+ - это конструктор для комплексных чисел?). Ну и конечно же, этот код не скомпилировался.

    Occurs check: cannot construct the infinite type: a = Complex a
      Expected type: a
      Inferred type: Complex a
    In the expression: 0 :+ sqrt x
    In the definition of `squareRoot':
        squareRoot x | x >= 0 = sqrt x
                     | x < 0 = 0 :+ sqrt x

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

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

> Математик бы сказал что-то типа "некоторые из моих онлайновых и оффлайновых друзей попытались и не смогли".

"В Шотландии есть хотя бы одна овца, которая хотя бы с одной стороны чёрного цвета". Не делай из математиков придурков.

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

> ну а какая же отрасль знания занимается вопросом "качества языков программирования"?

Никакая, это вообще не вопрос науки. Точно так же, как не является вопросом науки "какая рыба вкуснее".

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

> По мне, так f a b и f [a b] всё наглядно демонстрируют.

Ага, отрабатываешь назад. Ты сказал "они так определяются" - так будь любезен, определение в студию.

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

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

[code] Prelude> let a = 1 Prelude> let b = 2.0 Prelude> a + b[/code]

*> let a = 1 in let b = 2.0 in a + b
3.0
Miguel ★★★★★
()
Ответ на: комментарий от anonymous

> Ну а дальше встает вопрос, какие аннотации поставить

Никакие, у тебя тут банальная ошибка типизации (плюс логическая ошибка, но это мелочи)

[code] squareRoot x | x >= 0 = sqrt x :+ 0 | x < 0 = 0 :+ sqrt (abs x) [/code]

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

Блин.

Ну а дальше встает вопрос, какие аннотации поставить

Никакие, у тебя тут банальная ошибка типизации (плюс логическая ошибка, но это мелочи)

squareRoot x | x >= 0 = sqrt x :+ 0
             | x < 0 = 0 :+ sqrt (abs x)
Miguel ★★★★★
()
Ответ на: комментарий от anonymous

Кстати, разрабы знают про досадную ошибку в Data.Complex?

Берем 0 и возводим его во 2 степень.

Prelude Data.Complex> 0 ** 2
0.0

Теперь возводим комплексный 0 во 2 степень.

Prelude Data.Complex> (0 :+ 0) ** 2 
NaN :+ NaN

Ну, что называется без комментариев, я бы никогда язык с «такими» ошибками не поставил в продакшн. Ну и статическая типизация никак от этого не уберегла, да.

В лиспе все нормально - там 0.

[1]> (expt #c(0 0) 2)
0

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

[code] Prelude Data.Complex> (0 :+ 0) ^ 2 0.0 :+ 0.0 [/code]

Если кто не в курсе, то x ** y определяется как exp (log x * y), так что всё логично.

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

> Если кто не в курсе, то x ** y определяется как exp (log x * y), так что всё логично.

Не, тут поражает другое, почему значение 0 ** 2 отличается от значения (0 :+ 0) ** 2. как говорится либо крестик снимите, либо трусы оденьте.

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

> let a = 1 in let b = 2.0 in a + b

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

> 1 + 2.0

3.0

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

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

ага, а + определяется через 0, и оператор примитивной рекурсии

Просто в хаскелле несколько (!) возведений в степень, каждая операция имеет свои «особенности» и имеет разные типы, - например, (0 :+ 0) ^ 2.1 уже нельзя, потому что 2.1 - это не целое. Кстати, Miguel, как мне «правильно» вычислить (0 :+ 0) в степени 2.1? :) Все из-за не очень хорошей системы типов, в частности с комплексными числами.

Вот здесь [1] объясняется более подробно. Там есть еще более фееричные примеры, чем с (0 +: 0) ** 2

Prelude> (-1)**2 :: Double
1.0
Prelude> (-1)**(2 + 1e-15 - 1e-15) :: Double
NaN

[1] http://haskell.org/haskellwiki/Power_function

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

>Ага, отрабатываешь назад. Ты сказал "они так определяются" - так будь любезен, определение в студию.

мне лень просто дальше пояснять, считаю, что и так уже достаточно. хотя ладно, можно и еще, например гипотетически:

f :: (T t) => t -> t -> t

f :: (T t) => [t] -> t

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

Практически нужно будет не только изменить определении функции, но либо приписать вызывающий код( добавить обёртку), либо использовать две различные функции (хотя может это и можно как-то обойти костылями). Опять трудности из-за системы типов, в динамике никто даже бы не заметил, что вместо (f (a b)) стало (f (&rest x)).

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

>let a = 1 in let b = 2.0 in a + b

И какой тогда смысл в ghci, а если бы это были нетривиальные примеры при тестировании реального кода?

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

> Никакие, у тебя тут банальная ошибка типизации

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

someFunction :: Float -> Float

Я не смогу написать, someFunction (squareRoot 144), придется пользоваться более частной someFunction (sqrt 144) напрямую, причем она нисколько не лучше моей более общей функции - она точно также выйдет с ошибкой при отрицательном значение и менее удобна к тому же - мне придется помнить не одно имя для функции, а 10 разных - каждую под свой случай. И в этой ситуации мне опять придется бороться с тайп-чекером.

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

>ну вот только что буквально читал определение императивного программирования со вводом как функтора некоторого вида, построенного из некоторого множества базовых функций (add, multiply, etc) с использованием операций, доступных в дистрибутивной категории

jtootf, дайте пожалуйста ссылку, где такое написано. Тоже очень хочется прочитать

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

>jtootf, дайте пожалуйста ссылку, где такое написано. Тоже очень хочется прочитать

R. F. C. Walters, "Categories And Computer Science"

у меня она в бумажном виде, возможно получится найти где-нибудь электронный. если что, глава 5 (Categories Of Functors), раздел Imperative Programs With Input

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

>> ну а какая же отрасль знания занимается вопросом "качества языков программирования"?

> Никакая, это вообще не вопрос науки. Точно так же, как не является вопросом науки "какая рыба вкуснее".

прикольный взгляд

а ничего, что 1% увеличения производительности труда программиста (за счет повышения качества языка программирования) даст экономию порядка 1 000 000 000 баксов в год?

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Miguel
Prelude> let a = 1 
Prelude> let b = 2.0 
Prelude> a + b

*> let a = 1 in let b = 2.0 in a + b

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

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

> Опять трудности из-за системы типов,

да, система типов недоработана

> в динамике никто даже бы не заметил, что вместо (f (a b)) стало (f (&rest x)).

это минус динамики, а не плюс.

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

>> Математик бы сказал что-то типа "некоторые из моих онлайновых и оффлайновых друзей попытались и не смогли".

> "В Шотландии есть хотя бы одна овца, которая хотя бы с одной стороны чёрного цвета". Не делай из математиков придурков.

Придурком там выставляется астрофизик, если что :-)

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

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от anonymous
squareRoot x | x >= 0 = sqrt x
             | x <0 =  0 :+ (sqrt x)

Я здесь говорю не о хаскеле, а о «языке мечты». Компилятор прав, отвергая такое. Однако:

1. В сообщении об ошибке он должен выплевывать определение АлгТД ComplexOrFloat, с которым бы это тайпчекнулось

2. При наличии в области видимости уже готового и подходящего АлгТД ComplexOrFloat и/или (зависит от храбрости) ключевого слова, разрешающего компилятору самому вывести такой тип, он должен его вывести.

И возможно, вдобавок, в языке должна быть более тесная связь между Float & Complex.

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

> Компилятор прав, отвергая такое.

Я категорически не согласен. Еще раз, где тут ошибка? Тут нет ошибки в самой функции. Тут должен быть варнинг (с предупреждением о возможной ошибки), а не ошибка.

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

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

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

> но подозреваю, что и с ней пример анонимуса не тайпчекнется, не?

Не должен по идее :)

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

>но подозреваю, что и с ней пример анонимуса не тайпчекнется, не?

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

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

>Еще раз, где тут ошибка?

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

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

> пример анонимуса не тайпчекается на вполне законных основаниях.

Да, на законных для HM вывода, поэтому я и получаю при компиляции ошибку.

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

Брр, ты о чем вообще?

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

> ошибка в том, что функция R -> R и функция R -> C - это две разные функции.

Да функции разные, а у меня-то функция R -> (R U C). У нас, что такого отображения не может быть?

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

>у меня-то функция R -> (R U C)

нет

>У нас, что такого отображения не может быть?

может. только функция тогда немножко другой должна быть

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

> нет

?

> может. только функция тогда немножко другой должна быть

Какой, если не секрет? Можно ли это выразить без аннотаций и оберток?

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

>и продолжить борьбу с шибко умным тайп-чекером

У функции возвращаемый тип должен быть либо комплексный, либо Either.

Очень хороший совет. Прежде чем писать чего-то на хаскеле, попробуй опрежелить у ЭТОГО тип. Очень помогает.

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

>?

R c C => (R U C) ~ C. тебе не кажется, что ты в принципе хочешь странного?

>Какой, если не секрет?

либо выкинуть эту функцию нафиг, и честно работать в C; либо использовать Either

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

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

> R c C => (R U C) ~ C. тебе не кажется, что ты в принципе хочешь странного?

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

> либо выкинуть эту функцию нафиг, и честно работать в C; либо использовать Either

Т.е. без аннотаций никак.

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

Даже просто просуммировать я смогу.

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

>тип R и С - это разные типы, т.е. отдельная переменная у нас не может быть и типа R, и типа С, в отличие элементов множества R и С

ну вот ты сам всё и написал. если тебе важна работа с R и C как множествами, ты используешь C; если тебе важно то, что R и C - разные типы, итебе надо это учитывать, ты используешь Either

>Т.е. без аннотаций никак.

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

>Даже просто просуммировать я смогу.

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

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

>А в какой нотации это записано?

в математической. R включается в C, стало быть их объединение будет эквивалентно C

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

>Реквестую поддержку MathML на ЛОР'е :)

присоединяюсь

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

> почему значение 0 ** 2 отличается от значения (0 :+ 0) ** 2

Потому что для Float-ов, ЕМНИМЭ, используется стандартная интеловская инструкция.

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

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

Prelude> :set -XNoMonomorphismRestriction
Prelude> let a = 1
Prelude> let b = 2.0
Prelude> a + b
3.0
Так лучше?

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

> как мне "правильно" вычислить (0 :+ 0) в степени 2.1?

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

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

> первое можно рассматривать не только как функцию двух аргументов

Нельзя, это функция одного аргумента. Только.

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

> а ничего, что 1% увеличения производительности труда программиста(за счет более удобного кресла)

FIXED

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

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

Обыкновенно,

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

или так

[32]> (expt #c(0 1) 2.7)
#C(-0.45399043 -0.8910065)
anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.