Не это, что [SelectMany][1] для?
enum1.SelectMany(
a => a.SelectMany(
b => b.SelectMany(
c => c.Select(
d => d.Name
)
)
)
);
Вы можете попробовать эту версию (используется reduce
из SRFI 1 ):
(define (compose . fns)
(define (make-chain fn chain)
(lambda args
(call-with-values (lambda () (apply fn args)) chain)))
(reduce make-chain values fns))
Это не ракетостроение : когда я разместил это на IRC-канале #scheme, Эли заметил, что это стандартная реализация compose
. :-) (В качестве бонуса он также хорошо работал с вашими примерами.)
Хотя было бы неплохо, если бы «пустой» список был передан функции идентификации, передача этого, похоже, приведет к следующему, что не так уж плохо:
(define compose-n
(lambda (first . rest)
(foldl compose first rest)))
((compose-n cdr cdr car) '(1 2 3))
((compose-n list identity identity) 1 2 3)
Проблема в том, что вы пытаетесь смешивать процедуры разной степени сложности. Вы, вероятно, захотите составить список, а затем сделать следующее:
(((compose-n (curry list) identity) 1) 2 3)
Но это не совсем удовлетворительно.
Вы можете рассмотреть n-арную функцию идентичности:
(define id-n
(lambda xs xs))
Затем вы можете создать процедуру compose специально для составления n-арной functions:
(define compose-nary
(lambda (f g)
(lambda x
(flatten (f (g x))))))
Составление произвольного числа n-мерных функций с помощью:
(define compose-n-nary
(lambda args
(foldr compose-nary id-n args)))
Что работает:
> ((compose-n-nary id-n list) 1 2 3)
(1 2 3)
EDIT: Это помогает мыслить в терминах типов. Давайте придумаем обозначение типов для наших целей. Мы обозначим тип пар как (A. B)
, а тип списков как [*]
, с условием, что [*]
эквивалентно (A. [*])
, где A
- это тип автомобиля
списка (т. е. список - это пара атома и списка). Далее обозначим функции как (A => B)
, что означает «принимает A и возвращает B». =>
и .
оба связаны справа, поэтому (A. B. C)
равно (A. (B. C))
.
Итак ... с учетом этого, вот тип списка
(читается ::
как «имеет тип»):
list :: (A . B) => (A . B)
И вот идентификатор:
identity :: A => A
Есть различие по типу. Тип list
состоит из двух элементов (т.е. тип списка имеет вид * => * => *
), а тип identity
состоит из одного type (тип удостоверения имеет вид * => *
).
Состав имеет этот тип:
compose :: ((A => B).(C => A)) => C => B
Посмотрите, что произойдет, когда вы примените составить
к списку
и идентификатору
.
объединяется с доменом функции list
, поэтому это должна быть пара (или пустой список, но мы это не будем рассматривать). C
объединяется с доменом функции identity
, поэтому он должен быть атомом. Таким образом, композиция этих двух элементов должна быть функцией, которая принимает атом C
и дает список B
. Это не проблема, если мы дадим этой функции только атомы, но если мы дадим ей списки, она задохнется, потому что ожидает только один аргумент.
Вот как помогает карри:
curry :: ((A . B) => C) => A => B => C
Применить карри
в список
, и вы увидите, что произойдет. Вход в список
объединяется с (A. Б)
. Результирующая функция принимает атом (автомобиль) и возвращает функцию. Эта функция, в свою очередь, принимает оставшуюся часть списка (cdr типа B
) и, наконец, возвращает список.
Важно отметить, что функция curried list
имеет тот же вид как идентификатор
, поэтому они могут быть составлены без проблем. Это работает и в обратном направлении. Если вы создаете функцию идентификации, которая принимает пары, ее можно составить с помощью обычной функции list
.
list
. так что их можно составить без проблем. Это работает и в обратном направлении. Если вы создаете функцию идентификации, которая принимает пары, ее можно составить с помощью обычной функции list
. The OP mentioned (in a comment to my answer) that his implementation of Scheme does not have call-with-values
. Here's a way to fake it (if you can ensure that the
symbol is never otherwise used in your program: you can replace it with (void)
, (if #f #f)
, or whatever you like that's not used, and that's supported by your implementation):
(define (values . items)
(cons '<values> items))
(define (call-with-values source sink)
(let ((val (source)))
(if (and (pair? val) (eq? (car val) '<values>))
(apply sink (cdr val))
(sink val))))
What this does is that it fakes a multi-value object with a list that's headed by the
symbol. At the call-with-values
site, it checks to see if this symbol is there, and if not, it treats it as a single value.
If the leftmost function in your chain can possibly return a multi-value, your calling code has to be prepared to unpack the
-headed list. (Of course, if your implementation doesn't have multiple values, this probably won't be of much concern to you.)