Малоизвестный факт: если вы включите достаточное количество расширений языка (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))))))
задача
Я хочу, чтобы вы реализовали класс, который умножает две цифры Пеано (неотрицательные целые числа). Числа Пеано будут построены с использованием тех же типов данных в примере выше:
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)))))))))))))))))
Черпает вдохновение в наборе технических интервью
источник
Ответы:
130121 байт-9 байт благодаря Орджану Йохансену
Попробуйте онлайн!
Это определяет семейства закрытых типов для сложения
(+)
и умножения(*)
. Затем определяется класс типов,(#)
который использует семейство(*)
типов вместе с ограничением равенства для преобразования из мира семейств типов в мир пролога класса типов.источник
Zero
наz
.+
это полезно?139 байт
Попробуйте онлайн!
Определяет оператор типа
*
. Эквивалент программы Пролог:Potato44 и Hat Wizard сохранили по 9 байт каждый. Спасибо!
источник
f
вместоSucc
.Семейная версия, 115 байт
Попробуйте онлайн!
Это использует семейство закрытых типов, как potato44 . Кроме в отличие от другого ответа я использую только 1 тип семейства.
Это определяет оператор трех типов. Это по существу реализует
(a*b)+c
. Всякий раз, когда мы хотим добавить наш аргумент правой руки к итогу, мы вместо этого помещаем его в накопитель.Это мешает нам
(+)
вообще определять . Технически вы можете использовать это семейство для реализации сложения, выполнивКласс-версия, 137 байт
Попробуйте онлайн!
Эта версия класса немного уступает семейной версии, однако она все еще короче, чем самая короткая версия класса. Он использует тот же подход, что и моя семейная версия.
источник
Constraint
хотя оно действительно является полезным. Поэтому вы должны либо обновить спецификацию, либо вернуться обратно в форму, которая использует класс вместо синонима типа. Если бы я использовал синоним типа, я мог бы получить свой ответ до 96 байтов, так что это