-
Странный конечно вопрос, но все же. Eсть функция 'mkFun' которая берет имя и что-то ещё и возвращает что-то: 1. myFunName = mkFun "myFunName" $ somethingElse Но! Это совсем неудовлетворительно. Во-первых некрасиво, во-вторых нужно следить за уникальность имен, ну и писать руками. Хотелось бы что-нибудь эдакое: 2. myFunName = mkFun' $ somethingElse и это раскрывается в (1). Но здесь без TH не обойтись, верно? Или по крайней мере: myFunName = mkFun'' $ somethingElse что раскрывается в: 3. myFunName = mkFun "generatedUniqueName" $ somethingElse Но здесь без CPP не обойтись. Идентикатор может быть любого типа, главное чтобы он был уникальным и на нем был определен Eq, а ещё лучше Ord. Есть ещё вариант затащить это в State и крутить там счетчик, как-то так: (myFunName1, myFunName2) = runModule "myModuleName" $ do myFunName1 <- mkFun' $ somethingElse myFunName2 <- mkFun' $ somethingElse return (myFunName1, myFunName2) но это совсем несерьезно, тем более нужно опять же именовать модуль и следить за уникальность этих имен. Ну и самая безумная идея, глобальный IORef счетчик... Так вот, как это сделать по-нормальному?
Replies (74)
-
@SannySanoff, Да ну а как в ней жить-то? Планируется EDSL и хочется чтобы можно было в разных (хаскелевских) модулях "ембеддить" функции.
-
@fmap, а, я думал у тебя какой-то генератор кода... а шо у тебя?
-
@SannySanoff, Что-то вроде deep embedded EDSL для си-подобного языка. В конечном счете генерироваться будет, да.
-
@SannySanoff, Это придется все определения засовывать в квотацию и потом делать сплайсинг получается?
-
@max630, я вот что делал: gist.github.com
это примерно аналогично hackage.haskell.org , только без TH в топлевеле (а там он постепенно становится хуже) и без дурацкого манглинга в декларациях. Мне нравится -
@fmap, а ты напиши что ты хочешь, на псевдо изыке, а то вон max360 понимает, наверное, а я — нет.
-
@SannySanoff, я не то чтобы понимаю, я своё вспомнил по ключевым словам
-
@max630, Я хочу писать определения функций в топлевеле которые будут транслироваться в некоторое AST. Это самое AST представлено в виде GADT с кучей классов в конструкторов, в которых я уже сам скоро запутаюсь. Это GADT затем переводится в сишный код плейнтекстом.
По большому счету нужно сделать как можно больше проверок во время компиляции, через типы, с другой стороны все это должно быть достаточно гибким чтобы генерировать и компоновать код в рантайме.
Здесь /6 в принципе показано как это выглядит. -
@fmap, Ах да. Зачем вообще нужны имена. Чтобы потом строить зависимости между функциями и кодогенератор "выплевывал" только то что нужно. В противном случае остается только генерировать функции (сишные) столько раз сколько они встречаются, просто под разными именами. Это тоже не решение.
-
@SannySanoff, А вы знакомы с OpenCL? Может я не с того конца объясняю.
-
@fmap, ты само хаскельное тело хочешь затем переводить в AST? Или там будет другой язык, более DSL? Ты хочешь потом из этого DSL ссылаться на функции, определенные выше?
Люди пишут просто монаду, код в которой выглядит как программа (на бейсике, да), а при выполнении строит AST. Ну, а дальше сам смотришь.
fun1 = do
....LET A = "2";
....RETURN;
fun2 = do
....PRINT "HELLO";
....GOSUB fun1;
....PRINT A;
такое
не знаю как поформатируется жуйком -
@SannySanoff, Да, например:
a :: Body CLBool
a = do c <- new true
.......d <- fun(c, c)
.......while (c) $ do
.........c =: false
.......ret false
Раскрывается в внутреннее представление, и затем мы можем им ворочить как угодно. А затем перегнать в плейнтекст который скормим OpenCL'ному компилятору.
showCode $ stripBody $ mkBody a
bool c = true;
bool d = fun(c, c);
while (c)
c = false;
return false -
@SannySanoff, Да только проблема в том что я хочу не только тело определять но и функции. И что бы это выглядело приемлимо и можно было писать руками.
-
@fmap, я думал об этом. И пришел к выводу, что:
1) по сложности вся указанная процедура генерации (монада, базовые ф-ии итд) будет сравнима с обычным source-level транслятором на Parsec
2) сообщения об ошибках во время компиляции твоих программ GHC затрахают тебя уже на следующую неделю. Не говоря уже о том, как на них будет смотреть пользователь твоего языка, если он только не твой очень хороший друг.
И с тех пор я уже делал через парсек. Его можно зафигачить и в TH, чтобы преобразовывалось в compile-time, есличо. -
@fmap,
Такой пример:
fun1 = mkFun $ \ x -> x
kernel = mkKernel $ \ a -> fun1 (fun1 a) >> ret void
Здесь проблема в том, что если не давать функциям уникальные идентификаторы то придется fun1 генерировать два раза, но под разными именами, поскольку мы не знаем что обе fun1 в kernel одинаковы на DSL'ном уровне. -
@fmap, я вот думаю, что даже на простом интуитивном уровне понимания, с монадами или без, ты не получишь AST из тела функции так, как ты описал выше.
Вот смотри, fun1 = mkFun $ \x -> x, это ведь fun1 = mkFun id. Дальше понимай сам. -
@SannySanoff, Все разбирать в квотере я умучаюсь и не смогу, тем более планирую реализовать только то что понадобится. Пока что наверно попробую просто делать такое преобразование:
myFun a b c = ...
==>
myFun = mkFun "myFunName" $ \ a b c -> ... -
@SannySanoff, Нет, на самом деле я забыл там затащить в монаду Block. Должно быть так:
fun1 = mkFun (ret . id) -
@fmap, посмотри на свой accelerate, например сюда ( github.com ) . Там строятся expressions (Exp) и программа (App), и хаскиль используется чтобы построить такую себе недецкую программу и недецких нарепликейтить выражений. Тебе нужно писать туда не id, а CL.id, итд.
-
@fmap, mkFun передает в лямбду уже созданный(c Index'ом) Var отсюда:
data Expression :: -> where
Lit :: (LangType a, Literal a) => a -> Expression a
Var :: LangType a => Index -> Expression a
App :: (ParamList a, RetType b) => Expression (a -> b) -> Expression a -> Expression b
Fun :: (ParamList a, RetType b) => Spelling -> Maybe (Body b) -> Expression (a -> b) -
@SannySanoff, В каком смысле совпадений? Дело в том что у меня проще все устроено и код должен транслироваться 1 к 1му, безо всяких страшных оптимизаций на уровне функций. Этого не требуется, требуется именно EDSL чтобы во время компиляции убедиться что все нормально и OpenCL'ный компилятор скорее всего не будет ругаться.
-
@fmap, а во что будет транслироваться mkFun2 "test" $ \ a b -> do forever $ do {new true} ?
-
@SannySanoff, в лапшу из
a test(a1 b, a2 c)
{
bool a3 = true;
bool a4 = true;
bool a5 = true;
...
}
где a, b, c свободные типовые переменные которые где-нибудь там наверху уточняются, поскольку в этом теле не ограничены. -
@SannySanoff, Ну оно ни во что не будет генерироваться, поскольку зависнет или грохнется с переполненным стеком. Но это не то что ожидалось услышать я полагаю.
-
@fmap, а каким боком ты вообще получишь хоть в каком-то виде в своем коде, чем именно является forever? что, если я заюзаю Control.Monad.Loops и оттуда какой-нибудь forM? Ты сильно смешиваешь уровни в одну кучу, не будет так работать.
Твой код должен выглядеть так:
type1 = CL.mkStruct ...
true = CL.makeConst typeBool "true"
test <- CL.mkFun $ do
...[b,c] <- CL.mkArgs [type1, type2]
...forM [1..10] $ \i -> do
......a <- CL.construct true
......return
return -
@fmap, причем именно тут mkArgs говорит себе тихо внутре монады, что он сделал 2 аргумента (и их значения суть ptr1, ptr2, и теперь когда в нижележащем коде появляется ссылка на них, то это 1 и 2й арг соответственно, а имена к ним придут во кремя линковки, если надо будет), и эти 2 аргумента вернул тебе. CL.construct тоже говорит что ввела в scope новую переменную, и что если ссылка пойдет, то это на N-ю локальную переменную. И вернула ptr3 тебе, ты ее сохранил в "a"... итд
-
@SannySanoff, А, так так и есть. Я подумал про конечный код. Да, вот например булены:
newtype CLBool = CLBool Bool
true :: Expression CLBool
true = mkLit $ CLBool True
false :: Expression CLBool
false = mkLit $ CLBool False
instance LangType CLBool where
typeOf = mkTypeRepr "bool"
А то что сверху будет что-то такое:
Fun (Ordinary {- means not an operator -} "test") (Scope (StateT (infinity) (Writer ([Def 1 (Lit $ CLBool True), [Def 2 (Lit $ CLBool True), [Def 3 (Lit $ CLBool True) ...], ())))) -
@fmap, ну, так вот, пиши монаду, чтобы она внутре сохраняла что именно она тебе вернула, и если получит вдруг это взад, чтобы замапило на свой внутренний словарь, и не надо будет тебе 2 раза определять или имена давать в кавычках.
-
@fmap, нет, в accelerate там нет символов, как я посмотрю. Там всё проще.
-
-
@fmap, я смотрю AST, там определяется маленький чистый ФЯ-чок.... кстати там есть let-bindings
-
@fmap, я не знаю высоких материй.... я бы сделал на сегодняшний день x <- mkFun .... :: MyGenerator FunDef ; x <- mkForLoop :: MyGenerator Statement ; x<- mkExpr :: MyGenerator Expr итд
-
@SannySanoff, Я долгими бессонными ночами думал как сделать let'ы через монады и чтобы не было new. Но без RebindableSyntax, конечно. Ничего не придумалось.
-
@SannySanoff, Ну а так их надо в список ложить, а Expression параметризован типом реального CL'ного типа. Сверху оно якобы проходит тайпчек и все хорошо, а в списочке за экзистенциалами потому что деваться некуда.
-
@fmap, ну, Expr да, конечно, должен быть типизован... главное не пере-затипизовать 8)
-
@SannySanoff, тут еще какое дело, можно типы чекать в момент линковки этого AST, а в момент не компиляции хаскилем. Если у тебя юзер-типы не расширяются, то конечно можно и на GHC повесить. Ну, это от задачи зависит... а то типизуешь ты EXP хаскельными типом, и шо, как токо перцу нужно будет свой тип добавить, он будет на хаскиле тип вводить и его дружбу с твоим CL? Ненене..
-
@SannySanoff, Там в контексте конструкторов везде висит LangType, явно или неявно через ParamType, ParamList, RetType и т.д.
-
@SannySanoff,
А тут все просто.
newtype Scope r a = Scope { runScope :: StateT Index (Writer [r]) a }
deriving Monad
data Claim r = Ret (Expression r)
| forall a. LangType a => Def Index (Expression a)
| forall a. BoolLike a => While (Expression a) (Body r)
type Block r a = Scope (Claim r) (Expression a)
type Body r = Scope (Claim r) ()
Это для описания блоков. Claim это statement, просто он занят был уже. Важно что Block это Writer со списком который хранит statement'ы, мы их оттуда берем и генерим сишные statement'ы, что важно там сидят Expression'ы в неизменном виде и можно делать всякие constant propagation, constant folding, хоть в трехадрессный(или как там) код переводи. Плюс предположительно:
type ModuleMonad a = Scope (a ~> b) ()
ModuleMonad делает то же самое что делали в блоках с переменными. А зависимости между функциями мы можем уже потом по AST построить извлекая из конструктора Expression под названием App. И не нужно никаких имен.
Если я ничего не напутал то получится как раз то что нужно, просто все top-level дефиниции надо засунуть в ModuleMonad.
Похоже пора спать, а то я уже несу хрень. -
@fmap, из того, что понял, всё согласуется с моим пониманием. Всякими "~>" не владею, равно как и GADT-ами, forall-ами и прочим..
-
@dlebedev, Дело в том, что после mkStableName одинаковые объекты могут давать разные индексы, это скорее всего зависит от того, как себя поведет рантайм. В нашем случае это не фатально, но и не очень хорошо, поскольку код может дублироваться. Ещё неизвестно будут ли они уникальными, то есть одинаковые индексы для разных объектов вовсе не допустимы.
-
@fmap, Ну, что я могу сказать? Это хэш-таблицы, у них так заведено. Если хочется надежности, стоит запилить свою маленькую монаду. Я вот не знаю, если функций предполагается не пара миллионов на приложение, то может удастся подобрать такую хэширующую функцию, которая бы не повторялась или имела вероятность повтора в допустимых пределах (скажем, 0.01% хватит?)? Когда-то решал похожую задачу с сетевыми соединениями. ВЫбрал в итоге вообще от хэш-таблиц отказался и тупо сравнивал все параметры соединений.
-
@fmap, Давай еще раз сначала. У тебя есть некая mkFun, которая на хвод принимает несколько параметров (somethingElse), а выплевывает лямбду. mkFun детерминированная же функция? При одинаковых somethingElse будет всегда выплевывать одинаковую лямбду (но генерить в новой области памяти)? Так что мешает хранить привязку лямбды к имени вместе с входящими парметрами? Или они слишком много памяти сожрут? Тогда уж без хэш-таблиц не обойтись./73 · Reply