Архив за месяц: Декабрь 2014

Об отступах в коде Haskell

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

Как известно, отступы в коде могут выполняться как пробелами, так и табуляторами. Обратите внимание на следующие два скрина, снятых с Notepad++:




Второй вариант отличается от первого только тем, что между ключевым словом let и образцом sideArea вместо пробела стоит табулятор. Однако визуально выравнивание [форматирование] выглядит совершенно идентично:


Этот код был переписан мною из книги М. Липовачи. Понятное дело, что на бумаге не разобрать, где пробелы, а где табуляторы. Изначально я написал вариант, показанный мною на первом скрине. Визуально это в точности соответствовало тому, как обозначенный код выглядел в книге. Однако попытка загрузить этот код в ghci завершилась неудачей:

λ: :l src
[1 of 1] Compiling Main             ( src.hs, interpreted )

src.hs:4:25: parse error on input `='
Failed, modules loaded: none.
λ:

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

λ: :l src
[1 of 1] Compiling Main             ( src.hs, interpreted )
Ok, modules loaded: Main.
λ: cylinder 10.0 20.0
1884.9555921538758
λ:

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


то такой файл так же будет успешно загружен.

Нельзя сказать, что какой-то из  трёх обозначенных выше вариантов не отформатирован - они все отформатированы. Однако в Haskell, как видим, форматирование форматированию рознь, даже если визуально никакой разницы не видно. Вот такая может быть "музыка"...

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

Проблема с обновлением cabal

Проблема: попытки обновить cabal не приводят к появлению более новой версии программы.

Изначально я проверил текущую версию cabal:

C:UsersАндрей>cabal --version
cabal-install version 1.18.0.5
using version 1.18.1.3 of the Cabal library

Затем отправил запрос на проверку наличия более новой версии:

C:UsersАндрейReal>cabal update
Downloading the latest package list from hackage.haskell.org
Note: there is a new version of cabal-install available.
To upgrade, run: cabal install cabal-install

Как видим, более новая версия существует. Запускаю команду обновления до более свежей версии:

C:UsersАндрейReal>cabal install cabal-install
Resolving dependencies...
Notice: installing into a sandbox located at
C:UsersАндрейReal.cabal-sandbox
Configuring cabal-install-1.20.0.6...
Building cabal-install-1.20.0.6...
Installed cabal-install-1.20.0.6

Смотрю номер обновлённой версии cabal:

C:UsersАндрей>cabal --version
cabal-install version 1.18.0.5
using version 1.18.1.3 of the Cabal library

Как видим, ничего не изменилось - номер тот же, что и до обновления. Снова запускаю проверку наличия более новой версии:

C:UsersАндрейReal>cabal update
Downloading the latest package list from hackage.haskell.org
Note: there is a new version of cabal-install available.
To upgrade, run: cabal install cabal-install

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

В Интернете нашёл причину такого поведения и способ его исправления: нужно в системной переменной PATH прописать значение %AppData%cabalbin перед значением %PROGRAMFILES%Haskell Platform...bin.

После внесения изменений в PATH я попытался выполнить обновление, но в процессе получил ошибки. Однако после этого версия cabal стала отображаться более новой и проверка обновлений стала показывать, что установлена последняя версия приложения:

PS C:UsersАндрей> cabal --version
cabal-install version 1.20.0.6
using version 1.20.0.2 of the Cabal library
PS C:UsersАндрей> cabal update
Downloading the latest package list from hackage.haskell.org
Skipping download: Local and remote files match.



Созданеи MSI для приложений .Net

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

Собственно, сейчас там вот так:
При попытке создать этот проект, открывается html страничка, которая предлагает перейти на сайт Flexera (кто это такие?) и отдав им ключи от квартиры получить возможность создавать инсталяторы:
Немножко поискав, я нашел два способа создания инсталяторов без предоставления ключей от квартиры. Вот о них и поговорим.
1. Расширение для студии Microsoft Visual Studio Installer Projects. После его установки, в окне новых проектов становится доступна новая ветка в дереве и четыре проекта в ней:
Проекты создаются с минимумом настроек:
Мы указываем что будет копироваться в папку приложения, что будет создаваться на рабочем столе, ну и в меню программ. Инсталятор при запуске запросит куда устанавливать приложение, пропишет себя корректно в списке программ доступных для удаления, ну и удалится все при необходимости.
2. Второе решение это WiX Toolset. Он ставится со своего инсталятора:
После установки, в дереве новых проектов добавляется ветка с семью типами проектов:

Проект выглядит не сложнее чем из пункта 1, содержит одну XML и папку для Reference:
Добавляем ссылку на проект или проекты которые должны инсталироваться, а вот дальше... Файлик с расширением wxs позволяет в очень широких пределах настраивать наш инсталятор. Самое простое, это прописать приложение для развертывания:

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

Выводы. Если нужно написать простенький инсталятор, то ставим Microsoft Visual Studio Installer Projects и не заморачиваемся с настройками. Если нужно или с высокой вероятностью нужно будет в будующем серьезные возможности по работе с реестром, настройке мастера и так далее, то WiX Toolset вам в помощь.

Борьба с очень длинными путями в SQL запросах

Вчера ко мне обратился коллега с проблемой, при выборке данных в View с другого сервера возникает ошибка "The object name 'AW1.SAR.ICM.DialerDB.dbo.ViewDialerReport1' contains more than the maximum number of prefixes. The maximum is 3." Что это за проблема и как с ней бороться под катом.

Есть два SQL сервера, на одном есть View, а на другом нужно создать View которая будет забирать данные из View с первого сервера. Зачем так, не спрашивайте, особенности мироустройства такие.
На втором сервере первый подключается через Linked Server:
Запрос со второго сервера вида:
select * from AW1.SAR.ICM.DialerDB.dbo.ViewDialerReport1
Приводит к ошибке указанной в начале этой статьи:
В запросе можно ее решить просто, достаточно имя сервера взять в квадратные скобки:
select * from [AW1.SAR.ICM].DialerDB.dbo.ViewDialerReport1
Все работает:
Но это не самое интересное, если попробовать создать View по этому запросу, то выдается очень похожая ошибка:
 
Сначала я нашел сложный вариант, через создание функции:
CREATE FUNCTION dbo.func_ViewDialerReport1()
RETURNS TABLE
AS
RETURN (
    select * from [AW1.SAR.ICM].DialerDB.dbo.ViewDialerReport1
);
 
А потом, на ее основе уже View:
CREATE VIEW dbo.view_ViewDialerReport1 AS
SELECT * FROM func_ViewDialerReport1()
Но, уже когда писал эту статью, меня осенило, а зачем делать функцию, если при генерации SQL скриптом квадратные скобки не чистятся и можно сразу писать вот так:
CREATE VIEW dbo.view_ViewDialerReport1 AS
select * from [AW1.SAR.ICM].DialerDB.dbo.ViewDialerReport1
Оба способа (и через функцию и сразу генерить View) работают. Так что, SQL команды работают правильно, а вот дизайнер тупит в таких случаях.


Управляемая альтернатива ExplodeAllProxy и RemoveAllProxy

В данной заметке даётся ссылка на полный (т.е. без "цензуры" того или иного форума) исходный код управляемой библиотеки, реализующей функционал, аналогичный командам ExplodeAllProxy и RemoveAllProxy от Александра Ривилиса. Обозначенный исходный код может использоваться не только в AutoCAD, но и в nanoCAD, BricsCAD, а так же в любом приложении, работающем на базе платформы Teigha.

Некоторое время назад А.Н. Ривилис опубликовал исходный код своего ARX расширения (за что я ему весьма признателен). Ознакомившись с исходниками А.Н. Ривилиса я написал схожее по функционалу .NET расширение. Код писался сразу с прицелом на то, чтобы он мог успешно компилироваться под:
  • все версии AutoCAD не старее чем 2009-я (в более старых не тестировал) 
  • nanoCAD 6.0 (и любые более новые версии)
  • BricsCAD 14.2 (и любые более новые версии)
  • любое автономное приложение, использующее в своей работе платформу Teigha.
В принципе, возможно, что код мог бы работать и в nanoCAD 5.0, но во первых, эта версия у меня отсутствует, а во вторых, потребуется, чтобы разработчики выпустили патч для nanoCAD 5.0, устраняющий баг реализации управляемой обёртки для класса OpenCloseTransaction. Для версии nanoCAD 6.0 такой патч существует. Скачать его можно, например отсюда. Кстати, без этого патча код данного расширения не сможет работать в nanoCAD 6.0, так что патч нужно устанавливать обязательно.

В ходе тестирования выяснилось, что команда rmScales аварийно завершает работу BricsCAD. Я задал вопрос в службу технической поддержки BricsCAD и достаточно оперативно получил ответ:
Я подтверждаю, что BricsCAD аварийно завершает работу в результате выполнения прилагаемого кода. Нулевой указатель передается в панель Свойств объектов (свойство "Annotative"). Проблема устранена в версии v15.1.02 и всех последующих версиях.
Аварийного завершения работы можно избежать двумя способами:
1. закрыть панель Свойств;
2. в методе GetFreeAnnotativeScaleIds() удалять все AnnotationScale объекты, кроме текущего (определен в переменной CANNOSCALE)
При дальнейшем изучении проблемы выяснилось, что AcDbDatabase::purge(AcDbObjectIdArray&) из ObjectARX не возвращает id текущего именованного масштаба (задается переменной CANNOSCALE). В то же время, этот id возвращается методом OdDbDatabase::purge(OdDbObjectIdArray&) в Teigha. Баг репорт с этой проблемой был направлен в Open Design Alliance. 

Мною были внесены предложенные изменения в исходный код метода GetFreeAnnotativeScaleIds и проблема с работой команды rmScales исчезла.
Дополнительно, обозначенный в этой заметке код был задействован мною в моём же автономном консольном exe приложении, предназначенном для программной пакетной обработки чертежей. Это приложение использует платформу Teigha 4.0. Поскольку код совместим с Teigha, то портировать его на любое др. приложение, использующее эту платформу, не должно составлять особого труда.

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

В коде определены следующие команды:
  • proxy - вывести в консоль информацию о количестве объектов ProxyEntity и ProxyObject, имеющихся в текущей базе данных чертежа.
  • xProxy - расчленить все объекты ProxyEntity имеющиеся в текущей базе данных чертежа.
  • rmProxy - удалить из текущей базы данных чертежа все объекты ProxyEntity и ProxyObject.
  • rmScales - удалить из текущей базы данных чертежа все неиспользуемые аннотативные масштабы из общего списка аннотативных масштабов.
Обозначенный код тестировался в:
  • AutoCAD 2009 SP3 x64 Enu
  • AutoCAD 2015 SP1 x64 Enu
  • nanoCAD 6.0 x64 Rus (при этом необходимо установить обновление hostdbmgd.zip)
  • BricsCAD 14.2.17 x64 Enu
  • собственное приложение, работающее на основе платформы Teigha 4.0.

Откомпилированная версия для AutoCAD 2009-2016 - здесь (см. файл readme.txt), исходники выложены в открытый доступ на BitBucket.

О пользе возможности частичного применения функции

Маленький пример на тему практической пользы возможности частичного применения функций.


Предположим, что имеется некоторая функция, выводящая приветствие:

printHello::(String->String)->String->String
printHello f x = "Hello, " ++ f x ++ "!"

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

Поприветствуем Васю:

ghci> printHello (x->x) "Vasya"
"Hello, Vasya!"

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

shortName::String->String->String->String
shortName [] _ xs = xs
shortName (x:_) [] xs = x : ". " ++ xs
shortName (x:_) (y:_) xs = x : '.' : y : ". " ++ xs

Параметры в shortName ожидают последовательно имя, отчество и фамилию, на основании которых и будет формироваться конечная строка. Попробуем функцию в деле:

ghci> shortName "Vasiliy" "Vasilievich" "Vasiliev"
"V.V. Vasiliev"
ghci> shortName "Vasiliy" "" "Vasiliev"
"V. Vasiliev"
ghci> shortName "" "Vasilievich" "Vasiliev"
"Vasiliev"
ghci> shortName "" "" "Vasiliev"
"Vasiliev"

Заметьте, сигнатура функции shortName отличается от сигнатуры той функции, которая первым параметром передаётся в printHello. В виду этого мы могли бы снова прибегнуть к помощи лямбда-выражения и использовать shortName при вызове printHello например так:

ghci> printHello (x->x) $ shortName "Vasiliy" "Vasilievich" "Vasiliev"
"Hello, V.V. Vasiliev!"

Однако, благодаря возможности частичного применения функций, мы спокойно можем передать shortName первым параметром с двумя аргументами вместо трёх (третий [фамилия] будет передан ей функцией printHello):

ghci> let n = shortName "Vasiliy" "Vasilievich"
ghci> printHello n "Vasiliev"
"Hello, V.V. Vasiliev!"

или так:

ghci> printHello (shortName "Vasiliy" "Vasilievich") "Vasiliev"
"Hello, V.V. Vasiliev!"

Поскольку в данном случае в shortName передаётся два параметра вместо трёх, то генерируется промежуточная функция, сигнатура которой совпадает с ожидаемой. Этой сгенерированной функции printHello передаёт в качестве параметра фамилию, что собственно и приводит к запуску функции shortName. В результате получаем ожидаемую строку.

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