Временное пространство имен/контекст в Haskell

В Io вы можете установить контекст выполнения с помощью do:

Http := Object clone
Http get := method(uri, ("<GET request to " .. uri .. ">") println)
Http delete := method(uri, ("<DELETE request to " .. uri .. ">") println)

Database := Object clone
Database insert := method(table, data, ("<insert data to " .. table .. ">") println)
Database delete := method(table, id, ("<delete " .. id .. " from " .. table .. ">") println)

Http do(
  get("http://example.com/")
  delete("http://example.com/something")
)

Database do(
  insert("cats", list("Phil", "gray"))
  delete("cats", 12)
)

(Ruby имеет аналогичную функцию с Object#instance_exec, но его объектная модель немного сложнее.)

По сути, это дает вам временное пространство имен, которое хорошо подходит для написания доменных языков. Есть ли способ достижения аналогичного эффекта (временного пространства имен) в Haskell?

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

main = do
  http $ do
    get "http://example.com/"
    delete "http://example.com/something"
  database $ do
    insert "cats" ["Phil", "gray"]
    delete "cats" 12

Обратите внимание, что два delete - это совершенно разные функции. Я бы предпочел не писать такие вещи, как H.delete и D.delete, потому что это будет беспорядочно быстро. Я понимаю, что этого можно избежать, переименовав эту версию базы данных, например, deleteFrom, но я не хочу.

Ответы

Ответ 1

"Это сумасшедший динамический материал. Вы никогда не сможете сделать это на статическом языке..."

{-# LANGUAGE ImplicitParams, Rank2Types #-}
import Text.Printf
http :: (((?get :: String -> IO ()),
          (?delete :: String -> IO ())) 
         => IO a)
        -> IO a
http a = let ?get    = \s -> printf "http get %s\n" s
             ?delete = \s -> printf "http delete %s\n" s
         in a

database :: (((?insert :: String -> [String] -> IO ()),
              (?delete :: String -> Integer -> IO ())) 
               => IO a) 
            -> IO a
database a = let ?insert = \s ls -> printf "database insert %s %s\n" s (show ls)
                 ?delete = \s n  -> printf "database delete %s %d\n" s n
             in a

main = do
  http $ do
    ?get "http://example.com/"
    ?delete "http://example.com/something"
  database $ do
    ?insert "cats" ["Phil", "gray"]
    ?delete "cats" 12

", это безумие. Ни в коем случае это не работает"

*Main> :l Crazy.hs 
[1 of 1] Compiling Main             ( Crazy.hs, interpreted )
Ok, modules loaded: Main.
*Main> main
http get http://example.com/
http delete http://example.com/something
database insert cats ["Phil","gray"]
database delete cats 12

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


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

Вы также можете имитировать неявные параметры здесь с помощью ReaderT или что-то в этом роде. Хотя (по разным причинам), это не очень композиционно, если вы хотите остаться в Haskell 98.

Ответ 2

Вот метод RecordWildCards, который обсуждался недавно в шквал чрезвычайно интересных сообщений, например. это reddit/r/haskell и 'модульная прелюдия' repo раскрыл здесь и, наконец, этот учебный пост обсуждался здесь

На одном из способов использования метода нам понадобятся два вспомогательных модуля (они просто имитируют ваши предполагаемые):

module HTTP where
import System.Directory

data Http = Http {get :: String -> IO String, delete :: String -> IO ()}

http :: Http
http = Http {get = fmap reverse . readFile, delete = removeFile}

и

module DB where
import System.Directory

data DB = Db {get :: String -> IO String, insert :: String -> String -> IO ()}

db :: DB
db = Db readFile appendFile

затем импортируйте их, используя RecordWildCards, чтобы реализовать что-то вроде вашего плана:

{-#LANGUAGE RecordWildCards#-}
import           DB   -- qualified imports might be better
import           HTTP     -- but aren't needed in this case

main =  do 
  let Http{..} = http
  do test <- get "test.txt"
     delete "test2.txt"
     putStrLn $ take 10 test

  let Db{..} = db
  do test3 <- get "test3.txt"
     insert "test4.txt" "happy" 
     putStrLn $ take 10 test3 
     get "test4.txt" >>= putStrLn

Это немного менее уродливо, чем подход ImplicitParams и более гибкий, чем подход Дэниела Вагнера, который в противном случае является самым красивым.

Ответ 3

Просто для удовольствия, вот перевод ответа Филиппа Дж.Ф., который не использует сумасшедшую глупость. На самом деле, это довольно скучно, как только вы разработали, как это сделать:

import Text.Printf

http :: ((String -> IO ()) -> (String -> IO ()) -> IO a)
     -> IO a
http a = a (printf "http get %s\n") (printf "http delete %s\n")

database :: ((String -> [String] -> IO ()) -> (String -> Integer -> IO ()) -> IO a) 
         -> IO a
database a = a (\s -> printf "database insert %s %s\n" s . show)
               (printf "database delete %s %d\n")

main = do
  http $ \get delete -> do
    get "http://example.com/"
    delete "http://example.com/something"
  database $ \insert delete -> do
    insert "cats" ["Phil", "gray"]
    delete "cats" 12