在内部添加辅助函数的类型签名

时间:2019-05-31 09:31:51

标签: haskell functional-programming type-signature

首先,我定义以下数据类型

Route::get('/','SearchController@index');
Route::get('/search','SearchController@search');

然后我想实现一个函数,该函数映射到具有以下类型签名的<!DOCTYPE html> <html> <head> <meta id="token" name="_token" content="{{ csrf_token() }}"> <title>Live Search</title> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script> </head> <body> <div class="container"> <div class="row"> <div class="panel panel-default"> <div class="panel-heading"> <h3>Products info </h3> </div> <div class="panel-body"> <div class="form-group"> <input type="text" class="form-controller" id="search" name="search"></input> </div> <table class="table table-bordered table-hover"> <thead> <tr> <th>ID</th> <th>Product Name</th> <th>Description</th> <th>Price</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> </div> </div> <script type="text/javascript"> $('#search').on('keyup',function(){ $value=$(this).val(); $.ajax({ type : 'get', url : '{{URL::to('search')}}', data:{'search':$value}, success:function(data){ $('tbody').html(data); } }); }); </script> <script type="text/javascript"> $.ajaxSetup({ headers: { 'csrftoken' : '{{ csrf_token() }}' } }); </script> </body> </html>

data Supply s a = S (Stream s -> (a, Stream s))
data Stream a = Cons a (Stream a)

这是我的实现:(可以毫无问题地编译)

Supply

然后,当我尝试为mapSupply :: (a -> b) -> Supply s a -> Supply s b 内定义的名为mapSupply :: (a -> b) -> Supply s a -> Supply s b mapSupply mapFunc (S supFuncA) = S supFuncB where supFuncB strm = let (la, strms) = supFuncA strm in ((mapFunc la), strms) 的辅助函数的类型签名写下问题时,我遇到了一个问题。

supFuncB的类型签名非常简单,应为:

mapSupply

但是,当我尝试在代码中添加类型签名时,出现了编译器错误。代码看起来像这样

supFuncB

然后编译器抱怨:

supFuncB :: Stream s -> (b, Stream s)

我对Haskell还是陌生的,我不明白为什么编译会失败?如果我将其添加到代码中,那么正确的类型签名应该是什么。

1 个答案:

答案 0 :(得分:0)

从头开始,解决方案是打开ScopedTypeVariables并在forall签名中使用显式mapSupply,如下所示:

{-# LANGUAGE ScopedTypeVariables #-}  -- Put this at the top of your file.

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: Stream s -> (b, Stream s)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

接下来是为什么这样做的解释。


当您这样写签名时:

mapSupply :: (a -> b) -> Supply s a -> Supply s b

GHC实际上看到了这一点:

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b

通常可以保留为隐式的forall表示abs可以是任何东西-mapSupply polymorphic 函数,因此使用它的人可以自由选择三个类型变量的任何具体类型。明确地编写forall,第二个定义如下:

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: forall s b. Stream s -> (b, Stream s)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

据此,a中的bsmapSupply可以是任何东西,sb也是一样在supFuncB中。不过,这是一个问题。例如,定义涉及strms,其类型为s ...,但由于您正在使用s而显示的supFuncA not < / em>来自supFuncB签名的一个,而不是来自mapSupply签名的一个。正如我之前指出的,虽然s中的mapSupply可以是任何东西,但是一旦您实际上使用s选择了mapSupplys supFuncB中的内容必须与之匹配。就是这样,forall签名中的supFuncB不合适,因为其类型变量实际上不能是任何东西。更容易看出我们是否重命名了supFuncB中的类型变量,以使它们的名称与mapSupply中的类型变量不冲突(鉴于forall,这应该是一个有效的做法)那个):

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: forall s1 b1. Stream s1 -> (b1, Stream s1)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

(GHC在内部执行此操作,这说明了为什么您收到的错误消息中提到了s1类型的变量。)

仅由于添加到supFuncB的签名(引入了隐式forall)而发生此问题。没有签名,GHC可以通过不对supFuncB中的类型进行泛化来完成您想要的工作-在这种情况下,它不是多态的,而是monomorphic in the type variables a,{{1 }}和b ,它们是从s借来的。使用mapSupply扩展名可以在为ScopedTypeVariables编写类型签名时恢复该行为。启用该功能后,对签名中的类型变量使用显式supFuncB会使对应定义中具有相同名称的任何类型变量都引用同一事物(只要它们不在{ {1}}签名)。换句话说,这样做可以在相应定义范围内的任何地方引用外部签名中的变量,从而证明扩展名的正确性。