Вячеслав МасловНо если записать :
: 8* ( x -- x1 ) 8 * ;
то видно, что ваше определение не тождественно моему, не по реализации, а по типам параметров, которые они обслуживают.
Фотография
нажмите — покажем
нажмите — покажем
Можно и так
( u -- u1 )
Я не особо думаю о типах. Для меня на стеке параметров просто числа.
Вячеслав МасловНо если записать :
: 8* ( x -- x1 ) 8 * ;
то видно, что ваше определение не тождественно моему, не по реализации, а по типам параметров, которые они обслуживают.
Это определение является системно-зависимым, т.к. «*» применяется к значению типа x.
В В : 8* 3 lshift ; : *8 8 * ; : tst1 t6| 2 8* |1t ; seet tst1 : tst2 t6| 2 *8 |1t ; seet tst2 tst1 (10**6 runs) pr: 0 0 T1 = 1212816 tics 0 ms 275 us 640 ns pr: 2 2 Tc = 1221440 tics 0 ms 277 us 600 ns tst2 (10**6 runs) pr: 18 18 T1 = 4118224 tics 0 ms 935 us 960 ns pr: 2 2 Tc = 4084036 tics 0 ms 928 us 190 ns PS. По прежнему сдвиг в разы быстрее.
Вадим ЯковлевСтатья 1992 года о Forth.
Не будем судить автора строго.
Там такой пример:
: тройной-сдвиг-влево сдвиг-влево сдвиг-влево сдвиг-влево ;
что по сути есть:
: 8* ( u -- u1 ) 3 lshift ;
https://compiler.su/_A/014.html
PS Полагаю, Forth как и Lisp, критикуют весьма
посредственные программисты.
Это определение тоже системно-зависимое, но в меньшей степени.
ruvОдин из подходов — использовать явное или неявное приведение типа (type conversion). Данные, которые приходят извне, имеют тип dyn и не вызывают статической ошибки.
Тут идея в следующем.
При чтение значения из памяти, его собственный тип не изменяется. Для слова «@» это можно формально выразить так:
( a-addr[+ x1] -- x1 )
Но, если значение в память попало извне, то для тайпчекера оно имеет лишь самый общий тип x (unspecified cell). И, к примеру, где-то между «read-file» и «@ dup 0<» возникнет статическая несовместимость типов, т.к. в контекст, где ожидается «a-addr[+ n]», передается значение типа «a-addr[+ x]».
Оптимистичный или градуальный (gradual typing) тайпчекер может для значений типа x полученных извне (как пример, записанных в память), назначить тип dyn-x, который совместим с любыми подтипами x (или, как вариант, только с подтипами/надтипами u и n). И тогда в примере выше, статически будет передаваться значение типа «a-addr[+ dyn-x]», которое тайпчекер считает совместимым с «a-addr[+ n]».
Конечно, этот подход допускает возникновение динамической несовместимости типов при выполнении.
Еще, статически можно сужать тип значения (type narrowing) и ловить ситуацию, когда одно и тоже значение интерпретируется по разному в разных местах. В примере выше, тайпчекер может сузить тип значения dyn-x до dyn-n, которое совместимо только с подтипами и надтипами n. И если в другом месте это же значение используется в контексте, где ожидается u или addr, тайпчекер может указать на ошибку.
#typesystem #typechecking #gradual
R Ссылка
нажмите — покажемВ целом, такой код завязан на реализацию Форт-системы и предполагает совместимость бинарных форматов. В данном пример предполагается, что размер ячейки, порядок байт (endianness), и кодирование знаковых целых совпадают в данных извне и в Форт-системе. #forth #abi #compatibility
Еще один простой по реализации вариант стековых переменных:
\ виртуальное дно стека параметров
user-value vds
\ преобразование имен
m: nalf
a[ 1> ] b[ 2> ] c[ 3> ] d[ 4> ]
e[ 5> ] f[ 6> ] g[ 7> ] h[ 8> ] ;
\ определение определения с установкой виртуального дна стека параметров
: s: s0 @ depth 4 * - TO vds : ;
\ содержимое ячеек стека начиная от виртуального дна стека параметров
m: 1> vds @ ;
m: 2> vds 4 - @ ;
m: 3> vds 8 - @ ;
m: 4> vds 12 - @ ;
m: 5> vds 16 - @ ;
m: 6> vds 20 - @ ;
m: 7> vds 24 - @ ;
m: 8> vds 28 - @ ;
\ Пример:
\ сумма 4-х квадратов чисел
: sum_4sqr1 ( n1 n2 n3 n4 -- n.sum )
dup * swap dup * + swap dup * + swap dup * + ;
s: sum_4sqr2 ( n1 n2 n3 n4 -- n.sum )
nalf dup * a dup * b dup * c dup * + + + nip nip nip ;
: t1 t6| 3 4 5 6 sum_4sqr1 |1t ;
: t2 t6| 3 4 5 6 sum_4sqr2 |1t ;
seet t1
seet t2
see sum_4sqr1
see sum_4sqr2
: sum_4sqr3 { a b c d }
a dup * b dup * c dup * d dup * + + + ;
: t3 t6| 3 4 5 6 sum_4sqr3 |1t ;
seet t3
see sum_4sqr3
t1 (10**6 runs)
pr: 4 4
T1 = 4850208 tics 1 ms 102 us 320 ns
pr: 4 4
Tc = 4876696 tics 1 ms 108 us 340 ns
t2 (10**0 runs)
pr: 4 4
T1 = 4988060 tics 1 ms 133 us 650 ns
pr: 0 0
Tc = 5093440 tics 1 ms 157 us 600 ns
sum_4sqr1
7D2BAF F7E8 IMUL EAX
7D2BB1 8B5500 MOV EDX , 0 [EBP]
7D2BB4 894500 MOV 0 [EBP] , EAX
7D2BB7 8BC2 MOV EAX , EDX
7D2BB9 F7EA IMUL EDX
7D2BBB 034500 ADD EAX , 0 [EBP]
7D2BBE 8B5504 MOV EDX , 4 [EBP]
7D2BC1 894504 MOV 4 [EBP] , EAX
7D2BC4 8BC2 MOV EAX , EDX
7D2BC6 F7EA IMUL EDX
7D2BC8 034504 ADD EAX , 4 [EBP]
7D2BCB 8B5508 MOV EDX , 8 [EBP]
7D2BCE 894508 MOV 8 [EBP] , EAX
7D2BD1 8BC2 MOV EAX , EDX
7D2BD3 F7EA IMUL EDX
7D2BD5 034508 ADD EAX , 8 [EBP]
7D2BD8 8D6D0C LEA EBP , C [EBP]
7D2BDB C3
В Было не предусмотрено доступа к адресам ячеек стека параметров как адресам стековых локальных переменных. Тем самым не было предусмотрено присвоение значений таким локальным переменным(как в ФП). Но форт это не совсем ФП, поэтому присвоение сделать не сложно: m: @1 vds ; m: 1> @1 @ ; и т. д. Подменять имена адресов и содержимого ячеек стека(локалсов) можно произвольным образом. Например адреса можно обозначать большими буквами, b A ! означает записать содержимое второй ячейки от виртуального дна стека параметров в первую ячейку.R В Это точно. 1> 2> + даст возможность использовать сумму (1> 2> +) как 3> без явного присвоения, так как результат сложения содержимого 1 и 2 ячеек будет в ячейке 3 (все промежуточные операции над содержимым стека происходят без потери информации в стеке). В циклах нужно держать количество ячеек стека в определенном диапазоне, поэтому без присвоения (с переустановкой указателя в сторону уменьшения глубины) не обойтись.R В R В Ну раз в ФП отказались от понятия состояния(которое реально все-таки существует), то конечно нельзя. В форте и состояния есть и рекурсия есть. В конечном итоге вычислительные ресурсы всегда ограничены и при реализации тех или иных алгоритмов требуется это учитывать. Теоретически рекурсия и итерация могут быть преобразованы одна в другую. Есть компиляторы, которые позволяют преобразовать рекурсию в итерацию, чтобы, например ускорить код и/или уменьшить расход памяти.R Ссылка
нажмите — покажемНасчет реальности состояния — это философский и довольно спорный вопрос (см. ранее https://t.me/ruforth/14623/20727). Допустим, у нас есть переход (или отношение): Obj₁ → Obj₂, где Obj₁ и Obj₂ различимы как значения. Что именно позволяет нам сказать, что это изменение состояния одного и того же объекта, а не переход от одного объекта к другому? Насколько сильными должны быть изменения, чтобы мы считали Obj₁ и Obj₂ разными объектами? Содержимое объектов не даёт однозначного ответа. Мы должны ввести функцию идентификации, и если Id(Obj₁) = Id(Obj₂) , то мы интерпретируем этот переход как изменение состояния. И при этом неважно, насколько радикально это изменение. А в ФП мы не столько отказываемся от состояния, сколько отказываемся от интерпретации последовательности состояний (значений) как истории одного и того же объекта. #functional