在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
来避免这种情况,但我不想这样做。
答案 0 :(得分:15)
“那是疯狂的动态。你永远不能用静态语言做到这一点......”
{-# 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
你可能不应该以这种方式做事。但如果这真的是你想要的,隐式参数可能是获得它的最优雅方式
applicative的RecordWildCard解决方案在某种意义上比这更好,因为它需要较少的代码来设置,并且涉及对语言的小得多的扩展。此外,它非常类似于在具有适当模块系统的语言中使用“open”指令,或者在具有灵活名称间隔的语言中使用“using namespace”命令(Haskell既没有)。隐式参数解决方案捕获类似于动态语言的语义的东西。特别是,使用它可以调用使用隐式参数的函数,而不必担心手动传递环境。
您还可以使用ReaderT或类似的方法在此处模拟隐式参数。虽然(由于各种原因),如果你想留在Haskell 98,这不是很有组合。
答案 1 :(得分:15)
以下是最近在一系列非常有趣的帖子中讨论的RecordWildCards
的方法,例如这个reddit/r/haskell和'modular-prelude' repo解剖here,最后这个tutorial post讨论了here
在使用该方法的一种方法中,我们需要两个辅助模块(这些模块只是模仿你想要的模块):
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
方法稍微不那么丑陋,比Daniel Wagner的方法更灵活,否则这是最帅的。
答案 2 :(得分:14)
只是为了好玩,这里是菲利普JF答案的翻译,不使用任何疯狂的扩展废话。事实上,一旦你弄清楚如何做到这一点,它就很无聊了:
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