/pr/ – programming


023a8023a8623a27042228f6b721cd7892454 – ``Божественный язык''

@2f650ed6740e458086b7c948c599e8b1 Anonymous 2021-04-10 21:10:24
@e65d8@e65d8e7067094b8589055a0446738e81
@01185@011859c4bc46423dae1be30f6868fde6
@b13a9a0dd0a34f7a8a449736775691f1 Anonymous 2021-05-09 12:33:01
@b7e0a@b7e0afcd0a4948caac163da52745069e Это не так делается:
struct Num<t> {
    sum: (t, t) => t;
    multiply: (t, t) => t;
}
struct Neg<t> {
    negate: t => t;
    subtract: (t, t) => t;
}

[infixl(8)]
fn + <t>(#num-t: Num<t>, x: t, y: t): t {
    num-t.sum(x, y)
}

[infixl(8)]
fn - <t>(#neg-t: Neg<t>, x: t, y: t): t {
    neg-t.subtract(x, y)
}

let #num-int: Num<int> = Num {
    sum = primitive-int-sum;
    multiply = primitive-int-multiply;
};

let #neg-int: Neg<int> = Num {
    negate = x => primitive-int-subtract 0 x;
    subtract = primitive-int-subtract;
};

let x: int = 5;

let test = x + x - x;
// или явно
let test = (-)(neg-int, (+)(num-int, x, x), x); 

Но вообще, если есть несколько подходящих implicit значений, то берётся последнее.
@5ccd1afe8fa74a5f86b4c04c986d17dd Anonymous 2021-05-09 12:36:44
Только ещё забыл, что Neg должен подразумевать наличие Num:
struct Neg<t> {
    num: Num<t>;
    negate: t => t;
    subtract: (t, t) => t;
}
let #neg-int: Neg<int> = Num {
    num = num-int;
    negate = x => primitive-int-subtract 0 x;
    subtract = primitive-int-subtract;
};
@794c8a6678024137af1ada79e3da2427 Anonymous 2021-05-09 12:42:00
>let #neg-int: Neg<int> = Num {
let #neg-int: Neg<int> = Neg {
fix
@50e5b799f7864142a939fcdc983b368b Anonymous 2021-05-09 12:47:48
(И не точка с запятой между филдами структуры, а запятая (из-за OCaml по привычке написал ;))
@657302f594704df0bceff948df9460ac Anonymous 2021-05-09 13:41:40
@b13a9@b13a9a0dd0a34f7a8a449736775691f1
> если есть несколько подходящих implicit значений, то берётся последнее.
Потом придется отлаживать, почему оно вычитает, вместо того чтобы складывать, хотя ты явно написал "+". Лучше падать с ошибкой неоднозначности.
@a5e5c973156042aa87853dd8f14e82f4 Anonymous 2021-05-09 13:47:52
@65730@657302f594704df0bceff948df9460ac Ну, блядь, в любом языке можно написать fn plus(x: int, y: int): int { minus(x, y) }. И что теперь?
@e1dff1b0b3344224bfad3197e59aeaf7 Anonymous 2021-05-09 13:48:48
@a5e5c@a5e5c973156042aa87853dd8f14e82f4 Я утрирую.
@70f40b0fdb254af0bfb256b2223da383 Anonymous 2021-05-09 13:52:43
И что делать если я хочу свою имплементацию чего-то определить и использовать её вместо дефолтной? Нет, нельзя падать.
@44a375ef83684974a1bc015454afc809 Anonymous 2021-05-09 13:53:44
А, блин, на этом же оно и работает.
@3a71a5282da649f3a6f1810792bf2923 Anonymous 2021-05-09 13:54:28
Это как переопределение переменной:
let x = 5;
let x = 42;

Тут тоже нельзя падать. Такая же логика.
@fee054860e8e4b329522b690e2ae83b0 Anonymous 2021-05-09 18:05:30
Эксепшенов не будет, unwrap'нуть Option или Result будет нельзя, non-exhaustive паттерн матчинг запрещён.
@6d4abac4e9264fd093094d74aafaf0ed Anonymous 2021-05-10 10:02:46
@fee05@fee054860e8e4b329522b690e2ae83b0 Язык тотальный будет?
@6d97a73fc6354599b985dbfeda8d1eac Anonymous 2021-05-10 12:54:26
@6d4ab@6d4abac4e9264fd093094d74aafaf0ed Нет.
@4e876ba402424991b657dc7ec46772c1 Anonymous 2021-05-10 15:28:17
@fee05@fee054860e8e4b329522b690e2ae83b0 У тебя весь код будет густо обмазан монадами Result и Exception, вложенными друг в друга.
@dd8dec50362a481db05f69e06068dc11 Anonymous 2021-05-10 15:40:04
@4e876@4e876ba402424991b657dc7ec46772c1 Ну и похуй. К тому же ok(5).unwrap-or(0) никто не отменял.
@9104d1ebc1444c7cb1d7453b1d463dd2 Anonymous 2021-05-10 15:44:07
А зачем Exception?
@5bf94c3130cf4aa894831b822f72c1f4 Anonymous 2021-05-10 15:44:48
@9104d@9104d1ebc1444c7cb1d7453b1d463dd2 Чтобы по-разному ошибки обрабатывать.
@0b4c276ca99b40bda209a9a4e6a4f84e Anonymous 2021-05-10 15:46:40
Вообще, языки надо как-то обкатывать сначала, прежде чем выкатывать в прод. Наебашить на них с тонну кода и посмотреть, что норм, а что лучше поменять.
@1efb8b5d2da8436ebe4f05817db0b2a4 Anonymous 2021-05-10 16:13:29
@5bf94@5bf94c3130cf4aa894831b822f72c1f4 например user-interrupt? Да, хуёво, оно может в любом месте произойти и должна быть возможность его обработать. Получается все типы должны быть завёрнуты в Result с Exception.
@42f2cf5cf38b4f1db6bc18b5829ccadf Anonymous 2021-05-10 16:15:36
Может второй очень урезанный язык создать который будет обрабатывать эсепшены первого?
@5c008742a40b440098dcf2b732d44be5 Anonymous 2021-05-10 16:29:39
И я думаю надо сделать чтобы не было FFI, чтобы код языка можно было вызывать из других языков, но не наоборот.
@703ec05db7bb4a39b8fda5758b8c6d4d Anonymous 2021-05-10 16:41:57
@1efb8@1efb8b5d2da8436ebe4f05817db0b2a4
>Получается все типы должны быть завёрнуты в Result с Exception.
Или нет, достаточно наверное чтобы main имел нужный тип (как в хаскелл оно в IO обёрнуто). И тогда не нужен второй язык.
@4a49e5932a0e41a289f8ab6d1b262791 Anonymous 2021-05-10 18:10:55
А, придумал. Просто будет примитивная функция например user-interrupt-handler: (Signal => ()) => Result<(), Error>. И всё, ничего больше нах не надо.
@0c3cd949ea3b4ff381cb1911fcf4fc4c Anonymous 2021-05-10 21:51:08
@1efb8@1efb8b5d2da8436ebe4f05817db0b2a4 Например, значения errno у open(). И это самый простой случай, в общем случае ты даже Either не обойдёшься, потому что могут быть подтипы разных эксепшенов.
@800248a4cc284725b02a158261151da2 Anonymous 2021-05-10 21:53:54
Хрестоматийный случай: деление на 0.
@7d715140b7894ed5b813f55d2af92f0f Anonymous 2021-05-10 22:20:38
@0c3cd@0c3cd949ea3b4ff381cb1911fcf4fc4c Ну так open будет возвращать Result. В чём проблема?
@80024@800248a4cc284725b02a158261151da2 тоже Result.
@d189714a40ac49e79de402512bcb3bb3 Anonymous 2021-05-10 22:35:21
@0c3cd@0c3cd949ea3b4ff381cb1911fcf4fc4c
> могут быть подтипы разных эксепшенов.
Не понимаю о чём ты. Какие ещё подтипы? Сабтайпинга не будет.
@f9a1fb8e9e0348ffb25da598d3073d4a Anonymous 2021-05-10 22:53:14
@d1897@d189714a40ac49e79de402512bcb3bb3
try
  ,,,
catch FileNotFoundError
  shutdown()
catch FileReadError
  warning("не смог прочитать"); retry
catch GenericIOError
  fatalerror();
end

try
  ...
catch GenericIOError
  println("да и хрен с ним")
end
@8aab4da98a484da38fce8aff2fde7580 Anonymous 2021-05-10 23:06:36
@f9a1f@f9a1fb8e9e0348ffb25da598d3073d4a
enum io-error {
    file-not-found,
    file-read-error,
    generic-io-error,
    ...
}

let x: Result<string, io-error> = ...;
match x {
    file-not-found -> shutdown(),
    ...
}
@d811ed8e018646e2ae0aca1ee25e0840 Anonymous 2021-05-10 23:08:01
error(file-not-found) ->
fix
@1cff1c720d1c4dc5a7c07259beaa64d9 Anonymous 2021-05-10 23:08:23
s/Generic//g
@aa100c3aa799493b979f1ddee9380ea6 Anonymous 2021-05-10 23:09:21
Если выбрасывается FileReadError, то catch IOError ее тоже ловит.
@71f0f6476b7b4e7d998bfc80d1356298 Anonymous 2021-05-10 23:10:11
Впрочем, это уже другая проблема. Изначальный поинт был в том, что монадой Result обмазывается всё, даже арифметика.
@66cd9884a5624bf898a9ed2c0637769d Anonymous 2021-05-10 23:13:03
Причём разнотипизированной монадой, тип под error(..) может быть разный.
@ae27e2f7e63a451289d8ccb77af5ce08 Anonymous 2021-05-10 23:15:15
@66cd9@66cd9884a5624bf898a9ed2c0637769d Result - это функтор, эррор можно смапить в другой тип.
@d3e6a70d686f47e88618a6823279443d Anonymous 2021-05-10 23:16:48
@aa100@aa100c3aa799493b979f1ddee9380ea6
match x {
    io-error(file-read-error) -> ...
    io-error(_) -> ...
}
@567ddf9f132f45d7801c868d7114f5ba Anonymous 2021-05-10 23:17:28
а, тоже
@aebb95645bd84b8fbe9735fcef6ed5c5 Anonymous 2021-05-10 23:19:07
@ae27e@ae27e2f7e63a451289d8ccb77af5ce08
Это как?
public f() throws E1 { ... }
public g() throws E1, E2 { f(); ... }
@f162e50ca4754103b9c16898ff59e6ae Anonymous 2021-05-10 23:19:09
@ae27e@ae27e2f7e63a451289d8ccb77af5ce08
Это как?
public f() throws E1 { ... }
public g() throws E1, E2 { f(); ... }
@0797aa89fec544e180f90324178d91cd Anonymous 2021-05-10 23:26:04
@aebb9@aebb95645bd84b8fbe9735fcef6ed5c5
Специально для g создать sum type с E1 и E2.
@bb9810b3616e4b58adbd0e34e4fd8d3a Anonymous 2021-05-10 23:27:16
и потом f().map_error(e => ...)?; ... }
? используется для раннего выхода.
@51292a989d594bd883a8ec647f9ea05a Anonymous 2021-05-10 23:28:32
То есть, если где-то глубоко результат меняется с int на Result<int, E1>, то надо все функции по всему стеку вызовов переписывать?
@ab54fe928c5c479d96300a307549c4b5 Anonymous 2021-05-10 23:32:55
С "E1, E2", чувствую, ещё может expression problem возникнуть. Хотя примера пока не могу подобрать.
@505c064c99434ad4bcbbf5320a02e514 Anonymous 2021-05-10 23:34:18
@51292@51292a989d594bd883a8ec647f9ea05a ну вообще да. Смотря какая ситуация. Если например x заменился на x/y и ты знаешь точно, что y никогда не будет нулём, то можно написать (x/y).or_else(0) и ничего не надо переписывать.
@99044b043e8f4cf5a2d7182e20f1fdee Anonymous 2021-05-10 23:34:46
@51292@51292a989d594bd883a8ec647f9ea05a Или с Result<int, E1> на Result<int, ES>, где ES = E1 | E2.
@26c83f83a00f450f9269e9738acbdf5e Anonymous 2021-05-10 23:45:03
Такая же ситуация и с Option. Если где-то глубоко в выражении значение оборачивается в опшн, то всё надо менять. И это наоборот заебись.
@81c29615cd6b45c5abea85dac746e15e Anonymous 2021-05-10 23:52:08
В общем, эксепшоны - те же возвращаемые значения, но проблема в том, что если их рассматривать именно как возвращаемые значения, возникает очень много бойлерплейта. Поэтому их обычно делают отдельной сущностью (что тоже нехорошо).
@28b3f4ed641a4d78995cf65cf79b04a1 Anonymous 2021-05-10 23:53:17
Другой пример: есть стек вызовов из 30 функций, f1 ... f30. Где в f10 выбрасывается E1, но обработать его может только f20. В f11--f19 логика не меняется, но её придётся менять.
@05632171a1e340f5bdb9d5d1c3b99c8d Anonymous 2021-05-10 23:53:56
Или если к типу-сумме эксепшенов добавляется ещё один эксепшон.
@00bf91295cb549f1be3bd0325a5106f1 Anonymous 2021-05-10 23:56:50
@28b3f@28b3f4ed641a4d78995cf65cf79b04a1 Это нормально, это наоборот хорошо, видно где какие ошибки могут быть и сколько.
Точно так же, если f1 ... f30 все возвращали int, а потом f10 поменялась и стала возвращать Option<int> и только f30 должна это обработать, то да, надо все f11-f19 надо менять.
@8e1f8d08240e4443967794fc6f8db046 Anonymous 2021-05-10 23:58:17
*f20
@053fa33a21a64473a216cdae29e7f93c Anonymous 2021-05-11 00:02:11
Наоборот, какогда не обязательно что-то менять, то это плохо. Тогда и возникают внезапные баги с необработанными эсепшенами, хотя ты просто обновил депенденси и всё успешно сбилдилось.
@83333d06c8054b8d8ed64ebd238622b5 Anonymous 2021-05-11 00:06:00
Необработанные эксепшоны - это жопа. Но и обмазывать весь код match x { ...; error(new-error) -> ... } тоже не вариант.
@f5210d2a0de34011b0d40fcb27bfc849 Anonymous 2021-05-11 00:31:32
Наверное третий вариант - это алгебраические эффекты и хэндлеры. Тогда в функции f11-f19 надо будет только добавить в возвращаемый тип новый эффект, а внутри ничего в них меняться не будет, в f20 будет обработчик.
@e653a7c10f1b44948e4d0b3ac6dbebd6 Anonymous 2021-05-11 00:34:58
Но это дополнительная сущность, которую не хочется добавлять. Вроде как семантика алгебраических эффектов описывается free монадами, но смутно пока себе всё это представляю.
@cabbf6f0a21247d5bb5a65cceccedfe1 Anonymous 2021-05-11 14:17:53
let y: int = f(1, 2);
Допустим 1 поменялось на Some(1), тогда надо переписать как-то код. Хотелось бы минимального количества изменений.

let y: Option<int> = f(Some(1), 2); - вот так было бы удобно.

Можно ввести правило, что если F - функтор и f: a => b, то если x: F<a>, то f(x) автоматически преобразуется в x.map(f).

Если у f больше одного параметра, то мап делается последовательно по всем аргументам:
f: (a, b) => c, x: F1<a>, y: F2<b>, то f(x, y) станет x.map(v1 => y.map(v2 => f(v1, v2))).

Тогда кстати можно будет писать square([1, 2, 3]) вместо [1, 2, 3].map(square).
@4b715a8e8e644b8c8591604515f754b0 Anonymous 2021-05-11 14:28:47
>x.map(v1 => y.map(v2 => f(v1, v2)))
Блядь, нет, так не выйдет.
@bef83d095a2b42cb8e8d887ac5292406 Anonymous 2021-05-11 14:30:48
Короче, не функтор, а монада, и не map, а bind:
x >>= (v1 => y >>= (v2 => result(f(v1, v2))))
@a37d85d8981a41b2b7fa49952c61e7ad Anonymous 2021-05-11 14:37:29
Нет, какая нахуй монада, applicative достаточно:
pure(f).apply(x, y)
@11e83dbf244d43308fa412e607fcb4d7 Anonymous 2021-05-11 14:59:02
@a37d8@a37d85d8981a41b2b7fa49952c61e7ad
Но тогда x и y должны быть одного и того же applicative. Вот так тогда:
x: A1<a>
y: A2<b>
pure(y => pure(x => f(x, y)).apply(x)).apply(y): A1<A2<c>>
@4fb178e9b12a40d89c72235e67308834 Anonymous 2021-05-11 16:10:43
@11e83@11e83dbf244d43308fa412e607fcb4d7 @4b715@4b715a8e8e644b8c8591604515f754b0
Хуйню написал. Это то же самое. Всё было изначально правильно, функтора достаточно.
@d0fa341a55ef4c1bb319addc3e58085c Anonymous 2021-05-11 20:02:48
you only get 2 precedence levels and they are tied to associativity
Yes, you the reader have just been restricted by reading this
No, I can't undo this restriction and no i don't want to

people who love complex precedence hierarchies be damned cuz i like being capable of reading in a timely fashion and without immense effort

(c) https://types.pl/@Uber/106139207723770180
@84515b97d841461b92378dace0f50243 Anonymous 2021-05-12 03:55:31
@4fb17@4fb178e9b12a40d89c72235e67308834 Только я хотел сказать, что во всех языках код неявно обёрнут в монаду разной степени пермиссивности, а ты уже всё сделал.
@c73064e2442147f2b5e7886b4e069a73 Anonymous 2021-05-13 09:24:29
Почитал я ваш тред.

И вспомнил, почему я дропнул свой язык.
@6e81a61e8e484223a2f9958ef97d45d7 Anonymous 2021-05-13 09:24:59
Может, у тебя получится.
@61a74c6e862b46d6a05fa5ac79f26dfe Anonymous 2021-05-19 07:06:00
В Idris есть Named Implementations:
[myord] Ord Nat where
   compare Z (S n)     = GT
   compare (S n) Z     = LT
   compare Z Z         = EQ
   compare (S x) (S y) = compare @{myord} x y

*named_impl> show (sort testList)
"[sO, sssO, ssssO]" : String
*named_impl> show (sort @{myord} testList)
"[ssssO, sssO, sO]" : String
@0cd77d5bcdc540289243665cb1f81e86 Anonymous 2021-05-19 07:59:11
@61a74@61a74c6e862b46d6a05fa5ac79f26dfe Этого мало.
@d5ec70833d6949d098fb48f68efefc5a Anonymous 2021-05-20 15:11:25
Что будет с тредами?
@cc360261b42a4be992c086c35a35e52a Anonymous 2021-05-20 15:22:22
@d5ec7@d5ec70833d6949d098fb48f68efefc5a Не думал об этом. А что с ними может быть?
@0608f343fff64c4eb6315e858f2d2418 Anonymous 2021-05-20 15:22:50
Это не должно влиять на дизайн языка anyway.
@caf9ac9b9fe84cdd918d4833b210d812 Anonymous 2021-05-20 16:16:07
Логично.
@a122518e58cf4a1b9c8869b63157cb53 Anonymous 2021-05-20 16:16:22
Хотя вот в Eiffel оно таки повлияло.
@96766c35573e4d9899e6eeb5c607a7c4 fulmar 2021-09-03 12:23:35
Вот этот тред.
@e5d035629a0545a182471ab36acb80a1 kirahira 2021-09-03 12:27:08
@96766@96766c35573e4d9899e6eeb5c607a7c4 Блин. Интересно.
@66819b8f4a8c4ecd86abd0144b1e004a fulmar 2021-10-21 21:45:25
Всё-таки скобки мешают в f(x, y). Например,
fn foo<a>(f: int => a): a {
    f(10) // or f(10, _)? or f(10, _, _)?
}

Непонятно какая арити у f. Может быть a тоже тип функций.
Скобок для передачи параметров не должно быть, они противоестественны. Хотя без них читаемость снизится, но что поделать.
@2df76a1e0f1043289149de557287b0e0 fulmar 2021-10-21 22:07:48
Блядь, но что с этим делать тогда @f941b@f941bf80ae1842d7bc9dab1de07790c6.
@0ccc48f6cf8c4601922f327115589997 fulmar 2021-10-21 23:20:52
Надо переосмыслить точку. Вообще что такое точка? Это способ перейти из префиксной в постфиксную нотацию. Нам удобно миксовать различные нотации. Вот зачем это всё надо.
x.f(n, m).foo - f функция трёх аргументов и с помощью точки мы говорим ей стать как бы оператором.
Если перед точкой ставить пробел, то читаемость улучшается.
x .f n m .foo - то же самое, что ((((x .f) n) m) .foo).
С помощью точки мы говорим, что функция справа будет, а не слева.
@6e1a569e69fc4f5899a13712cca4e2d1 fulmar 2021-10-21 23:25:30
>x .f n m .foo - то же самое, что ((((x .f) n) m) .foo).
А это, в свою очередь, то же самое, что (foo (((f x) n) m)).
@b3850e8d5dab4298b37833f309631fd7 fulmar 2021-10-21 23:43:16
Ну да, короче, точка это |> и с читаемостью всё норм.
@8d86d40200a7418bac3d2e7132be1aaa fulmar 2021-10-25 23:16:26
Но только надо сделать чтобы у точки был приоритет выше чем у аппликации, иначе 5 .subt 2 будет 5 .(subt 2), т.е. -3, а не 3. Не надо повторять ошибку хаскеллей и прочих идрисов, где у аппликации приоритет всегда выше любого инфиксного оператора. Для точки нужно сделать исключение.