16

In Io, you can set the execution context using 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 has a similar feature with Object#instance_exec, but its object model is a bit more complicated.)

In effect, this gives you a temporary namespace, which is nice for writing domain specific languages. Is there a technique to achieve a similar effect (a temporary namespace) in Haskell?

For example, something like: (Not necessarily exactly like this, but something with similarly succinct syntax.)

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

Notice that the two deletes are completely different functions. I'd prefer to avoid writing things like H.delete and D.delete, because that would get messy quick. I realize that this could be avoided by renaming that database version to, for example, deleteFrom, but I don't want to.

Snowball
  • 11,102
  • 3
  • 34
  • 51
  • 1
    http://stackoverflow.com/questions/6636107/polymorphism-in-haskell Maybe you can make your arguments hava different type. – Pikaurd Jul 26 '12 at 03:56

3 Answers3

16

"That is crazy dynamic stuff. You could never do that in a static language..."

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

"that is crazy. No way it works"

*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

you should probably not actually do things this way. But if that is really what you want, implicit parameters are probably the most elegant way to get it


applicative's RecordWildCard solution is in some sense better than this since it takes less code to set up, and involves a much smaller extension to the language. Furthermore it is very similar to using "open" directives in languages with proper module systems, or "using namespace" commands in languages with flexible name spacing (Haskell has neither). The implicit parameter solution captures something similar to the semantics of dynamic languages. In particular, using it you can call a function that uses the implicit arguments, and not have to worry about manually passing the environment.

You can also simulate implicit parameters here with a ReaderT or something like it. Although (for various reasons), that is not very compositional if you want to stay in Haskell 98.

Philip JF
  • 28,199
  • 5
  • 70
  • 77
16

Here is the method of RecordWildCards much discussed recently in a flurry of extremely interesting posts e.g. this one on reddit/r/haskell and a 'modular-prelude' repo dissected here and finally this tutorial post discussed here

On one way of using the method we'd need two helper modules (these merely imitate your intended ones):

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}

and

module DB where
import System.Directory

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

db :: DB
db = Db readFile appendFile

then import them, using RecordWildCards to realize something like your plan:

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

This is a little less ugly than the ImplicitParams approach and more flexible than Daniel Wagner's approach, which is otherwise the most handsome.

applicative
  • 8,081
  • 35
  • 38
  • 1
    That's an interesting possibility. The problem with Daniel Wagner's solution (and the reason I asked the question) is it gets quite a bit less handsome when you add, for example, `post`, `put`, `options`, `trace`, and `head` on the HTTP side and `update`, `drop`, `alterTable`, `createTable`, `createIndex`, `createTrigger`, `truncate`, `grant`, and `revoke` on the database side. – Snowball Jul 26 '12 at 13:13
  • Just to be sure. This actually compiles with a recent enough ghc, right? – Tarrasch Jul 27 '12 at 08:44
  • 1
    It compiles with ghc-7.4. I think the RecordWildCards extension has been around for several years. – applicative Jul 27 '12 at 12:44
  • This is just AWESOME! Never thought of using RecordWildCards in that way. With this let-binding trick, (explicit) dictionaries become more elegant than the abuse of type classes (which are implicit dictionaries). – comonad Aug 02 '12 at 16:13
15

Just for fun, here's a translation of Philip JF's answer that doesn't use any crazy extension nonsense. In fact, it's quite boring, once you've worked out how to do it:

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
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380