Здравствуйте, D. Mon, Вы писали:
VD>>Ну, примитивный он. Что тут еще можно сказать то? Много ограничений. Много случаев когда он не работает. Классика F#-а — f g не срабатывает, в то время как g |> f срабатывает. DM>А можешь показать, как конкретно этот баг получается в Хиндли-Милнере? По-моему, никак не должен.
Этот баг получается потому, что система типов не System F и не какое-то ее расширение, которое придумано с учетом возможности выводить типы. Это система типов .net, в которой нет типа
< foo : 'a; .. >
(это некоторое упрощение, для inline функций нечто похожее есть, но это пока можно оставить за скобками) и, соотвественно, код вроде такого
let foo e = e.Foo;;
Не может быть типизирован. Чтобы как-то с этим справится, Х-М в f# расширен некоей подпоркой, выводящей тип, используя информацию, которая доступна сверху-слева от типизируемого выражения (видимо, чтоб типизировать в один проход). Вот и получается, что так все работает:
> Array.map (fun e -> e.Name) <| typeof<int>.GetMembers()
- ;;
Array.map (fun e -> e.Name) <| typeof<int>.GetMembers()
--------------------^^^^^^
stdin(1,21): error FS0072: Lookup on object of indeterminate type based on information
prior to this program point. A type annotation may be needed prior to this program
point to constrain the type of the object. This may allow the lookup to be resolved.
Если же никакие сабтайпинги-перегрузки не используются — разницы нет:
> Array.map ((+) 1) <| [|1; 2; 3|];;
val it : int [] = [|2; 3; 4|]
> [|1; 2; 3|] |> Array.map ((+) 1);;
val it : int [] = [|2; 3; 4|]
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, D. Mon, Вы писали:
DM>А можешь показать, как конкретно этот баг получается в Хиндли-Милнере? По-моему, никак не должен.
В этом алгоритме вывод типов идет в одни проход. Это требует чтобы тип всех выражений мог быть выведен из предыдущего кода. И если складывается ситуация, что тип f можно вычислить только когда будет известен тип g, то получается вот такая вот дурь — f g не прокатывает, а g |> f проходит.
Кроме того оригинальный HM не поддерживал подтипы (читай ООП), параметрический полиморфизм и т.п. Потом было много работ решающий эти проблемы. Но успеха добились только Мартин Одесски и разработчики немерла. (хотя, возможно я чего-то не знаю)
Одесски (автор Скалы) предложил свой алгоритм для локального вывода типов в ООЯ (если не ошибаюсь — это было расширение HM). Москаль (автор вывода типов Немерла) усовершенствовал его сделав его многопроходным. Потом алгоритм Немерла допиливался мой еще несколько раз (сам себя не похвалишь... ).
На сегодня, на мой взгляд, алгоритм вывода типов немерла самый "умный". Найти разумный случай в котором споткнется современный алгоритм немерла очень не просто.
VD>>Дык глобальный то вывод типов по жизни никому не нужен.
DM>Зря ты так, очень даже нужен.
Я то тут прчим? Любой толковый программист тебе сразу скажет, что по крайней мере, типы для публичного интерфейса всегда нужно описывать явно.
DM>Т.е. ты, может, и привык без него обходиться, но мой опыт с окамлом говорит, что когда такой вывод типов есть, это очень хорошо и удобно. У меня в компиляторе на две с лишним тыщи строк очень мало где у функций задан явно тип, для подавляющего большинства он просто выводится. А там, где задан, это не из необходимости, а для задания "центра кристаллизации".
Ну, вот ты и сам подтверждаешь мои слова. Только нужно понять, что твоя "центра кристаллизация" — это на самом деле описание "контракта" коим и является публичный интерфейс.
В Немерле тоже в 99% случаев описывать типы не нужно. Основной (по объему) код расположен в телах методов. В теле метода ты можешь описывать десятки вложенных функций не вписав ни одного типа. Типы же требуется задавать для полей и сигнатур методов, тем самым специфицируя интерфейс.
Писать же объемный код не указывая типов — это форменный идиотизм. Первая ошибка в типах может обернутся в кашу из сообщений компилятора который будет указывать куда угодно, только не в место где появилась реальная проблема.
Так что глобальный вывод типов — это такая игрушка в которую можно поиграть, но которую лучше не использовать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
DM>>А можешь показать, как конкретно этот баг получается в Хиндли-Милнере? По-моему, никак не должен.
VD>В этом алгоритме вывод типов идет в одни проход. Это требует чтобы тип всех выражений мог быть выведен из предыдущего кода.
Это Х-М такой? Правда?
VD>Я то тут прчим? Любой толковый программист тебе сразу скажет, что по крайней мере, типы для публичного интерфейса всегда нужно описывать явно.
Прямо так и всегда? Вот есть у меня объявление
let my_global_const = 42
неужели так нужно выписывать рядом очевидный факт, что это int?
Или есть оператор композиции
let (|>) x f = f x
тип которого тоже тривиален, зачем его явно выписывать?
Для чего-нибудь сложного да, явные типы работают документацией и несут пользу.
DM>>Т.е. ты, может, и привык без него обходиться, но мой опыт с окамлом говорит, что когда такой вывод типов есть, это очень хорошо и удобно. У меня в компиляторе на две с лишним тыщи строк очень мало где у функций задан явно тип, для подавляющего большинства он просто выводится. А там, где задан, это не из необходимости, а для задания "центра кристаллизации".
VD>Ну, вот ты и сам подтверждаешь мои слова. Только нужно понять, что твоя "центра кристаллизация" — это на самом деле описание "контракта" коим и является публичный интерфейс.
Не-а. Какой еще контракт, когда из 10 функций описан тип одной или даже менее одной (можно задать тип отдельного параметра, не задавая остальных).
VD>Писать же объемный код не указывая типов — это форменный идиотизм. Первая ошибка в типах может обернутся в кашу из сообщений компилятора который будет указывать куда угодно, только не в место где появилась реальная проблема.
Собственно, чтобы не появлялась такая каша, у меня и указан тип в паре мест, этого хватает, чтобы компилятор не выдумывал глупостей и не порождал каши. Это я и называю центром кристаллизации — когда вокруг одного правильно указанного типа как в домино или в кристалле складываются правильно остальные типы.
VD>Так что глобальный вывод типов — это такая игрушка в которую можно поиграть, но которую лучше не использовать.
Да нет же, это просто удобный инструмент, который надо использовать с пользой — пусть хотя бы там, где типы вполне очевидны, выводит их сам. Я не против их ручного описания там, где это полезно или необходимо программисту, но обычно это далеко не 100% методов / функций.
Здравствуйте, D. Mon, Вы писали:
VD>>В этом алгоритме вывод типов идет в одни проход. Это требует чтобы тип всех выражений мог быть выведен из предыдущего кода.
DM>Это Х-М такой? Правда?
По крайней мере та разновидность что используется в F#.
Вообще, чистый Х-М, наверно, вообще не применим в современных языках, так как трудно сере представить, чтобы IDE на каждый чих парсила бы весь проект, строила бы уравнение типов, а потом его заново решала бы. Плюс это не работает в условиях перегрузки.
VD>>Я то тут прчим? Любой толковый программист тебе сразу скажет, что по крайней мере, типы для публичного интерфейса всегда нужно описывать явно.
DM>Прямо так и всегда? Вот есть у меня объявление DM>let my_global_const = 42 DM>неужели так нужно выписывать рядом очевидный факт, что это int?
Для констант и даже для константных выражений можно сделать исключение. В немерле так и сделано, кстати.
DM>Или есть оператор композиции DM>let (|>) x f = f x DM>тип которого тоже тривиален, зачем его явно выписывать?
Затем, чтобы его нельзя было нечаянно применить в неверном контексте.
С константами все ясно, так как они сами задают тип (не явно).
С функциями же все не так очевидно.
Потом не ясно где провести границу между простыми и сложными выражениями. Человеку легко сказать "выражение простое", но что это означает для компилятора?
Еще одной проблемой глобального вывода типов является то, что типы это негативно влияет на работу IDE, которым нужно икрементально обновлять внутреннюю информацию.
Если у нас есть модули типы которых могут меняться в зависимости от контекста их использования, то система становится принципиально не масштабируемой и не надежной.
Ну, а если типы не могут выходить за пределы модулей (классов для ООЯ), то логично было бы заставить явно декларировать типы для того что экспортируется во вне.
DM>Для чего-нибудь сложного да, явные типы работают документацией и несут пользу.
А где провести эту границу?
По жизни же будет так — если можно не писать типы, то найдутся те кто не будет их писать нигде и ни когда. А потом от этого будут страдать окружающие.
Так не логичнее было бы заранее определить те места где нужно писать типы, введя некоторый стандарт?
VD>>Ну, вот ты и сам подтверждаешь мои слова. Только нужно понять, что твоя "центра кристаллизация" — это на самом деле описание "контракта" коим и является публичный интерфейс.
DM>Не-а. Какой еще контракт, когда из 10 функций описан тип одной или даже менее одной (можно задать тип отдельного параметра, не задавая остальных).
Публичный. Главное, что описание сигнатур публичных функций — это ни разу не напряжно. Их действительно очень не много, по сравнению с кодом реализации.
VD>>Писать же объемный код не указывая типов — это форменный идиотизм. Первая ошибка в типах может обернутся в кашу из сообщений компилятора который будет указывать куда угодно, только не в место где появилась реальная проблема.
DM>Собственно, чтобы не появлялась такая каша, у меня и указан тип в паре мест, этого хватает, чтобы компилятор не выдумывал глупостей и не порождал каши.
Ну, а есть какие-то четки правила в каких местах нужно указывать типы? Ты может и можешь найти золотую середину, но дргие нет.
Потом для языка на котором ты пишешь есть IDE (или дугой тул) которая может показать типы любой части программы? Если нет, то твой код будет ой как не просто изучать непосвященному.
Я вот имею не малый опыт чтения и правки кода где типы указываются раз в год по обещанию. И я четко вижу, что понять смысл кода намного проще в условиях когда можно легко узнать какой тип имеет то или иное выражение/идентификатор.
DM>Это я и называю центром кристаллизации — когда вокруг одного правильно указанного типа как в домино или в кристалле складываются правильно остальные типы.
А я это называю безалаберностью. Пока ты работаешь над кодом один, может это и прокатит. В промышленном коде — это неприемлемо. Должны быть стандарты обеспечивающие качество. И лучше, если эти стандарты гарантируются зыком и его компилятором.
VD>>Так что глобальный вывод типов — это такая игрушка в которую можно поиграть, но которую лучше не использовать.
DM>Да нет же, это просто удобный инструмент, который надо использовать с пользой — пусть хотя бы там, где типы вполне очевидны, выводит их сам. Я не против их ручного описания там, где это полезно или необходимо программисту, но обычно это далеко не 100% методов / функций.
Дык никто не говорит о 100%. Если посмотреть на немерл (за скалу не скажу, но наверно и так примерно так же), то на каждый публичный метод приходится по несколько локальных функций. Понятно, что тут работает специфика ООЯ. Но для чистого ФЯ (читай для чисто процедурного языка ) такой границей могут выступать границы модуля.
Ну, а уточнять типы в местах что программист сочтет нужным — это уже полезная возможность.
Главное, что есть четка граница не дающая получить сильно связанный код и вытекающие из этого проблемы.
Потому я и утверждаю, что глобальный вывод типов не нужен. И между хреновым, но глобальным выводом типов F# и локальным, но качественным выводом типов Nemerle я не задумываясь выберу второй вариант.
Для языков типа Хаскеля, где нет перегрузки функций (за исключением классов типов, которые только с натяжкой можно отнести к перегрузки) и приведения типов можно создать довольно эффективный глобальный вывод. Но для ООЯ это уже слишком сложно. F# и так жертвует приведением типов и совместимостью с другими языками .Net ради эфемерных преимущество.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали: VD>А где провести эту границу?
А зачем ее проводить? У каждого человека(команды) свой стиль программирования, если кому то хочется глобальный вывод и они прикрасно с ним живут, зачем их застовлять что то там описывать?
Вот пишут я на хаскеле, в начале все сигнатуры методов могут менятся по сто раз, как только более менее устаканится можно нажать волшебное сочитание и у метода автоматов пропишется тип.
Здравствуйте, dotneter, Вы писали:
D>Здравствуйте, VladD2, Вы писали: VD>>А где провести эту границу?
D>А зачем ее проводить? У каждого человека(команды) свой стиль программирования, если кому то хочется глобальный вывод и они прикрасно с ним живут, зачем их застовлять что то там описывать?
Какой еще стиль? Это смелый и решительный шаг к завалу проекта даже средней сложности.
D>Вот пишут я на хаскеле, в начале все сигнатуры методов могут менятся по сто раз, как только более менее устаканится можно нажать волшебное сочитание и у метода автоматов пропишется тип.
Ну, вот и экспериментируй себе в песочнице. А когда добавляешь код в большой проект, то будь добр нажать свою волшебную кнопку.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Потом не ясно где провести границу между простыми и сложными выражениями. Человеку легко сказать "выражение простое", но что это означает для компилятора?
Ну так границу человек будет проводить, а не компилятор. Так что формализовать ее не нужно. Кому как удобнее, тот так и проведет. Главное, чтобы компилятор не мешал это делать.
VD>Потом для языка на котором ты пишешь есть IDE (или дугой тул) которая может показать типы любой части программы? Если нет, то твой код будет ой как не просто изучать непосвященному.
Да, в моем случае OcaIDE по наведению мыши показывает тип любого выражения и функции.
С твоими соображениями про IDE, про работу в команде и пр. я согласен.
Здравствуйте, VladD2, Вы писали:
D>>Вот пишут я на хаскеле, в начале все сигнатуры методов могут менятся по сто раз, как только более менее устаканится можно нажать волшебное сочитание и у метода автоматов пропишется тип. VD>Ну, вот и экспериментируй себе в песочнице. А когда добавляешь код в большой проект, то будь добр нажать свою волшебную кнопку.
Так экспериментировать-то даже в песочнице нельзя, если глобального вывода типов нет — приходится всегда указывать. Кроме того, отображение сигнатуры с типами в случае их автоматического вывода — это чисто технический вопрос, его можно, например, так решить:
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Re[24]: Популярность F#
От:
Аноним
Дата:
06.10.11 11:05
Оценка:
Здравствуйте, Klapaucius, Вы писали:
О! К картинке вопросик: это что ж, живой VisualHaskell к 2010 студии? Где такие раздают?
Здравствуйте, Klapaucius, Вы писали:
K>Так экспериментировать-то даже в песочнице нельзя, если глобального вывода типов нет — приходится всегда указывать.
Ой, да ладно придумывать то. Для песочницы локальный вывод типов отличается от глобального только тем, что приходится указывать типы у описаний типов. Все игры ведутся на базе локальных функций (которые, кстати, ничем не отличаются от глобальных в языках типа F#), в которых так же действует вывод типов.
На то она и песочница чтобы быть маленькой. В рамках песочницы можно на каждый раз перепарсивать и перетипизировать весь код. А вот в большом проекте так не удастая.
То что в F# слабый вывод типов — это как раз, плата за совмещение глобальности с необходимостью интерактивной работы (в IDE).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
K>>Так экспериментировать-то даже в песочнице нельзя, если глобального вывода типов нет — приходится всегда указывать. VD>Для песочницы локальный вывод типов отличается от глобального только тем, что приходится указывать типы у описаний типов. Все игры ведутся на базе локальных функций (которые, кстати, ничем не отличаются от глобальных в языках типа F#), в которых так же действует вывод типов.
"Песочница" — это, допустим, модуль, который сейчас разрабатывается. Можно его писать "от типов" — указать сначала типы функций, а потом уже писать код. А можно комбинировать имеющиеся — а типы будут какие получатся, и пока идет активное экспериментирование с кодом сигнатуры бы приходилось постоянно переисывать — зачем это нужно? На завершающем этапе их можно записать явно для документации (причем это можно сделать автоматически). Плюс некоторые комбинации первого и второго подходов. Второй подход без глобального вывода типов не работает, так что и выбора никакого не будет.
VD>На то она и песочница чтобы быть маленькой. В рамках песочницы можно на каждый раз перепарсивать и перетипизировать весь код. А вот в большом проекте так не удастая.
Зачем весь код? Модуль с которым мы работаем и те модули, которые на него ссылаются. Для автокомплита/подсказок и т.д. внутри модуля, с которым сейчас работаем перекомпилировать/тайпчекать те, которые от него зависят не нужно.
Вообще, под локальным выводом типов понимаются локальные функции и значения — вложенные в методы функции в Немерле, например. Поэтому не нужно говорить, что в случае "Локального" вывода типов нужно указывать типы только для публичных контрактов, которые и так нужно указывать и которые, в общем-то, часто зарание известны. Потому, что для локальных, неэкспортируемых методов, которые за пределами класса никто не видит — тоже придется сигнатуры с типами указывать. Вот в них-то как раз и проблема. Для ФЯ — это серьезная проблема, потому как в них пишется масса простых функций, для которых тип будет длиннее кода и который не особенно полезен. И функции эти постоянно изменяются в ходе экспериментов и поэтому переписывать для них типы каждый раз — элементарно неудобно. Не все из них можно сделать вложенными, да и те что можно — часто лучше не делать, потому как вложенные недоступны из REPL.
VD>То что в F# слабый вывод типов — это как раз, плата за совмещение глобальности с необходимостью интерактивной работы (в IDE).
Не согласен. Это плата за совмещение с ситемой типов .net, которая на глобальный вывод типов не рассчитана. И да, не факт, что глобальный вывод типов для .net-языка — это хорошая идея. Но это не означает, что для языка с другой системой типов, спроектированной с учетом возможности выводить типы, глобальный вывод типов оказется плохой идеей. как раз наоборот — там глобальный вывод типов желателен.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, InCus, Вы писали:
IC>>в лучшем случае проседают на 5-10%, по сравнению с мутабельным циклом, уже не радует.
K>Впечатляющий недостаток, конечно, на фоне остальных, ухудшающих производительность в разы.
С этого места подробнее. Только без обуждения убогости разработки гуев. Что в разы садит производительность кода на F#?
Здравствуйте, dsorokin, Вы писали:
D>Здравствуйте, InCus, Вы писали:
IC>>Ключевой вопрос в том какого F# вообще гадит подобными конструкциями, IC>>и почему за него должен расплачиваться JIT? JIT в отличии от F# не IC>>позиционировался как интелектуальный оптимизарующий компилятор, насколько я помню.
D>А зачем писать такие функции?
Здравствуйте, InCus, Вы писали:
IC>Что в разы садит производительность кода на F#?
CLR. Всё так называемое ФП в F# натянуто натянуто на CLR-ные объекты. Их получается ну очень много (каждый элемент списка, или каждая функция — отдельный объект), больше чем в классическом ООП. А логика их работы сложнее, чем тех же санков, бо проектировались они для решения более широкого круга задач.
Ну и отсутсвие оптимизаций, свойственных ФЯ. Не будет в F# stream fusion (т.к. список — набор обычных CLR-ных объектов, фьюзить их не совсем корректно), не будет распаковки пареметров туплов (т.к. тупл — тоже CLR-ный объект, с чего бы его распаковывать?) и тому подобного.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Здравствуйте, InCus, Вы писали:
IC>>Что в разы садит производительность кода на F#?
ПМ>CLR. Всё так называемое ФП в F# натянуто натянуто на CLR-ные объекты. Их получается ну очень много (каждый элемент списка, или каждая функция — отдельный объект), больше чем в классическом ООП. А логика их работы сложнее, чем тех же санков, бо проектировались они для решения более широкого круга задач.
ПМ>Ну и отсутсвие оптимизаций, свойственных ФЯ. Не будет в F# stream fusion (т.к. список — набор обычных CLR-ных объектов, фьюзить их не совсем корректно), не будет распаковки пареметров туплов (т.к. тупл — тоже CLR-ный объект, с чего бы его распаковывать?) и тому подобного.
Натануто естессно. А как же иначе. F# _обязан_ поддерживать совместимость по типам с фреймфорком. Сейчас F# это просто доставалка данных, а данные приходится визуализировать, чем то более вменяемым для построения гуйни. Если F# не будет натянут на CLR, то визуализатор не сможет ни иницировать получение данных, ни распознать их, что сведет смысл существования F# к 0. Утверждать, что проблемой является то без чего F# просто лишен смысла, в целом некорректно.
ПМ>... stream fusion ...
Я правильно понимаю что это такой хаскельный аналог модуля Seq?
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, InCus, Вы писали:
IC>>но его ломает анализировать ошибки
K>Не его. Он пишет что не сможет написать вывод типов, обрабатывающий такие случаи так, чтоб сообщения об ошибках вывода типов были понятными.
Че он там пишет, я понимаю. В переводе на человечий это назвается. "Кто хочет — тот делает, кто не хочет — ищет причины".
IC>>В соотвествии с п.3., на рассказы о том, что не работает из-за того, что "система типов расширена" можно не обращать внимание.
K>Если бы система типов не была расширена — ничего особенного Сайму изобретать и не пришлось.
Да что ж это за мантра то такая про "расширенную систему типов".
В GetName она не мешает, а в fun e -> e.Name она вот никак.
При этом, что типы обеих функций совершенно! одинаковые. Ну при чем тут расширение системы типов?
IC>>Самое смешное в происходящем, что компилятор мог бы автоматически выносить IC>>(fun e -> e.Name) в GetName и никаких бы проблем с анализом ошибок при этом не произошло, IC>>но это же делать надо, а у нас п.3.
K>Единичный пример не доказывает возможности делать это в общем случае.
Это не единичный пример. Это стандарный шаблон обхода ограничений F#.
Здравствуйте, InCus, Вы писали:
IC>Утверждать, что проблемой является то без чего F# просто лишен смысла, в целом некорректно.
Вопрос был про производительность, я ответил.
IC>Я правильно понимаю что это такой хаскельный аналог модуля Seq?
С очень большой натяжкой. Stream fusion нужно, чтобы не создавать промежуточные структуры данных, Seq тоже в какой-то степени позволяет избегать создания промежуточных списков, но Seq:
1. Seq не Stream. Seq слабее. Ты не сможешь, например, пробежаться до 5-го элемента, запомнить его, потом пробежать дальше, потом вернуться к 5-му элементу и снова пробежаться с него. Seq позволяет только заново пройти по всему списку.
2. Seq не поддерживает Fusion. Поскольку не устраняет промежуточных структур, а просто заменяет их другими (т.е. самой Seq). По идее Seq.map a << Seq.map b долно преврящаться в Seq.map (a << b) но этого не происходит.
Здравствуйте, Klapaucius, Вы писали:
K>Если же никакие сабтайпинги-перегрузки не используются — разницы нет: K>
>> Array.map ((+) 1) <| [|1; 2; 3|];;
K>val it : int [] = [|2; 3; 4|]
>> [|1; 2; 3|] |> Array.map ((+) 1);;
K>val it : int [] = [|2; 3; 4|]
K>
Не совсем так. Все еще хуже. В данных примерах типы вводятся только потому, что работа идет с константами (которые по определению имеют конерктный тип).
Но в главном я с тобой согласен — алгоритм вывода типов в F# очень слабенький. В МС могли бы и почитать работы авторов Скалы и Немерла. Там есть все ответы на все вопросы.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.