【问题标题】:Pattern matching with streams in Racket?与球拍中的流模式匹配?
【发布时间】:2020-06-19 12:32:14
【问题描述】:

模式匹配是在 let 子句中手动解构的一种非常易读的替代方法。我想知道是否可以将它用于流,就像我们可以用于列表一样。

作为一个例子,考虑一个简单的add-between 实现来生成一个序列,其中在原始序列的每个元素之间插入一个元素。

(define (add-between lst sep)
  (match lst
    ['() '()]
    [(cons v vs)
     (cons v
           (cons sep
                 (add-between vs sep)))]))

用法:

(add-between (list 'a 'b 'c) 'and) ; => '(a and b and c and)

如果我们想对任意序列(如流)做同样的事情怎么办?

(define (add-between seq sep)
  (match seq
    ['() '()]
    [(cons v vs)
     (stream-cons v
                  (stream-cons sep
                               (add-between vs sep)))]))

这会产生错误:

(add-between (stream 'a 'b 'c) 'and)
; match: no matching clause for #<stream>
; stdin:1:1
; Context:
;  /Applications/Racket v7.5/collects/racket/match/runtime.rkt:24:0 match:error
;  /Applications/Racket v7.5/collects/racket/repl.rkt:11:26

如果我们可以对通用序列类型进行模式匹配,那将是理想的,因为这将封装列表和流。

【问题讨论】:

    标签: stream pattern-matching racket


    【解决方案1】:

    您可以为流定义自己的模式匹配形式,将define-match-expander? 与谓词一起使用,app 与访问器一起使用。

    #lang racket
    (require syntax/parse/define (prefix-in * racket/stream))
    (define (stream-empty? v) (and (stream? v) (*stream-empty? v)))
    (define (stream-cons? v) (and (stream? v) (not (*stream-empty? v))))
    
    (define-match-expander stream-cons
      (syntax-parser
        [(_ fst rst) #'(? stream-cons? (app stream-first fst) (app stream-rest rst))])
      (syntax-parser
        [(_ fst rst) #'(*stream-cons fst rst)]))
    
    (define-match-expander stream*
      (syntax-parser
        [(_ rst) #'rst]
        [(_ fst more ... rst) #'(stream-cons fst (stream* more ... rst))])
      (syntax-parser
        [(_ init ... rst) #'(*stream* init ... rst)]))
    
    (define-match-expander stream
      (syntax-parser
        [(_) #'(? stream-empty?)]
        [(_ elem ...) #'(stream* elem ... (? stream-empty?))])
      (syntax-parser
        [(_ elem ...) #'(*stream elem ...)]))
    

    使用它:

    > (define (add-between seq sep)
        (match seq
          [(stream) (stream)]
          [(stream-cons v vs)
           (stream-cons v
                        (stream-cons sep
                                     (add-between vs sep)))]))
    > (add-between (stream 'a 'b 'c) 'and)
    #<stream>
    > (stream->list (add-between (stream 'a 'b 'c) 'and))
    '(a and b and c and)
    

    【讨论】:

    • 能够扩展匹配模式听起来很完美。您上面的代码将专门用于流,但似乎应该直接修改它以支持任意序列,例如球拍序列或数据/收集序列。这太棒了!
    • 我不确定任意序列。流之所以如此出色,是因为它们与普通列表一样基于类似cons 的结构,并且并非所有序列都可以方便地进行模式匹配
    【解决方案2】:

    @AlexKnauthanswer 很有启发性。从那以后我还发现data/collection 库提供了一个built-in way 来为任意序列(即定义为here 的序列)执行此操作。使用这种匹配模式,问题中的代码可以针对通用序列编写为:

    (require data/collection)
    
    (define (add-between seq sep)
      (match seq
        [(sequence) empty-stream]
        [(sequence v vs ...)
         (stream-cons v
                      (stream-cons sep
                                   (add-between vs sep)))]))
    

    使用它:

    (sequence->list (add-between (stream 'a 'b 'c) 'and))
    => '(a and b and c and)
    
    (sequence->string (add-between "hello" #\space))
    "h e l l o "
    

    【讨论】:

    • 我们可能还需要一个嵌套匹配来防止在空流之前添加sep。与问题中的列表相同。当然,这只是细节。
    • @WillNess 你的意思是处理单例情况,所以(add-between '(1) 'a) =&gt; '(1) 而不是'(1 a)?是的,我们需要一个额外的(sequence v) (stream v) 基本情况。
    猜你喜欢
    • 2023-03-21
    • 2014-07-06
    • 2016-07-13
    • 1970-01-01
    • 2016-02-17
    • 2012-10-14
    • 2021-09-14
    • 2011-10-27
    • 2019-05-02
    相关资源
    最近更新 更多