Lisp 中的选项类型编码/健壮性
Option type encoding / robustness in Lisp
(define (nth n lst)
(if (= n 1)
(car lst)
(nth (- n 1)
(cdr lst) )))
是不安全的偏函数,n
可能超出范围。 error
可能会有帮助,
(define (nth n lst)
(if (null? lst)
(error "`nth` out of range")
(if (= n 1)
(car lst)
(nth (- n 1)
(cdr lst) ))))
但是与 Haskell 的 Maybe
数据类型类似的 稳健 方案会是什么样子?
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
只返回 '()
就足够了吗?
(define (nth n lst)
(if (null? lst) '()
(if (= n 1)
(car lst)
(nth (- n 1)
(cdr lst) ))))
很容易打破你的尝试。只需创建一个包含空列表的列表:
(define lst '((1 2) () (3 4)))
(nth 2 lst)
-> ()
(nth 100 lst)
-> ()
您遗漏的关键点是 Haskell 的 Maybe
不只是 return 存在时的裸值,它包含该值。如您所说,Haskell 定义 Maybe
如下:
data Maybe a = Nothing | Just a
不是这样的:
data Maybe a = Nothing | a
后者相当于你正在做的事情。
为了获得正确 Maybe
的大部分方法,如果元素不存在,您可以 return 空列表,就像您一样,但也 如果元素确实存在,则将 return 值包装在另一个列表中:
(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)
-> ()
另一种表示未找到匹配元素的方法是使用多个 return 值:
(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 ...)
作为一个非 lisper 我真的不能说这是多么地道,但你可以 return 选项类型的 Church encoding:
(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
本身添加一个默认参数。
有几种方法可以做到这一点。
直接等同于模仿 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
(define (nth n lst)
(if (= n 1)
(car lst)
(nth (- n 1)
(cdr lst) )))
是不安全的偏函数,n
可能超出范围。 error
可能会有帮助,
(define (nth n lst)
(if (null? lst)
(error "`nth` out of range")
(if (= n 1)
(car lst)
(nth (- n 1)
(cdr lst) ))))
但是与 Haskell 的 Maybe
数据类型类似的 稳健 方案会是什么样子?
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
只返回 '()
就足够了吗?
(define (nth n lst)
(if (null? lst) '()
(if (= n 1)
(car lst)
(nth (- n 1)
(cdr lst) ))))
很容易打破你的尝试。只需创建一个包含空列表的列表:
(define lst '((1 2) () (3 4)))
(nth 2 lst)
-> ()
(nth 100 lst)
-> ()
您遗漏的关键点是 Haskell 的 Maybe
不只是 return 存在时的裸值,它包含该值。如您所说,Haskell 定义 Maybe
如下:
data Maybe a = Nothing | Just a
不是这样的:
data Maybe a = Nothing | a
后者相当于你正在做的事情。
为了获得正确 Maybe
的大部分方法,如果元素不存在,您可以 return 空列表,就像您一样,但也 如果元素确实存在,则将 return 值包装在另一个列表中:
(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)
-> ()
另一种表示未找到匹配元素的方法是使用多个 return 值:
(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 ...)
作为一个非 lisper 我真的不能说这是多么地道,但你可以 return 选项类型的 Church encoding:
(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
本身添加一个默认参数。
有几种方法可以做到这一点。
直接等同于模仿 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