试图理解evancz/url-parser模块,我偶然发现了我难以理解的这种类型声明:( 来源)
type Parser a b =
Parser (State a -> List (State b))
Run Code Online (Sandbox Code Playgroud)
"Parser"作为类型名称出现在类型定义中的事实尤其令人不安.
有人可以用英语解释类型注释吗?例如"给定两个抽象类型a和b,......?"
非常感谢.
这里有一些东西需要解压缩,所以让我们分解一下:
类型名称可以具有相同名称的构造函数.这是有效的代码:
type Foo a = Foo a
Run Code Online (Sandbox Code Playgroud)
上面的类型Foo采用单个类型参数,并且Foo a通过使用恰好共享相同名称的单个构造函数,只有一种方法可以创建类型的值.这让我们可以定义不同类型的Foos,如下所示:
fooString : Foo String
fooString = Foo "abc"
fooInt : Foo Int
fooInt = Foo 123
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,Foo充当字符串或int值的容器.但这不是它可以坚持的全部.由于函数是Elm中的值,因此您可以Foo拥有一个函数.让我们定义一个带整数并向其中加一的函数:
plusOne : Int -> Int
plusOne = (+) 1
Run Code Online (Sandbox Code Playgroud)
现在,让我们将其包装成一个Foo值:
fooPlusOner : Foo (Int -> Int)
fooPlusOner = Foo plusOne
Run Code Online (Sandbox Code Playgroud)
这是完全有效的代码.类型的Foo (Int -> Int)值只是函数的包装器.那么现在我们正在包装一个函数,我们怎么能用它做点什么呢?让我们创建一个在里面运行函数的函数fooPlusOner,给出一个整数作为起点:
runFooIntFunc : Int -> Foo (Int -> Int) -> Int
runFooIntFunc val (Foo f) = f val
Run Code Online (Sandbox Code Playgroud)
如果您像这样运行此函数runFooIntFunc 3 fooPlusOner,则会收到该值4.
我们可以稍微概括一下这个函数,以便明确地使用Ints:
runFooFunc : a -> Foo (a -> a) -> a
runFooFunc val (Foo f) = f val
Run Code Online (Sandbox Code Playgroud)
现在,这适用于任何返回与其输入相同类型的函数.假设我们想要一个为任何字符串添加感叹号的Foo函数:
fooShouter : Foo (String -> String)
fooShouter = Foo (\s -> s ++ "!")
Run Code Online (Sandbox Code Playgroud)
跑步runFooFunc "wow" fooShouter会回来"wow!".
现在,让我们分解Parser定义中发生的事情:
type Parser a b =
Parser (State a -> List (State b))
Run Code Online (Sandbox Code Playgroud)
请注意,Parser构造函数只是包装了一个类型的函数State a -> List (State b).不幸的是,State类型是不透明的(非导出的),所以我们不能直接编写代码,但你可以定义自己的状态并使用它.
不要过多地执行细节,请记住它只是某种类型函数的包装器.所以问题可能是,为什么这样写呢?
好吧,实现使分析Parsers的方式更容易隐藏实现细节,提供原始解析器的良好基础,允许优雅的方式分层解析器而不必担心状态.在处理解析器和解码器时,或者在围绕状态的任何事物中,通常会在函数式语言中看到这种类型的模式.
阅读Haskell内部状态monad的介绍可能会有所帮助.类型不同,但许多基本概念是共享的.