Інтерпретована арифметика


9

Маловідомий факт полягає в тому, що якщо увімкнути достатню кількість мовних розширень (ghc), Haskell стає динамічно набраною інтерпретованою мовою! Наприклад, наступна програма реалізує додавання.

{-# Language MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, UndecidableInstances #-}

data Zero
data Succ a

class Add a b c | a b -> c
instance Add Zero a a
instance (Add a b c) => Add (Succ a) b (Succ c)

Це вже не схоже на Haskell. Для одного, а не для роботи над об'єктами, ми працюємо над типами. Кожне число - це власний тип. Замість функцій у нас є типи класів. Функціональні залежності дозволяють використовувати їх як функції між типами.

То як ми можемо викликати наш код? Ми використовуємо інший клас

class Test a | -> a
 where test :: a
instance (Add (Succ (Succ (Succ (Succ Zero)))) (Succ (Succ (Succ Zero))) a)
  => Test a

Це встановлює тип testдо типу 4 + 3. Якщо ми відкриємо це в ghci, ми виявимо, що testце дійсно типу 7:

Ok, one module loaded.
*Main> :t test
test :: Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero))))))

Завдання

Я хочу, щоб ви реалізували клас, що множить дві цифри Пеано (невід'ємні цілі числа). Число Peano буде побудовано за допомогою тих самих типів даних у наведеному вище прикладі:

data Zero
data Succ a

І ваш клас буде оцінюватися так само, як і вище. Ви можете назвати свій клас, що завгодно.

Ви можете використовувати будь-які розширення мови ghc, які хочете, без байт.

Випробування

Ці тестові випадки передбачають, що ваш клас названий M, ви можете назвати його чимось іншим, якщо хочете.

class Test1 a| ->a where test1::a
instance (M (Succ (Succ (Succ (Succ Zero)))) (Succ (Succ (Succ Zero))) a)=>Test1 a

class Test2 a| ->a where test2::a
instance (M Zero (Succ (Succ Zero)) a)=>Test2 a

class Test3 a| ->a where test3::a
instance (M (Succ (Succ (Succ (Succ Zero)))) (Succ Zero) a)=>Test3 a

class Test4 a| ->a where test4::a
instance (M (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))) (Succ (Succ (Succ Zero))) a)=>Test4 a

Результати

*Main> :t test1
test1
  :: Succ
       (Succ
          (Succ
             (Succ
                (Succ (Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))))))
*Main> :t test2
test2 :: Zero
*Main> :t test3
test3 :: Succ (Succ (Succ (Succ Zero)))
*Main> :t test4
test4
  :: Succ
       (Succ
          (Succ
             (Succ
                (Succ
                   (Succ
                      (Succ
                         (Succ
                            (Succ
                               (Succ
                                  (Succ
                                     (Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))))))))))))

Черпає натхнення від набору технічного інтерв'ю


Чи є розширення мови безкоштовними? Якщо так, то які?
Картопля44

@ Картопля44 О так. Усі розширення мови безкоштовні.
Спеціальний мисливець на Garf Garf

1
Хе ... Ця публікація здається пам'ятною, хоча це не так.
Чарівний восьминога Урна

Відповіді:


9

130 121 байт

-9 байт завдяки Ерджану Йохансену

type family a+b where s a+b=a+s b;z+b=b
type family a*b where s a*b=a*b+b;z*b=z
class(a#b)c|a b->c
instance a*b~c=>(a#b)c

Спробуйте в Інтернеті!

Це визначає сім’ї закритого типу для додавання (+)та множення (*). Тоді визначається клас типу (#), який використовує (*)сімейство типів разом з обмеженням рівності для перетворення зі світу сімейства типів у світ типу прологів.


3
Якщо ви поміняєте місцями рівняння, то їх можна замінити Zeroна z.
Ørjan Johansen

1
@ ØrjanJohansen Готово. Я зберігаю 9 байт для когось і 9 байт для мене збережено.
Картопля44

Я не знаю , як використовувати сім'ї типу, але , можливо, функція , як це так , що вам не потрібно , щоб визначити +це корисно?
Лінн

@Lynn, що закінчується, виходить довше. TIO
Картопля44

1
@WheatWizard Я щойно зрозумів, що код, який я розмістив у коментарі, тому що він вийшов довше, є по суті хвостовою рекурсивною версією вашої відповіді.
Картопля44

6

139 байт

class(a+b)c|a b->c;instance(Zero+a)a;instance(a+b)c=>(s a+b)(s c)
class(a*b)c|a b->c;instance(Zero*a)Zero;instance((a*b)c,(b+c)d)=>(s a*b)d

Спробуйте в Інтернеті!

Визначає оператор типу *. Еквівалентно програмі Prolog:

plus(0, A, A).
plus(s(A), B, s(C)) :- plus(A, B, C).
mult(0, _, 0).
mult(s(A), B, D) :- mult(A, B, C), plus(B, C, D).

Potato44 та Hat Wizard врятували по 9 байт. Дякую!


Вам не потрібно зараховувати декларації даних до загального байту. Я зроблю це ясніше в питанні, коли я отримаю шанс
Ad Hoc Garf Hunter

Також я думаю, що ви можете використовувати загальний fзамість Succ.
Ad Hoc Hunter Hunter

1
Ви можете зберегти 9 байт, скидаючи колони.
Картопля44

Я думаю, що Hat Wizard також врятував 9, а не 6. Було три випадки Succ.
Картопля44

1

Сім'я-версія, 115 байт

type family(a%b)c where(a%b)(s c)=s((a%b)c);(s a%b)z=(a%b)b;(z%b)z=z
class(a#b)c|a b->c
instance(a%b)Zero~c=>(a#b)c

Спробуйте в Інтернеті!

Для цього використовується сім'я закритого типу, як картопля44 . За винятком іншої відповіді, я використовую лише родину 1 типу.

type family(a%b)c where
  -- If the accumulator is Succ c:
  -- the answer is Succ of the answer if the accumulator were c
  (a%b)(s c)=s((a%b)c)
  -- If the left hand argument is Succ a, and the right hand is b
  -- the result is the result if the left were a and the accumulator were b
  (s a%b)z=(a%b)b
  -- If the left hand argument is zero
  -- the result is zero
  (z%b)z=z

Це визначає оператора на три типи. Він по суті реалізує (a*b)+c. Кожен раз, коли ми хочемо додати аргумент правої руки до загального, ми замість цього помістимо його в акумулятор.

Це заважає нам (+)взагалі не потребувати визначення . Технічно ви можете використовувати цю сім'ю для здійснення доповнення, виконуючи це

class Add a b c | a b -> c
instance (Succ Zero % a) b ~ c => Add a b c

Клас-версія, 137 байт

class(a#b)c d|a b c->d
instance(a#b)c d=>(a#b)(f c)(f d)
instance(a#b)b d=>(f a#b)Zero d
instance(Zero#a)Zero Zero
type(a*b)c=(a#b)Zero c

Спробуйте в Інтернеті!

Ця версія класу втрачає певну основу для сімейної версії, однак вона все ж коротша, ніж найкоротша версія тут. Він використовує той самий підхід, що і в моїй сімейній версії.


Приємно, я бачу, що ваша родина типів математично реалізує * b + c. Це згадка про "поділ" означала "доповнення"?
Картопля44

btw, у вас зараз трапляється порушення вашої специфікації. "реалізуйте клас, який помножує дві цифри Пеано" Те, що у вас зараз не є класом, все ж, буває, є добрим Constraint. Тож вам слід або оновити специфікацію, або повернутись до форми, яка використовує клас, а не синонім типу. Якби я використовував синонім типу, я міг би отримати свою відповідь до 96 байт, тож це врятує мені ще один байт, ніж ти
Potato44

@ Potato44 У мене було враження, що клас - це просто щось таке, що призводить до контракту. Можливо, це було пов’язано з недостатньою чіткістю питання. Я повернусь до своєї відповіді 115.
Ad Hoc Hunter Hunter
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.