metaclass: (Default)
metaclass ([personal profile] metaclass) wrote2012-10-11 03:22 pm

Динамическая типизация, или статическая типизация для ленивых

Вчера [livejournal.com profile] ivan_gandhi сделал замечание что я, пользуясь динамически типизированной кложурью, при этом требую, чтобы в Java проверяли входные параметры на валидность. (Если что, проверка валидности в дотнете есть на каждом шагу, а объяснения вида "экономят циклы и не делаю проверки" в контексте жабы, тяжелого железа, JIT и прочего звучат крайне странно).
Собирался на эту тему устроить срач с утра, но [livejournal.com profile] thedeemon уже начал, так что я продолжу :)

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

Например, я хочу использовать кортеж с именованными полями (потому что позиционные кортежи нихера нечитабельны и их тип вида int*string*smallint*money*bool*Chervie ни о о чем не говорят). От входа в F# при этом нужно:
1) объявить этот чертов record где-то
2) сослаться на модуль с объявлением везде где он нужен
3) создавать экземпляры рекорда кривопачвярными конструкциями, причем оставить поля значениями по умолчанию нельзя.
4) паттерн матчинг с декомпозицией вроде не работает с рекордами.

Хаскель сразу закапываем - там на каждый случай имеется 3-4 расширения и 10 пакетов в hackage различной степени недоделанности, идиоматический подход - писать в point-free style, чтобы коллеги не разобрались, а работать в продакшене можно только с теми сторонними библиотеками, которые я могу сам починить.

При этом, у меня при работе с оперденями постоянно ситуации вида: есть запись с тремя полями, полученная из БД, мне нужно произвести обработку этой записи и добавить результат обработки в виде четвертого поля, получив новый тип записи.
Я НЕ хочу объявлять каждый раз такое руками и в Clojure это делается элементарно, добавлением нового ключа в map в функции-обработчике записей.

При этом РЕАЛЬНО динамическую типизацию я не использую. Она мне почти не нужна, потому что единственная ситуация, где вменяемый человек будет на одном цикле биндить к имени число, на втором строку, на третьем - список записей - это когда по условию задачи нужна, например, EAV-модель во все поля. И то - обычно EAV делается от безысходности, потому что пользователь не может нормально работать со схемой БД, а задача требует чего-нибудь вроде "добавить к части записей атрибут "фаза луны в которую производилась приемка товара"". В норме должны быть зависимые типы и миграции и пользователи бы пользовались той же системой типов что и разработчик.
Т.е. нормальный вывод типов - это когда программа берет типы из тут же описанного SQL-запроса, а рекорды расширяемые и объявлять их не нужно.

Второй use-case, где "вроде бы динамическая типизация" - это когда я делаю документы в виде кложурных структур данных, подгоняя комбинации списков-мапов-массивов-множеств под предметную область. В кложуре же это делается в лоб, а в хаскеле в один список не положишь три разных по структуре(типу) раздела документа. Но на самом деле, то что я делаю в кложури - это просто алгебраический тип данных "для бедных", без объявления заранее и без явно выделенных-именованных конструкторов данных. Если бы была возможность делать расширяемые и объявляемые по месту типы данных (чтобы каждый раз при разработке не переключаться между объявлением типа и конструированием данных по этому типу) - то было бы то же самое что в кложури - но статически типизированное.

PS: На ту же тему: http://justy-tylor.livejournal.com/190153.html

[identity profile] freiksenet.livejournal.com 2012-10-11 12:37 pm (UTC)(link)
"где вменяемый человек будет на одном цикле биндить к имени число, на втором строку, на третьем - список записей"

Ну никто же так не делает, причем тут вообще динамическая типизация? Формально даже статическая типизация не обязана этому мешать.

[identity profile] rssh.livejournal.com 2012-10-11 12:41 pm (UTC)(link)
кстати, case классы в скале как раз для этого предназначенны и нормально работают
(даже странно слышать что в C# ничего подобного нет)

[identity profile] rssh.livejournal.com 2012-10-11 12:41 pm (UTC)(link)
s/C#/F#/

[identity profile] metaclass.livejournal.com 2012-10-11 12:48 pm (UTC)(link)
В F# есть хитрожопые активные паттерны, но радости от них никакой - все равно много писанины.

[identity profile] volodymir-k.livejournal.com 2012-10-11 12:52 pm (UTC)(link)
> Если что, проверка валидности в дотнете есть на каждом шагу, а объяснения вида "экономят циклы и не делаю проверки" в контексте жабы

Сравните 3 кода:
public void execute(String sql) throws Exception {

a.
if(sql == null) throw IllegalArgumentException("sql must be not null");
b.
if(sql == null) throw NullPointerException("sql must be not null");
c.
sql = sql.toLowerCase();

а от б отличается только классом исключения, а от ц только сообщением. Вы видите полезную разницу? Я -- нет.
Зачем писать бесполезный код? Достаточно задокументировать, что @throws NullPointerException if sql is null

[identity profile] metaclass.livejournal.com 2012-10-11 01:02 pm (UTC)(link)
Полезная разница: в первом пункте я по сообщению об ошибке сразу узнаю, в чем причина, не занимаясь разгребанием стек-трейса. Кроме того, проверку нужно расположить в паблик-методе, сразу на входе, от чего оный стек трейс будет в два раза короче и понятнее:)

[identity profile] mikkim08.livejournal.com 2012-10-11 01:10 pm (UTC)(link)
В скале с кейс классами вроде та же фигня:

- об'явить case class MyRecord(x:Int = 0, s:String = ""),
- сослаться на него везде, где надо,
- об'явить новый case class MyOtherRecord(x:Int = 0, s:String = "", y:Int),
- и как-то написать трансформацию из MyRecord в MyOtherRecord.

Выходит довольно многословно.
Edited 2012-10-11 13:41 (UTC)

[identity profile] stdray.livejournal.com 2012-10-11 01:16 pm (UTC)(link)
>4) паттерн матчинг с декомпозицией вроде не работает с рекордами.
Работает же.

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

В любом случае, держать в уме, какой там тип был, во что он превратился после расширения сложно. То есть мне это чем-то point-free style напоминает.

[identity profile] metaclass.livejournal.com 2012-10-11 01:28 pm (UTC)(link)
Вот в clojure приходится держать в уме. В норме хотелось бы, чтобы про это думал компилятор.

[identity profile] kosiakk.livejournal.com 2012-10-11 02:10 pm (UTC)(link)
Умные люди, которые пишут Котлин, решили не делать Tuples совсем.
Т.к. единственное их применение - возврат нескольких значений из функции, то и проблему нужно решать на корню.

Вместо кортежей нам предлагается использовать всё-таки именованные data class. Мол, если вам придётся всё-таки назвать это - заставит лишний раз подумать, нужно ли оно в принципе =)
http://blog.jetbrains.com/kotlin/2012/09/how-do-you-traverse-a-map/

[identity profile] thesz.livejournal.com 2012-10-11 02:13 pm (UTC)(link)
Я затрудняюсь представить себе путь мысли и способы использования Хаскеля, чтобы получилась вот такая гротескная картина.
wizzard: (фото)

[personal profile] wizzard 2012-10-11 02:16 pm (UTC)(link)
нужно такое себе IDE которое из динамических хинтов компилит модель системы, где можно посмотреть все выведенные статически типы, и при желании дохинтовать руками.

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

[identity profile] volodymir-k.livejournal.com 2012-10-11 02:18 pm (UTC)(link)
> я по сообщению об ошибке сразу узнаю, в чем причина, не занимаясь разгребанием стек-трейса

не узнаете, потому что 1 к 1

> проверку нужно расположить в паблик-методе, сразу на входе

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

А если 2 паблик метода, то все эти проверки дублировать, да?

если вы такой умный, то чего вы библиотеки не пишете

[identity profile] metaclass.livejournal.com 2012-10-11 02:37 pm (UTC)(link)
Я в основном говорю про тривиальные проверки - допустимые диапазоны величин, в основном. Со сложными контрактами - зависит от того, насколько они сложные. В экстремальных случаях это будет что-нибудь вроде "показ ошибки компилятором там где она возникла, а не в десятке вызовов вверх по стеку там где ошибка вызвала конфликт типов".

Сериализуемость, впрочем, можно статик типизацией лечить - т.е. "метод принимает IList и никак иначе".

Библиотеки я пишу, в основном для внутреннего использования, изредка на заказ.

[identity profile] stdray.livejournal.com 2012-10-11 02:56 pm (UTC)(link)
def(test){
    {test with HellFiled = 666"}
}

В этом примере в абстрактном синтаксисе, я расширил test полем HellFiled
Я к тому, что обычно можно найти кусок кода с описание типа, с которым приходится иметь дело. Здесь же остается надеятся только на проверки компилятором и на поддрежку IDE. Компактно, но я не уверен, что удобно.

[identity profile] golikov konstantine (from livejournal.com) 2012-10-11 04:02 pm (UTC)(link)
Тогда можно использовать tuples. Не поддерживают именованные параметры, но при деструктуризации в паттернматчинге можно прикрутить им значимые имена

аля:

tupleX match {
case (id, name, surname) =>
println(name + " has id = " + id)
getGoodiesById(id)
...
}

[identity profile] golikov konstantine (from livejournal.com) 2012-10-11 04:06 pm (UTC)(link)
Чем это отличается от case class в Scala?

[identity profile] yurrrrr (meowth) (from livejournal.com) 2012-10-11 04:33 pm (UTC)(link)
1C же!

[identity profile] antilamer.livejournal.com 2012-10-11 04:43 pm (UTC)(link)
> не узнаете, потому что 1 к 1
По сообщению "sql нулевой" понятно, что sql нулевой.
По эксепшну "NullPointerException внутри execute(sql)" без всего - непонятно, что именно нулевое. Может, это вообще не в параметре дело, а коннекшн протух или еще что-нибудь.

[identity profile] thedeemon.livejournal.com 2012-10-11 04:47 pm (UTC)(link)
Ребе, обычный C# и анонимные типы в нем спасут российскую белорусскую демократию что там у вас.
var v = new { Amount = 108, Message = "Hello" };
Или если угодно окамл и его объекты, которые можно создавать вообще без объявления класса, и все будет статически проверено. Короче, "нет языков" - не верю.
Edited 2012-10-11 16:49 (UTC)

[identity profile] metaclass.livejournal.com 2012-10-11 05:07 pm (UTC)(link)
Верните анонимный тип в C# из метода и я вам поверю:)

[identity profile] thedeemon.livejournal.com 2012-10-11 05:23 pm (UTC)(link)
Ха, действительно. Я на современном шарпе не пишу, прокололся. Ну, значит остается окамл. :)

[identity profile] mikkim08.livejournal.com 2012-10-11 05:24 pm (UTC)(link)
А хочется именнованных параметров и т.д.

[identity profile] antilamer.livejournal.com 2012-10-11 05:27 pm (UTC)(link)
Я думаю, что динамическая типизация таки нужна, и даже что, может, она победит. Нужна она, концептуально, в гетерогенных и динамических системах, где какие-то компоненты или внешние службы могут не очень предсказуемо менять контракт, а нам нужно при этом хоть как-то продолжать работать. Тривиальный пример - javascript в браузере :)

Впрочем, я не исключаю, что получится как с IO в хаскелле: внутри чистый костяк, а снаружи небольшая нечистая обертка. Будет внутри строго типизированный костяк, а на системном уровне - динамическая типизация, duck typing, protocol negotiation, обработка ошибок, fallback и пр.

[identity profile] funbuttrue.livejournal.com 2012-10-11 06:06 pm (UTC)(link)
Кстати, а возврат dynamic типа вам подойдёт?

Page 1 of 10