2
(define (nth n lst)
  (if (= n 1)
    (car lst)
    (nth (- n 1)
         (cdr lst) )))

is an unsafe partial function, n may go out of range. An error can be helpful,

(define (nth n lst)
  (if (null? lst)
    (error "`nth` out of range")
    (if (= n 1)
      (car lst)
      (nth (- n 1)
           (cdr lst) ))))

But what would a robust Scheme analogue to Haskell's Maybe data type look like?

data Maybe a = Nothing | Just a

nth :: Int -> [a] -> Maybe a
nth _ []       = Nothing
nth 1 (x : _)  = Just x
nth n (_ : xs) = nth (n - 1) xs

Is just returning '() adequate?

(define (nth n lst)
  (if (null? lst) '()
    (if (= n 1)
      (car lst)
      (nth (- n 1)
           (cdr lst) ))))
4

4 回答 4

6

很容易破坏你的尝试。只需创建一个包含空列表的列表:

(define lst '((1 2) () (3 4)))
(nth 2 lst)
-> ()
(nth 100 lst)
-> ()

您缺少的关键点是 Haskell'sMaybe不会在存在时简单地返回裸值,它会包装该值。正如您所说,Haskell 定义Maybe如下:

data Maybe a = Nothing | Just a

不是这样的:

data Maybe a = Nothing | a

后者相当于你正在做的事情。

为了获得正确的大部分方法,Maybe如果元素不存在,您可以返回一个空列表,但如果元素确实存在,也可以将返回值包装在另一个列表中:

(define (nth n lst)
  (if (null? lst) '()
    (if (= n 1)
      (list (car lst)) ; This is the element, wrap it before returning.
      (nth (- n 1)
           (cdr lst) ))))

这样,您的结果将是一个空列表,这意味着该元素不存在,或者一个只有一个元素的列表:您要求的元素。重用上面的同一个列表,我们可以区分空列表和不存在的元素:

(define lst '((1 2) () (3 4)))
(nth 2 lst)
-> (())
(nth 100 lst)
-> ()
于 2017-04-04T16:56:18.030 回答
3

另一种表示未找到匹配元素的方法是使用多个返回值:

(define (nth n ls)
  (cond
    ((null? ls) (values #f #f))
    ((= n 1) (values (car ls) #t))
    (else (nth (- n 1) ls))))

这是以对该功能的用户来说有点麻烦为代价的,因为他们现在必须做一个

(call-with-values (lambda () (nth some-n some-list))
                  (lambda (element found?)
                     ... whatever ...))

但这可以通过使用一些仔细的宏观来缓解。R7RS 指定let-values语法。

(let-values (((element found?) (nth some-n some-list)))
   ... whatever ...)
于 2017-04-04T17:09:03.000 回答
2

有几种方法可以做到这一点。

直接等价于模仿 Miranda 版本:

#!r6rs
(library (sylwester maybe) 
  (export maybe nothing maybe? nothing?)
  (import (rnrs base))

  ;; private tag 
  (define tag-maybe (list 'maybe)) 

  ;; exported tag and features
  (define nothing (list 'nothing))

  (define (maybe? v)
    (and (pair? v) 
         (eq? tag-maybe (car v))))

  (define (nothing? v)
    (and (maybe? v)
         (eq? nothing (cdr v))))

  (define (maybe v)
    (cons tag-maybe v)))

如何使用它:

#!r6rs
(import (rnrs) (sylwester maybe))
(define (nth n lst)
  (cond ((null? lst) (maybe nothing))
        ((zero? n) (maybe (car lst)))
        (else (nth (- n 1) (cdr lst)))))

(nothing? (nth 2 '()))
; ==> #t

例外

(define (nth n lst)
  (cond ((null? lst) (raise 'nth-nothing))
        ((zero? n) (car lst))
        (else (nth (- n 1) (cdr lst)))))


(guard (ex
        ((eq? ex 'nth-nothing)
         "nothing-value"))
  (nth 1 '())) ; ==> "nothing-value"

默认值:

(define (nth n lst nothing)
  (cond ((null? lst) nothing)
            ((zero? n) (car lst))
            (else (nth (- n 1) (cdr lst)))))

(nth 1 '() '()) 
; ==> '()

源自过程的默认值

(define (nth index lst pnothing)
  (cond ((null? lst) (pnothing))
        ((zero? n) (car lst))
        (else (nth (- n 1) (cdr lst)))))

(nth 1 '() (lambda _ "list too short")) 
; ==> "list too short"

异常和默认过程的组合

Racket 是一个不错的 Scheme,通常有一个默认值选项,默认为异常或过程 thunk。可以模仿这种行为:

(define (handle signal rest)
  (if (and (not (null? rest))
           (procedure? (car rest)))
      ((car rest))
      (raise signal)))

(define (nth n lst . nothing)
  (cond ((null? lst) (handle 'nth-nothing nothing)) 
        ((zero? n) (car lst))
        (else (nth (- n 1) (cdr lst)))))

(nth 1 '() (lambda () 5)) ; ==> 5
(nth 1 '()) ; exception signalled
于 2017-04-05T08:51:21.410 回答
1

作为一个非 lisper,我真的不能说这是多么地道,但你可以返回一个选项类型的Church 编码:

(define (nth n ls)
  (cond
    ((null? ls) (lambda (default f) default))
    ((= n 1) (lambda (default f) (f (car ls))))
    (else (nth (- n 1) ls))))

但这与@Dirk 的提议一样复杂。我个人更喜欢为其nth自身添加一个默认参数。

于 2017-04-04T18:34:03.717 回答