在大多数支持可变变量的编程语言中,可以轻松实现类似 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!
}
对于那些不了解Java的人来说,上面的代码收到的东西可以提供一个带有一个参数的函数,它应该把这个参数作为最终结果。
这与callCC
不同:没有控制流交替。仅涉及内部函数的参数。
我认为 Haskell 中的等效类型签名应为
getFromDoubleAccepter :: (forall b. (a -> b) -> b) -> a
因此,如果有人可以为您选择的某种类型提供函数(a -> b) -> b
,那么他必须已经拥有a
。所以你的工作就是给他们一个“回调”,而不是保留他们发送给你的任何东西,一旦他们回到你身边,将那个值返回给你的来电者。
但我不知道如何实现这一点。我能想到几种可能的解决方案。虽然我不知道它们中的每一个是如何工作的,但我可以根据预期的困难对它们进行评级和排序:
Cont
或ContT
monad。我认为这是最简单的。
RWS
monad或类似。
任何其他monad。 纯 monad,如Maybe
我认为更难。
仅使用标准的纯功能特性,如延迟评估,模式匹配,定点污染等。我认为最难(甚至不可能)。
< / LI>我希望看到使用上述任何技术的答案(并且更喜欢更难的方法)。
注意:不应对类型签名进行任何修改,解决方案应该与 Java 代码相同。
更新
有一次我看到有人评论getFromDoubleAccepter f = f id
我意识到我犯了错误。基本上我使用forall
只是为了让游戏更容易,但看起来这种扭曲太容易了。实际上,上面的类型签名强制调用者传回我们给他们的任何内容,因此如果我们选择a
作为b
,那么该实现会给出相同的预期结果,但它只是......没想到。
实际上,我想到的是类型签名,如:
getFromDoubleAccepter :: ((a -> ()) -> ()) -> a
而这一次更难。
另一位评论作者要求推理。让我们看一下类似的函数
getFunctionFromAccepter :: (((a -> b) -> b) -> b) -> a -> b
这个有一个天真的解决方案:
getFunctionFromAccepter f = \a -> f $ \x -> x a
但是在下面的测试代码中它在第三个测试中失败了:
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
在失败的情况下,我们传递一个返回30
的函数,我们期望得到该函数。但最终结果依次为40
,因此失败。有没有办法实现只是我想要的东西?
如果可以在Haskell中完成,那么会有很多有趣的序列。例如,元组(或其他“代数”类型)也可以定义为函数,因为我们可以说type (a,b) = (a->b->())->()
之类的内容,并在此方面实现fst
和snd
。这就是我在其他几种语言中使用的方式,这些语言没有本地“元组”支持,但具有“闭包”功能。
答案 0 :(得分: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
如果您想在Haskell中使用惯用的非IO解决方案,除了尝试模仿某些Java模式之外,还需要更具体地了解您的实际最终目标。
编辑:关于更新
getFromDoubleAccepter :: ((a -> ()) -> ()) -> a
对不起,但这个签名绝不等同于Java版本。你所说的是对于任何a
,给定一个函数,该函数接受一个a
但不返回任何或做任何副作用的函数,你想以某种方式让人联想到a
类型的值。满足给定签名的唯一实现基本上是:
getFromDoubleAccepter :: ((a -> ()) -> ()) -> a
getFromDoubleAccepter f = getFromDoubleAccepter f
答案 1 :(得分:7)
首先,我会尽可能地音译。我将这些计算提升为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)
当然,我们不能制作这样的可变列表,所以我们必须在这里使用一些直观的火花。当一个动作只是向某个累加器添加一个元素时,我会想到Writer
monad。所以也许那条线应该是:
acc $ \t -> tell [t]
由于你只是在最后返回列表的头部而没有任何效果,我认为签名应该成为:
getFromDoubleAccepter :: Accepter M (Accepter M t) -> t
其中M
是合适的monad。它需要能够写[t]
s,这样才能给我们:
type M t = Writer [t]
getFromDoubleAccepter :: Accepter (M t) (Accepter (M t) t) -> t
现在这个函数的类型告诉我们如何编写其余部分:
getFromDoubleAccepter acc =
head . execWriter . acc $ \t -> tell [t]
我们可以检查它是否有所作为......
ghci> getFromDoubleAccepter $ \acc -> acc 42
42
所以这似乎是正确的,我想。我还不清楚这段代码应该是什么意思。
类型签名中的显式M t
对我来说有点美观。如果我知道我解决了什么问题,我会仔细看看。如果你的意思是参数可以是一系列命令,但是没有可用的计算功能,那么你可以将类型签名专门化为:
getFromDoubleAccepter :: (forall m. (Monad m) => Accepter m (Accepter m t)) -> t
仍然适用于我们的示例。当然,这有点傻。考虑
forall m. (Monad m) => Accepter m (Accepter m t))
= forall m. (Monad m) => (t -> m ()) -> m ()
此类型的函数唯一能做的就是按顺序调用其各种t
的参数,然后返回()
。这些函数中的信息由t
完全表征[1],因此我们可以很容易地使用
getFromDoubleAccepter :: [t] -> t
getFromDoubleAccepter = head
[1]只要我一无所获,我不妨说在面对无限时这不太准确。计算
crazy :: Integer -> Accepter m (Accepter m Integer)
crazy n acc = crazy (n+1) >> acc n
可用于形成无限序列
... >> acc 3 >> acc 2 >> acc 1 >> acc 0
没有第一个元素。如果我们试图将其解释为列表,那么在尝试查找第一个元素时,我们会得到无限循环。然而,这个计算比无限循环有更多的信息 - 如果不是列表,我们使用Last
monoid来解释它,我们将能够提取0
。真的是
forall m. (Monad m) => Accepter m (Accepter m t)
与某些比列表更通用的东西是同构的;特别是一个免费的幺半群。
答案 2 :(得分:0)
感谢上述答案,我最后得出结论,在Haskell中,我们可以做一些与其他语言不同的事情。
实际上,这篇文章的动机是翻译着名的“single axiom classical logic reduction system”。我用其他一些语言实现了这个。
实现应该没问题Axiom: (a|(b|c)) | ((d|(d|d)) | ((e|b) | ((a|e) | (a|e))))
但是,由于缩小规则看起来像
Rule: a|(b|c), a |-- c
有必要提取内部参数作为最终结果。在其他语言中,这是通过使用像可变插槽这样的副作用来完成的。但是,在Haskell中,我们没有可变的插槽,涉及IO
会很难看,所以我一直在寻找解决方案。
乍一看(在我的问题中显示),getFromDoubleAccepter f = f id
似乎是无稽之谈,但我意识到它实际上在这种情况下有效!例如:
rule :: (forall r.a -> (b -> c -> r) -> r) -> a -> c
rule abc a = abc a $ flip const
诀窍仍然是相同的:因为存在性限定从调用者隐藏r
,并且由被调用者为其获取类型,我们可以指定c
为{ {1}},所以我们只需应用给定的函数来获得结果。另一方面,给定的函数必须使用我们的输入来产生最终答案,因此它有效地将实现限制为我们真正想要的!
将它们放在一起,让我们看看我们能用它做些什么:
r
这里我使用命名约定来跟踪类型签名:变量名称由“有效”类型变量组合(意味着它不是结果类型 - 所有newtype I r a b = I { runI :: a -> b -> r }
rule :: (forall r. I r a (I r b c)) -> a -> c
rule (I abc) a = abc a (I (\b c -> c))
axiom :: I r0 (I r1 a (I r2 b c))
(I r0 (I r3 d (I r3 d d))
(I r4 (I r2 e b) (I r4 (I r1 a e) (I r1 a e))))
axiom = let
a1 (I eb) e = I $ \b c -> eb e b
a2 = I $ \d (I dd) -> dd d d
a3 (I abc) eb = I $ \a e -> abc a (a1 eb e)
a4 abc = I $ \eb aeae -> runI a2 (a3 abc eb) aeae
in I $ \abc (I dddebaeae) -> dddebaeae a2 (a4 abc)
类型变量)。
我不会重复在选址文章中所代表的证明,但我想展示一些东西。在r*
的上述定义中,我们使用一些let绑定变量来构造结果。毫不奇怪,可以使用axiom
和rule
提取这些变量本身。让我们看看如何:
axiom
还有一件事需要证明,我们可以--Equal to a4
t4 :: I r0 a (I r1 b c) -> I r2 (I r1 d b) (I r2 (I r0 a d) (I r0 a d))
t4 abc = rule axiom abc
--Equal to a3
t3 :: I r0 a (I r1 b c) -> I r1 d b -> I r0 a d
t3 abc eb = rule (t4 abc) eb
--Equal to a2
t2 :: I r a (I r a a)
t2 = rule (t3 axiom (t3 (t4 axiom) axiom)) axiom
--Equal to a1
t1 :: I r a b -> a -> I r b c
t1 ab a = rule (t3 t2 (t3 (t3 t2 t2) ab)) a
使用t1
来证明所有重言式。我觉得情况确实如此,但还没有证明。
与其他语言相比,Haskell的称呼似乎更有效和富有表现力。