Подтвердить что ты не робот

Как я могу писать единицы языка на языке как постфикс в Haskell, например `3 секунды`?

Ruby имеет приятную функцию, которая позволяет преобразовывать числа в другие вещи, например. 3.times для итерации или 3.to_s для преобразования его в строку.

Люди говорят, что Haskell хорош для написания естественного DSL.

Можно ли писать единицы как постфиксные, например. timeout = 3 seconds?

4b9b3361

Ответ 1

Да.

Вы можете сделать это с помощью следующего простого трюка:

{-# LANGUAGE FlexibleInstances #-}

instance Num (Integer -> Integer) where
  fromInteger n = \scale -> n * scale  -- return a function that takes
                                       -- a number and returns a number

Затем вы можете написать:

seconds, minutes, hours, days :: Integer

seconds = 1000000 -- base unit, e.g. microseconds
minutes = 60 seconds
hours   = 60 minutes
days    = 24 hours

soon :: Integer
soon = 2 hours + 4 seconds

Как это работает?

Выше мы представили экземпляр Num для Integer -> Integer, то есть для функции, которая принимает целое число и возвращает целое число.

Каждый тип, реализующий Num и имеющий свою функцию fromInteger, разрешен для представления числовым литералом, например. 3.

Это означает, что мы можем написать 3 :: Integer -> Integer - здесь 3 - это функция, которая берет целое число и возвращает целое число!

Поэтому мы можем применить к нему целое число, например seconds; мы можем написать 3 seconds, и выражение будет иметь тип Integer.


Более безопасная версия

Фактически, мы могли бы даже написать 3 (3 :: Integer) сейчас - это, вероятно, не имеет особого смысла. Мы можем ограничить это, сделав его более безопасным для типов:

newtype TimeUnit = TimeUnit Integer
  deriving (Eq, Show, Num)

instance Num (TimeUnit -> TimeUnit) where
  fromInteger n = \(TimeUnit scale) -> TimeUnit (n * scale)

seconds, minutes, hours, days :: TimeUnit

seconds = TimeUnit 1000000
minutes = 60 seconds
hours   = 60 minutes
days    = 24 hours

Теперь мы можем применять только типы типа TimeUnit к числу литералов.

Вы можете сделать это для всех видов других единиц, таких как веса или расстояния или люди.

Ответ 2

Если ваши блоки в нижнем регистре, вы можете использовать тип TimeUnit, указанный выше. Но если ваши юниты начинаются в верхнем случае, вам нужно будет определить новый тип или данные для каждого элемента и определить экземпляр Num для них. Пример этого в основном интерпретаторе
задокументировано здесь: http://hackage.haskell.org/package/BASIC-0.1.5.0/docs/Language-BASIC.html