如何使用GHC MultiParamTypeClass

时间:2015-06-22 12:47:31

标签: haskell ghc

我正在尝试实现一个由点类型索引的“DrawEnv”类型类:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}

class Monad m => DrawEnv p m where
    box     :: p -> p -> m ()
    clear   :: m ()
    line    :: p -> p -> m ()

type Pos = (Float,Float)

instance DrawEnv Pos IO where
    box     p0 p1   = putStrLn $ "Box " ++ show p0 ++ " " ++ show p1
    clear           = putStrLn "Clear"
    line    p0 p1   = putStrLn $ "Line " ++ show p0 ++ " " ++ show p1

draw :: DrawEnv Pos m => m ()
draw = do
    clear
    box  (10.0,10.0) (100.0,100.0)
    line (10.0,10.0) (100.0,50.0)

然而GHC并不高兴:

Could not deduce (DrawEnv (t0, t1) m) arising from a use of `box'
from the context (DrawEnv Pos m)
  bound by the type signature for draw :: DrawEnv Pos m => m ()
  at Code/Interfaces3.hs:63:9-29
The type variables `t0', `t1' are ambiguous
Relevant bindings include
  draw :: m () (bound at Code/Interfaces3.hs:64:1)
Note: there is a potential instance available:
  instance DrawEnv Pos IO -- Defined at Code/Interfaces3.hs:56:10
In a stmt of a 'do' block: box (10.0, 10.0) (100.0, 100.0)
In the expression:
  do { clear;
       box (10.0, 10.0) (100.0, 100.0);
       line (10.0, 10.0) (100.0, 50.0) }
In an equation for `draw':
    draw
      = do { clear;
             box (10.0, 10.0) (100.0, 100.0);
             line (10.0, 10.0) (100.0, 50.0) }

我的问题是为什么GHC在Pos约束下不接受这个?

2 个答案:

答案 0 :(得分:2)

代码含糊不清。具体来说,我们不知道

sub sendMail {
    my $params = shift;
    my $body;
    my $smtp;
    ( $smtp = Net::SMTP::SSL->new(
                    'smtp.gmail.com',
                    Port=>465,
                    Debug   => 1,
                    Timeout => 60,
                    Hello => 'smtp2.dnikolov.co.nf@gmail.com',
                    Notify =>['SUCCESS','FAILURE'],
                    Debug => 1
                  ) )or die "Cant connect to server".$@.$!;
    $smtp->auth('smtp2.dnikolov.co.nf@gmail.com', $params->{password} ) or die "Can't authenticate ".$smtp->message();
    return 0 unless $smtp->verify( $params->{readerEmail}->{value} );
    $smtp->mail('smtp.dnikolov.co.nf@gmail.com'."\n") or die "Cant from";
    $smtp->to('dnikolov.co.nf@gmail.com'."\n") or die "invalid address";
    $smtp->data() or die "cant call data successfuly";
    $smtp->datasend('From:< "'.$params->{readerEmail}->{value}.'" smtp.dnikolov.co.nf@gmail.com>'."\n");
    $smtp->datasend('To:dnikolov.co.nf@gmail.com'."\n");
    $smtp->datasend('Subject:'.$params->{readerSubject}->{value}."\n");
    $smtp->datasend("\n");
    $smtp->datasend('Reader Email: '.$params->{readerEmail}->{value}."\n");
    $smtp->datasend('Reader Site: '.$params->{readerSite}->{value}."\n");
    $smtp->datasend('Reader Subject: '.$params->{readerSubject}->{value}."\n");
    $smtp->datasend('Reader Message: '.$params->{readerMessage}->{value}."\n");
    $smtp->dataend()or die "Failed to Send Message";
    $smtp->quit() or die "Failed on quit";
    return 1;
}

的类型。例如,它可能是sub sendMail { my $params = shift; my $body; my $smtp; ( $smtp = Net::SMTP::SSL->new( 'smtp.gmail.com', Port=>465, Debug => 1, Timeout => 60, Hello => 'smtp2.dnikolov.co.nf@gmail.com', Notify =>['SUCCESS','FAILURE'], Debug => 1 ) )or die "Cant connect to server".$@.$!; $smtp->auth('smtp2.dnikolov.co.nf@gmail.com', $params->{password} ) or die "Can't authenticate ".$smtp->message(); return 0 unless $smtp->verify( $params->{readerEmail}->{value} ); $smtp->mail('smtp.dnikolov.co.nf@gmail.com'."\n") or die "Cant from"; $smtp->to('dnikolov.co.nf@gmail.com'."\n") or die "invalid address"; $smtp->data() or die "cant call data successfuly"; $smtp->datasend('From:< "'.$params->{readerEmail}->{value}.'" smtp.dnikolov.co.nf@gmail.com>'."\n"); $smtp->datasend('To:dnikolov.co.nf@gmail.com'."\n"); $smtp->datasend('Subject:'.$params->{readerSubject}->{value}."\n"); $smtp->datasend("\n"); $smtp->datasend('Reader Email: '.$params->{readerEmail}->{value}."\n"); $smtp->datasend('Reader Site: '.$params->{readerSite}->{value}."\n"); $smtp->datasend('Reader Subject: '.$params->{readerSubject}->{value}."\n"); $smtp->datasend('Reader Message: '.$params->{readerMessage}->{value}."\n"); $smtp->dataend()or die "Failed to Send Message"; $smtp->quit() or die "Failed on quit"; return 1; } 。它最常见的类型是(10.0,10.0)

解决方法是写

(Double,Double)

而是类似地修复其他行。

答案 1 :(得分:2)

此类定义不起作用,因为clear的类型没有提及类型变量p,因此无法使用clear实例化box具体类型。将类型签名添加到lineclear :: IO ()不会有帮助 - 即使class Monad m => DrawEnv p m | m -> p where 也会产生类型错误。

这可以通过向您的类添加函数依赖项来解决:

class Monad m => MonadDraw m where 
  putStringLn :: String -> m () 

  clear   :: m ()
  clear = putStringLn "Clear"

class DrawEnv p where
  box     :: MonadDraw m => p -> p -> m ()
  line    :: MonadDraw m => p -> p -> m ()

instance (Fractional a, Show a, Fractional b, Show b) => DrawEnv (a,b) where
    box     p0 p1   = putStringLn $ "Box " ++ show p0 ++ " " ++ show p1
    line    p0 p1   = putStringLn $ "Line " ++ show p0 ++ " " ++ show p1

draw :: MonadDraw m => m () 
draw = do
    clear
    box  (10.0,10.0) (100.0,100.0)
    line (10.0,10.0) (100.0,50.0)

然后你的其余代码编译。或者,您可以将您的班级分为两类:

<mvcSiteMapNode id="Home" title="Home" controller="Home" action="Index">
    <mvcSiteMapNode title="Page2" controller="Page2" action="Index"/>
    <mvcSiteMapNode title="Page3" controller="Page3" action="Index" />
    <mvcSiteMapNode title="Page4" controller="Pag4" action="Index" />
</mvcSiteMapNode>