Frage Temporärer Namespace / Kontext in Haskell


Im Io, können Sie den Ausführungskontext mit 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 hat eine ähnliche Funktion mit Object#instance_exec, aber das Objektmodell ist etwas komplizierter.)

Dadurch erhalten Sie einen temporären Namespace, mit dem Sie domänenspezifische Sprachen schreiben können. Gibt es eine Technik, um einen ähnlichen Effekt (einen temporären Namensraum) in Haskell zu erreichen?

Zum Beispiel, etwas wie: (Nicht unbedingt genau so, aber etwas mit ähnlich prägnanter Syntax.)

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

Beachten Sie, dass die beiden deletes sind völlig verschiedene Funktionen. Ich würde es lieber vermeiden, Dinge wie zu schreiben H.delete und D.deleteweil das schnell unordentlich werden würde. Ich weiß, dass dies vermieden werden könnte, indem man diese Datenbankversion beispielsweise in deleteFromaber ich will nicht.


15
2017-07-26 03:32


Ursprung


Antworten:


"Das ist verrückt dynamisches Zeug. Das könntest du niemals in einer statischen Sprache machen ..."

{-# 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

"Das ist verrückt. Es funktioniert nicht"

*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

Sie sollten die Dinge wahrscheinlich nicht so machen. Aber wenn das wirklich das ist, was Sie wollen, sind implizite Parameter wahrscheinlich der eleganteste Weg, um es zu bekommen


Die recordWildCard-Lösung von applicative ist in gewisser Hinsicht besser als diese, da für die Einrichtung weniger Code benötigt wird und die Sprache viel kleiner ist. Außerdem ist es der Verwendung von "open" -Direktiven in Sprachen mit passenden Modulsystemen sehr ähnlich, oder "using namespace" -Befehle in Sprachen mit flexiblem Namensabstand (Haskell hat keine). Die implizite Parameterlösung erfasst etwas, das der Semantik dynamischer Sprachen ähnelt. Insbesondere können Sie mit dieser Funktion eine Funktion aufrufen, die implizite Argumente verwendet und sich nicht darum kümmern muss, die Umgebung manuell zu übergeben.

Sie können hier auch implizite Parameter mit einem ReaderT oder ähnlichem simulieren. Obwohl (aus verschiedenen Gründen), das ist nicht sehr kompositorisch, wenn Sie in Haskell 98 bleiben wollen.


15
2017-07-26 04:09



Hier ist die Methode von RecordWildCards viel diskutiert kürzlich in einer Flut von äußerst interessanten Beiträge, z.B. dieses an reddit / r / haskell und ein "Modular-Prelude" -Repo seziert Hier und schließlich das Tutorial Post besprochen Hier

Auf einem Weg, die Methode zu verwenden, würden wir zwei Hilfsmodule benötigen (diese ahmen nur Ihre beabsichtigten nach):

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}

und

module DB where
import System.Directory

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

db :: DB
db = Db readFile appendFile

dann importiere sie mit RecordWildCards etwas wie deinen Plan zu verwirklichen:

{-#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

Dies ist ein bisschen weniger hässlich als die ImplicitParams Annäherung und flexibler als Daniel Wagners Ansatz, der sonst der schönste ist.


15
2017-07-26 05:08



Nur zum Spaß, hier ist eine Übersetzung von Philip JF's Antwort, die keinen verrückten Erweiterungs-Quatsch verwendet. In der Tat ist es ziemlich langweilig, wenn du einmal herausgefunden hast, wie es geht:

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

14
2017-07-26 04:38