在大多数支持可变变量的编程语言中,可以轻松实现类似这样的Java示例:
interface Accepter<T> {
void accept(T t);
}
<T> T getFromDoubleAccepter(Accepter<Accepter<T>> acc){
final List<T> l = new ArrayList<T>();
acc.accept(new Accepter<T>(){
@Override
public void accept(T t) {
l.add(t);
}
});
return l.get(0); //Not being called? Exception!
}
Run Code Online (Sandbox Code Playgroud)
对于那些不了解Java的人来说,上面的代码收到的东西可以提供一个带有一个参数的函数,它应该将这个参数作为最终结果.
这不像callCC:没有控制流交替.仅涉及内部函数的参数.
我认为Haskell中的等效类型签名应该是
getFromDoubleAccepter :: (forall b. (a -> b) -> b) -> a
Run Code Online (Sandbox Code Playgroud)
所以,如果有人可以为你(a -> b) -> b选择的某种类型提供功能,那么他必须已经拥有了a.所以你的工作就是给他们一个"回调",而不是保留他们发送给你的任何东西,一旦他们回到你身边,就把这个价值归还给你的来电者.
但我不知道如何实现这一点.我能想到几种可能的解决方案.虽然我不知道它们中的每一个是如何工作的,但我可以根据预期的困难对它们进行评级和排序:
Cont或者ContTmonad.我认为这是最简单的.
RWS monad或类似的.
任何其他单子.纯粹的 monad就像Maybe我认为更难.
仅使用标准的纯功能特性,如惰性评估,模式匹配,定点污染等.我认为最难(甚至不可能).
我希望看到使用上述任何技术的答案(并且更喜欢更难的方法).
注意:不应该对类型签名进行任何修改,解决方案应该与Java代码执行相同的操作.
UPDATE
有一次我看到有人评论说getFromDoubleAccepter f = f id我意识到我犯了错误.基本上我forall只是为了让游戏更容易,但看起来这种扭曲太容易了.实际上,上面的类型签名会强制调用者返回我们给他们的任何内容,所以如果我们选择a那样b实现给出相同的预期结果,但它只是...不期望.
实际上我想到的是类型签名,如:
getFromDoubleAccepter :: ((a -> ()) -> ()) -> a
Run Code Online (Sandbox Code Playgroud)
而这一次更难.
另一位评论作者要求推理.让我们看一下类似的功能
getFunctionFromAccepter :: (((a -> b) -> b) -> b) -> a -> b
Run Code Online (Sandbox Code Playgroud)
这个有一个天真的解决方案:
getFunctionFromAccepter f = \a -> f $ \x -> x a
Run Code Online (Sandbox Code Playgroud)
但是在下面的测试代码中它在第三个上失败:
exeMain = do
print $ getFunctionFromAccepter (\f -> f (\x -> 10)) "Example 1" -- 10
print $ getFunctionFromAccepter (\f -> 20) "Example 2" -- 20
print $ getFunctionFromAccepter (\f -> 10 + f (\x -> 30)) "Example 3" --40, should be 30
Run Code Online (Sandbox Code Playgroud)
在失败的情况下,我们传递一个返回的函数,30我们期望得到该函数.然而,最终结果反过来40,所以它失败了.有没有什么办法来实现这样做只是这件事,我想?
如果这可以在Haskell中完成,那么有很多有趣的序列.例如,元组(或其他"代数"类型),也可被定义为功能,因为我们可以这样说type (a,b) = (a->b->())->(),实施fst和snd在这个词.这就是我在其他几种语言中使用的方式,这些语言没有本地"元组"支持但具有"闭包"功能.
sha*_*ang 10
类型accept是void accept(T)等效的Haskell类型t -> IO ()(因为Java中的每个函数本质上都是IO).因此getFromDoubleAccepted可以直接翻译为
import Data.IORef
type Accepter t = t -> IO ()
getFromDoubleAccepter :: Accepter (Accepter a) -> IO a
getFromDoubleAccepter acc = do
l <- newIORef $ error "Not called"
acc $ writeIORef l
readIORef l
Run Code Online (Sandbox Code Playgroud)
如果您想在Haskell中使用惯用的非IO解决方案,除了尝试模仿某些Java模式之外,您还需要更具体地了解您的实际最终目标.
编辑:关于更新
getFromDoubleAccepter :: ((a -> ()) -> ()) -> a
Run Code Online (Sandbox Code Playgroud)
对不起,但这个签名绝不等于Java版本.你所说的是,对于任何一个a,给定一个函数,它接受一个a但不返回任何东西或做任何副作用的函数,你想要以某种方式让人联想到一个类型的值a.满足给定签名的唯一实现基本上是:
getFromDoubleAccepter :: ((a -> ()) -> ()) -> a
getFromDoubleAccepter f = getFromDoubleAccepter f
Run Code Online (Sandbox Code Playgroud)
首先,我会尽可能多地音译.我将把这些计算提升到monad因为accept返回void(()在Haskell-land中读取),除非有一些效果,否则它是无用的.
type Accepter m t = t -> m ()
getFromDoubleAccepter :: (MonadSomething m) => Accepter m (Accepter m t) -> m t
getFromDoubleAccepter acc = do
l <- {- new mutable list -}
acc $ \t -> add l t
return (head l)
Run Code Online (Sandbox Code Playgroud)
当然,我们不能制作这样的可变列表,所以我们必须在这里使用一些直观的火花.当一个动作只是向某个累加器添加一个元素时,我想到了Writermonad.所以也许那条线应该是:
acc $ \t -> tell [t]
Run Code Online (Sandbox Code Playgroud)
由于你只是在最后返回列表的头部,这没有任何影响,我认为签名应该变为:
getFromDoubleAccepter :: Accepter M (Accepter M t) -> t
Run Code Online (Sandbox Code Playgroud)
哪个M是合适的单子.它需要能够写[t]s,这样才能给我们:
type M t = Writer [t]
getFromDoubleAccepter :: Accepter (M t) (Accepter (M t) t) -> t
Run Code Online (Sandbox Code Playgroud)
现在,这个函数的类型告诉我们如何编写其余部分:
getFromDoubleAccepter acc =
head . execWriter . acc $ \t -> tell [t]
Run Code Online (Sandbox Code Playgroud)
我们可以检查一下它做了些什么......
ghci> getFromDoubleAccepter $ \acc -> acc 42
42
Run Code Online (Sandbox Code Playgroud)
我想这似乎是对的.我还不清楚这段代码应该是什么意思.
M t类型签名中的显式对我来说有点美观.如果我知道我解决了什么问题,我会仔细看看.如果您的意思是参数可以是一系列命令,但是否则没有可用的计算功能,那么您可以将类型签名专门化为:
getFromDoubleAccepter :: (forall m. (Monad m) => Accepter m (Accepter m t)) -> t
Run Code Online (Sandbox Code Playgroud)
这仍然适用于我们的例子.当然,这有点傻.考虑
forall m. (Monad m) => Accepter m (Accepter m t))
= forall m. (Monad m) => (t -> m ()) -> m ()
Run Code Online (Sandbox Code Playgroud)
这种类型的函数唯一能做的就是t按顺序调用各种s的参数,然后返回().这些函数中的信息完全由那些ts 表征[1] ,因此我们可以很容易地使用
getFromDoubleAccepter :: [t] -> t
getFromDoubleAccepter = head
Run Code Online (Sandbox Code Playgroud)
[1]只要我什么都不做,我不妨说在面对无限时这不太准确.计算
crazy :: Integer -> Accepter m (Accepter m Integer)
crazy n acc = crazy (n+1) >> acc n
Run Code Online (Sandbox Code Playgroud)
可以用来形成无限序列
... >> acc 3 >> acc 2 >> acc 1 >> acc 0
Run Code Online (Sandbox Code Playgroud)
没有第一个元素.如果我们试图将其解释为列表,那么在尝试查找第一个元素时,我们会得到无限循环.然而,这个计算比无限循环有更多的信息 - 如果不是列表,我们使用Lastmonoid来解释它,我们将能够提取0结束.真的
forall m. (Monad m) => Accepter m (Accepter m t)
Run Code Online (Sandbox Code Playgroud)
与列表略微一般的东西是同构的; 特别是一个免费的幺半群