objective c - Usando Bloques Objective-C
coding-style (3)
Hoy experimenté con los bloques de Objective-C, así que pensé que sería inteligente y agregaré a NSArray algunos métodos de colección de estilo funcional que he visto en otros idiomas:
@interface NSArray (FunWithBlocks)
- (NSArray *)collect:(id (^)(id obj))block;
- (NSArray *)select:(BOOL (^)(id obj))block;
- (NSArray *)flattenedArray;
@end
El método collect: toma un bloque al que se llama para cada elemento de la matriz y se espera que devuelva los resultados de alguna operación utilizando ese elemento. El resultado es la recopilación de todos esos resultados. (Si el bloque devuelve nulo, no se agrega nada al conjunto de resultados).
El método select: devolverá una nueva matriz con solo los elementos del original que, cuando se pasa como un argumento al bloque, el bloque devuelve SÍ.
Y, finalmente, el método flattenedArray itera sobre los elementos de la matriz. Si un elemento es una matriz, recursivamente llama flattenedArray en él y agrega los resultados al conjunto de resultados. Si el elemento no es una matriz, agrega el elemento al conjunto de resultados. El conjunto de resultados se devuelve cuando todo está terminado.
Así que ahora que tenía algo de infraestructura, necesitaba un caso de prueba. Decidí encontrar todos los archivos de paquetes en los directorios de aplicaciones del sistema. Esto es lo que se me ocurrió:
NSArray *packagePaths = [[[NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES) collect:^(id path) { return (id)[[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil] collect:^(id file) { return (id)[path stringByAppendingPathComponent:file]; }]; }] flattenedArray] select:^(id fullPath) { return [[NSWorkspace sharedWorkspace] isFilePackageAtPath:fullPath]; }];
Sí, eso es todo una línea y es horrible. Intenté algunos enfoques para agregar nuevas líneas y sangría para intentar limpiarlo, pero aún siento que el algoritmo real se ha perdido en todo el ruido. Sin embargo, no sé si es solo una cuestión de sintaxis o mi experiencia relativa con el uso de un estilo funcional.
Para comparar, decidí hacerlo "a la manera antigua" y simplemente usar bucles:
NSMutableArray *packagePaths = [NSMutableArray new];
for (NSString *searchPath in NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES)) {
for (NSString *file in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:searchPath error:nil]) {
NSString *packagePath = [searchPath stringByAppendingPathComponent:file];
if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:packagePath]) {
[packagePaths addObject:packagePath];
}
}
}
OMI esta versión fue más fácil de escribir y es más legible para iniciar.
Supongo que es posible que este sea de alguna manera un mal ejemplo, pero me parece una forma legítima de usar bloques. (¿Me equivoco?) ¿Me falta algo sobre cómo escribir o estructurar el código de Objective-C con bloques que lo limpiarían y lo dejarían más claro (o incluso tan claro como) la versión en bucle?
Creo que el problema es que (al contrario de lo que afirman los críticos de Python;) el espacio en blanco es importante. En un estilo más funcional, parece que tendría sentido copiar el estilo de otros lenguajes funcionales. La forma más LISP-y de escribir tu ejemplo podría ser algo como:
NSArray *packagePaths = [[[NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES)
collect:^(id path) {
return [[[NSFileManager defaultManager]
contentsOfDirectoryAtPath:path
error:nil]
collect:^(id file) {
return [path stringByAppendingPathComponent:file];
}
];
}
]
flattenedArray
]
select:^(id fullPath) {
return [[NSWorkspace sharedWorkspace] isFilePackageAtPath:fullPath];
}
];
Yo no diría que esto es más claro que la versión en bucle. Al igual que cualquier otra herramienta, los bloques son una herramienta y deben usarse solo cuando son la herramienta adecuada para el trabajo. Si la legibilidad sufre, diría que no es la mejor herramienta para el trabajo. Los bloques son, después de todo, una adición a un lenguaje fundamentalmente imperativo. Si realmente desea la concisión de un lenguaje funcional, use un lenguaje funcional.
Parece que estás reinventando la mensajería de alto orden. Marcel Weiher ha realizado un extenso trabajo en HOM en Objective-C, que puede encontrar aquí:
http://www.metaobject.com/blog/labels/Higher%20Order%20Messaging.html
Use nuevas líneas y divida su llamada en múltiples líneas.
El patrón estándar utilizado en todas las API de Apple es que un método o función solo debe tomar un argumento de bloque y ese argumento siempre debe ser el último argumento.
Lo que has hecho. Bueno.
Ahora, al escribir el código que usa dicha API, haga algo como:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES);
paths = [paths collect: ^(id path) {
...
}];
paths = [paths collect: ^(id path) {
...
}];
paths = [paths select: ^(id path) {
...
}];
Es decir, realice cada paso de su recopilación / selección / filtro / aplanamiento / mapa / lo que sea como un paso separado. Esto no será más rápido / lento que las llamadas a métodos encadenados.
Si necesita anidar bloques en el lado de los bloques, hágalo con sangría completa:
paths = [paths collect: ^(id path) {
...
[someArray select:^(id path) {
...
}];
}];
Al igual que anidado si las declaraciones o similares. Cuando se vuelve demasiado complejo, refactorícelo en funciones o métodos, según sea necesario.