一对一
你的例子包括(map (lambda (x) x) ...)
(map (lambda (ignore-me)
(map (lambda (x) x) ; <- no-op
'(1 2 3)))
'(1))
((1 2 3))
“很难看到”,因为内部 map 本质上是一个空操作。 map 在输入和输出之间创建 1:1 关系,对映射过程的作用没有任何意见。如果映射过程返回单个元素、列表或非常嵌套的列表,则无关紧要 -
(map (lambda (ignore-me)
'(1 2 3))
'(foo))
((1 2 3))
(map (lambda (_)
'(((((really nested))))))
'(1 2 3))
((((((really nested)))))
(((((really nested)))))
(((((really nested))))))
或者使用一些 ascii 可视化 -
(define (fn x) (+ x x))
(map fn '(1 2 3))
; \ \ \
; \ \ (fn 3)
; \ \ \
; \ (fn 2) \
; \ \ \
; (fn 1) \ \
; \ \ \
; output: '(2 4 6)
你是否应该选择将map里面的映射程序到另一个map调用-
(map (lambda (...)
(map ...)) ; <- nested map
...)
同样的规则适用。每个map 输出与其输入列表具有1:1 关系。我们可以想象map的类型以获得更多洞察力-
map : ('a -> 'b, 'a list) -> 'b list
-------- ------- -------
\ \ \_ returns a list of type 'b
\ \
\ \__ input list of type 'a
\
\__ mapping procedure, maps
one element of type 'a
to one element of type 'b
实施地图
作为一项学习练习,实施map 有助于深入了解其工作原理 -
(define (map fn lst)
(if (null? lst)
'()
(cons (fn (car lst))
(map fn (cdr lst)))))
(map (lambda (x)
(list x x x x x))
'(1 2 3))
((1 1 1 1 1)
(2 2 2 2 2)
(3 3 3 3 3))
如您所见,map 对lst 中的元素类型或fn 的返回值是什么没有意见。 map 将列表的car 传递给fn 和cons'es 到列表的cdr 的递归结果。
附加地图
我们应该查看的另一个相关过程是append-map -
append-map : ('a -> 'b list, 'a list) -> 'b list
------------- ------- -------
\ \ \_ returns a list of type 'b
\ \_ input list of type 'a
\
\_ mapping function, maps
one element of type 'a
to a LIST of elements of type 'b
这里唯一的区别是映射过程应该返回一个元素的列表,而不仅仅是一个元素。这样,append-map 在输入列表和输出列表之间创建了 1:many 关系。
(append-map (lambda (x)
(list x x x x x))
'(1 2 3))
(1 1 1 1 1 2 2 2 2 2 3 3 3 3 3)
append-map 的这一特性是它有时被称为flatmap 的原因,因为它“扁平化”了一层嵌套 -
(append-map (lambda (x)
(map (lambda (y)
(list x y))
'(spade heart club diamond)))
'(J Q K A))
((J spade)
(J heart)
(J club)
(J diamond)
(Q spade)
(Q heart)
(Q club)
(Q diamond)
(K spade)
(K heart)
(K club)
(K diamond)
(A spade)
(A heart)
(A club)
(A diamond))
为读者准备的后续练习:
- 如果我们在上面的示例中将
append-map 换成map,输出会是什么?
- 以另一种或两种方式定义
map。验证正确的行为
- 根据
map 定义append-map。不使用map 再次定义它。
松散的打字
Scheme 是一种无类型语言,因此列表的内容不需要是同质的。尽管如此,以这种方式考虑 map 和 append-map 还是很有用的,因为类型有助于传达函数的行为方式。以下是 Racket 提供的更准确的类型定义 -
(map proc lst ...+) → list?
proc : procedure?
lst : list?
(append-map proc lst ...+) → list?
proc : procedure?
lst : list?
这些要松散得多,并且确实反映了您可以实际编写的松散程序。比如——
(append-map (lambda (x)
(list 'a-symbol "hello" 'float (* x 1.5) 'bool (> x 1)))
'(1 2 3))
(a-symbol "hello" float 1.5 bool #f a-symbol "hello" float 3.0 bool #t a-symbol "hello" float 4.5 bool #t)
variadic map 和 append-map
你在上面的类型中看到那些...+ 了吗?为了简单起见,到目前为止,我一直在隐藏一个重要的细节。 map 的可变参数接口意味着它可以接受 1 个或更多输入列表 -
(map (lambda (x y z)
(list x y z))
'(1 2 3)
'(4 5 6)
'(7 8 9))
((1 4 7) (2 5 8) (3 6 9))
(append-map (lambda (x y z)
(list x y z))
'(1 2 3)
'(4 5 6)
'(7 8 9))
(1 4 7 2 5 8 3 6 9)
为读者准备的后续练习:
- 定义可变参数
map
- 定义可变参数
append-map
一点点 lambda 演算
您了解 lambda 演算。您知道(lambda (x y z) (list x y z) 与list 相同吗?这被称为Eta reduction -
(map list '(1 2 3) '(4 5 6) '(7 8 9))
((1 4 7) (2 5 8) (3 6 9))
(append-map list '(1 2 3) '(4 5 6) '(7 8 9))
(1 4 7 2 5 8 3 6 9)
分隔延续
Remember when 我们谈到了delimited continuations 和运营商shift 和reset?了解了append-map,再来看看amb,ambiguous choice operator-
(define (amb . lst)
(shift k (append-map k lst)))
我们可以像这样使用amb -
(reset (list (amb 'J 'Q 'K 'A) (amb '♡ '♢ '♤ '♧)))
(J ♡ J ♢ J ♤ J ♧ Q ♡ Q ♢ Q ♤ Q ♧ K ♡ K ♢ K ♤ K ♧ A ♡ A ♢ A ♤ A ♧)
(reset (list (list (amb 'J 'Q 'K 'A) (amb '♡ '♢ '♤ '♧))))
((J ♡) (J ♢) (J ♤) (J ♧) (Q ♡) (Q ♢) (Q ♤) (Q ♧) (K ♡) (K ♢) (K ♤) (K ♧) (A ♡) (A ♢) (A ♤) (A ♧))
在一个更有用的例子中,我们使用pythagorean theorem to find-right-triangles -
(define (pythag a b c)
(= (+ (* a a) (* b b)) (* c c))) ; a² + b² = c²
(define (find-right-triangles . side-lengths)
(filter ((curry apply) pythag)
(reset (list
(list (apply amb side-lengths)
(apply amb side-lengths)
(apply amb side-lengths))))))
(find-right-triangles 1 2 3 4 5 6 7 8 9 10 11 12)
((3 4 5) (4 3 5) (6 8 10) (8 6 10))
定界延续有效地把你的程序从里到外,让你专注于声明性结构 -
(define (bit)
(amb #\0 #\1))
(define (4-bit)
(reset (list (string (bit) (bit) (bit) (bit)))))
(4-bit)
("0000" "0001" "0010" "0011" "0100" "0101" "0110" "0111" "1000" "1001" "1010" "1011" "1100" "1101" "1110" "1111")
amb 可以在任何表达式中的任何位置使用,以对每个提供的值计算一次表达式。这种无限的使用可以产生非凡的结果-
(reset
(list
(if (> 5 (amb 6 3))
(string-append "you have "
(amb "dark" "long" "flowing")
" "
(amb "hair" "sentences"))
(string-append "please do not "
(amb "steal" "hurt")
" "
(amb "any" "other")
" "
(amb "people" "animals")))))
("please do not steal any people"
"please do not steal any animals"
"please do not steal other people"
"please do not steal other animals"
"please do not hurt any people"
"please do not hurt any animals"
"please do not hurt other people"
"please do not hurt other animals"
"you have dark hair"
"you have dark sentences"
"you have long hair"
"you have long sentences"
"you have flowing hair"
"you have flowing sentences")