Можно ли внедрить в Haskell постфиксный калькулятор?

main = do
    print $ begin push 1 push 2 add end
    print $ begin push 1 push 2 push 3 add mul end

На первый взгляд такой код на Haskell не может работать. Функция begin должна иметь произвольное количество аргументов, а Haskell является языком со статической типизацией. Но на самом деле, для написания вариативных (polyvariadic) функций достаточно полиморфизма.

Формально все функции в Haskell являются функциями с одним аргументом (в силу каррирования). В данной статье арностью функции будем называть количество аргументов, которые нужно передать функции, чтобы возвращаемое значение было не функцией. Или, другими словами, количество стрелок вне скобок в описании типа функции. В этом смысле простейшей вариативной функцией является id.

main =
    print $ id id id 1

Если мы посмотрим типы, которые выводит компилятор, то увидим, что у нас три разных функции id с разным количеством аргументов.

main =
    print $ (id `asTypeOf` _t1) (id `asTypeOf` _t2) (id `asTypeOf` _t3) 1

-- _t1 :: ((Integer -> Integer) -> Integer -> Integer) -> (Integer -> Integer) -> Integer -> Integer
-- _t2 :: (Integer -> Integer) -> Integer -> Integer
-- _t3 :: Integer -> Integer

После этого простого примера становится понятно, что для решения исходной задачи достаточно, чтобы begin была функцией, которая принимает функцию и возвращает функцию.

Первая, наивная реализация идеи:

begin :: ([a] -> t) -> t
begin f = f []

push :: [a] -> a -> ([a] -> t) -> t
push st x f = f (x:st)

add :: [Int] -> ([Int] -> t) -> t
add (x:y:st) f = f (x+y:st)

mul :: [Int] -> ([Int] -> t) -> t
mul (x:y:st) f = f (x*y:st)

end :: [a] -> a
end (x:_) = x

result =
    begin
        push 1
        push 3
        push 7
        add
        push 8
        mul
        add
    end

main :: IO ()
main =
    print $ result -- 81 = 1 + (3 + 7)*8

Данное решение очень простое, но у него есть существенный недостаток. При большом количестве "операций" внутри begin-end выведение типа занимает много времени. Во всех функциях выше (кроме заключительного end) возвращаемый тип t в описании повторяется дважды. Поэтому при увеличении количества промежуточных функций размер описаний растёт по экспоненте (начиная с конца), и фактический тип функции begin получается очень сложный.

В приведённом выше примере тип begin выглядит так
• Found hole:
    _ :: ([Int]
          -> Int
          -> ([Int]
              -> Int
              -> ([Int]
                  -> Int
                  -> ([Int]
                      -> ([Int]
                          -> Int
                          -> ([Int]
                              -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                          -> ([Int] -> ([Int] -> Int) -> Int)
                          -> ([Int] -> Int)
                          -> Int)
                      -> Int
                      -> ([Int]
                          -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                      -> ([Int] -> ([Int] -> Int) -> Int)
                      -> ([Int] -> Int)
                      -> Int)
                  -> ([Int]
                      -> Int
                      -> ([Int]
                          -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                      -> ([Int] -> ([Int] -> Int) -> Int)
                      -> ([Int] -> Int)
                      -> Int)
                  -> Int
                  -> ([Int]
                      -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                  -> ([Int] -> ([Int] -> Int) -> Int)
                  -> ([Int] -> Int)
                  -> Int)
              -> Int
              -> ([Int]
                  -> ([Int]
                      -> Int
                      -> ([Int]
                          -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                      -> ([Int] -> ([Int] -> Int) -> Int)
                      -> ([Int] -> Int)
                      -> Int)
                  -> Int
                  -> ([Int]
                      -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                  -> ([Int] -> ([Int] -> Int) -> Int)
                  -> ([Int] -> Int)
                  -> Int)
              -> ([Int]
                  -> Int
                  -> ([Int]
                      -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                  -> ([Int] -> ([Int] -> Int) -> Int)
                  -> ([Int] -> Int)
                  -> Int)
              -> Int
              -> ([Int]
                  -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
              -> ([Int] -> ([Int] -> Int) -> Int)
              -> ([Int] -> Int)
              -> Int)
          -> Int
          -> ([Int]
              -> Int
              -> ([Int]
                  -> ([Int]
                      -> Int
                      -> ([Int]
                          -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                      -> ([Int] -> ([Int] -> Int) -> Int)
                      -> ([Int] -> Int)
                      -> Int)
                  -> Int
                  -> ([Int]
                      -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                  -> ([Int] -> ([Int] -> Int) -> Int)
                  -> ([Int] -> Int)
                  -> Int)
              -> ([Int]
                  -> Int
                  -> ([Int]
                      -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                  -> ([Int] -> ([Int] -> Int) -> Int)
                  -> ([Int] -> Int)
                  -> Int)
              -> Int
              -> ([Int]
                  -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
              -> ([Int] -> ([Int] -> Int) -> Int)
              -> ([Int] -> Int)
              -> Int)
          -> Int
          -> ([Int]
              -> ([Int]
                  -> Int
                  -> ([Int]
                      -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                  -> ([Int] -> ([Int] -> Int) -> Int)
                  -> ([Int] -> Int)
                  -> Int)
              -> Int
              -> ([Int]
                  -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
              -> ([Int] -> ([Int] -> Int) -> Int)
              -> ([Int] -> Int)
              -> Int)
          -> ([Int]
              -> Int
              -> ([Int]
                  -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
              -> ([Int] -> ([Int] -> Int) -> Int)
              -> ([Int] -> Int)
              -> Int)
          -> Int
          -> ([Int]
              -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
          -> ([Int] -> ([Int] -> Int) -> Int)
          -> ([Int] -> Int)
          -> Int)
         -> Int
         -> ([Int]
             -> Int
             -> ([Int]
                 -> Int
                 -> ([Int]
                     -> ([Int]
                         -> Int
                         -> ([Int]
                             -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                         -> ([Int] -> ([Int] -> Int) -> Int)
                         -> ([Int] -> Int)
                         -> Int)
                     -> Int
                     -> ([Int]
                         -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                     -> ([Int] -> ([Int] -> Int) -> Int)
                     -> ([Int] -> Int)
                     -> Int)
                 -> ([Int]
                     -> Int
                     -> ([Int]
                         -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                     -> ([Int] -> ([Int] -> Int) -> Int)
                     -> ([Int] -> Int)
                     -> Int)
                 -> Int
                 -> ([Int]
                     -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                 -> ([Int] -> ([Int] -> Int) -> Int)
                 -> ([Int] -> Int)
                 -> Int)
             -> Int
             -> ([Int]
                 -> ([Int]
                     -> Int
                     -> ([Int]
                         -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                     -> ([Int] -> ([Int] -> Int) -> Int)
                     -> ([Int] -> Int)
                     -> Int)
                 -> Int
                 -> ([Int]
                     -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                 -> ([Int] -> ([Int] -> Int) -> Int)
                 -> ([Int] -> Int)
                 -> Int)
             -> ([Int]
                 -> Int
                 -> ([Int]
                     -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                 -> ([Int] -> ([Int] -> Int) -> Int)
                 -> ([Int] -> Int)
                 -> Int)
             -> Int
             -> ([Int]
                 -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
             -> ([Int] -> ([Int] -> Int) -> Int)
             -> ([Int] -> Int)
             -> Int)
         -> Int
         -> ([Int]
             -> Int
             -> ([Int]
                 -> ([Int]
                     -> Int
                     -> ([Int]
                         -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                     -> ([Int] -> ([Int] -> Int) -> Int)
                     -> ([Int] -> Int)
                     -> Int)
                 -> Int
                 -> ([Int]
                     -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                 -> ([Int] -> ([Int] -> Int) -> Int)
                 -> ([Int] -> Int)
                 -> Int)
             -> ([Int]
                 -> Int
                 -> ([Int]
                     -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                 -> ([Int] -> ([Int] -> Int) -> Int)
                 -> ([Int] -> Int)
                 -> Int)
             -> Int
             -> ([Int]
                 -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
             -> ([Int] -> ([Int] -> Int) -> Int)
             -> ([Int] -> Int)
             -> Int)
         -> Int
         -> ([Int]
             -> ([Int]
                 -> Int
                 -> ([Int]
                     -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
                 -> ([Int] -> ([Int] -> Int) -> Int)
                 -> ([Int] -> Int)
                 -> Int)
             -> Int
             -> ([Int]
                 -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
             -> ([Int] -> ([Int] -> Int) -> Int)
             -> ([Int] -> Int)
             -> Int)
         -> ([Int]
             -> Int
             -> ([Int]
                 -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
             -> ([Int] -> ([Int] -> Int) -> Int)
             -> ([Int] -> Int)
             -> Int)
         -> Int
         -> ([Int]
             -> ([Int] -> ([Int] -> Int) -> Int) -> ([Int] -> Int) -> Int)
         -> ([Int] -> ([Int] -> Int) -> Int)
         -> ([Int] -> Int)
         -> Int

Для решения этой проблемы мы объявим специальный класс Forth. Заодно заменим список на цепочку вложенных пар, чтобы наш стек мог хранить значения разных типов. Название для класса выбрано не случайно. С его помощью можно реализовать полноценный постфиксный язык - с ветвлением, циклами, побочными эффектами и так далее.

class Forth stack r where
    build :: stack -> r

begin = build ()

data End = End
end = End
instance (stack ~ (a, v)) => Forth stack (End -> a) where
    build (x,_) _ = x

data Add = Add
add = Add
instance Forth (Int,stack) r => Forth (Int,(Int,stack)) (Add -> r) where
    build (x, (y,st)) _ = build (x + y, st)

data Mul = Mul
mul = Mul
instance Forth (Int,stack) r => Forth (Int,(Int,stack)) (Mul -> r) where
    build (x, (y,st)) _ = build (x * y, st)
 
data Push = Push
push = Push
instance (a ~ Int, Forth (Int,stack) r) => Forth stack (Push -> a -> r) where
    build st _ x = build (x,st)


result = 
    begin
        push 1
        push 3
        push 7
        add
        push 8
        mul
        add
    end


main :: IO ()
main =
    print $ result

Теперь тип функции begin гораздо проще, и исходный код компилируется очень быстро.

• Found hole:
    _t1
      :: Push
         -> Int
         -> Push
         -> Int
         -> Push
         -> Int
         -> Add
         -> Push
         -> Int
         -> Mul
         -> Add
         -> End
         -> Int

Аналогичный подход может использоваться и для других задач. Например, для имитации функции форматирования.

class C a where
    f :: String -> a

instance C String where
    f s = s

instance C x => C (Char -> x) where
    f a x = f (a ++ [x])

instance C x => C (Bool -> x) where
    f a x = f (a ++ show x)

instance C x => C (String -> x) where
    f a x = f (a ++ x) 
    
main :: IO ()
main = 
    putStrLn $ f "Hello, " True " world" '!'

Более подробную информацию по теме со ссылками на оригинальные работы можно найти здесь: Polyvariadic functions and keyword arguments: pattern-matching on the type of the context.

Комментарии (0)