Begriff aus der funktionalen Programmierung gesucht

Es gibt doch sicher einen schönen (etablierten) Begriff dafür, dass man eine Funktion, die auf einzelne Elemente angewendet werden kann, auf eine Menge von Elementen anwendet (und dann auch eine Menge als Ergebnis erhält).

(Aber vielleicht ist das auch so „trivial“, dass es gar keinen Namen gibt?)

Das ist ähnlich zu dem, was mit einem stream.map(...) gemacht wird. Aber diesen Prozess an sich mit map zu bezeichnen passt ja dann nicht: Es wird ja nicht gemappt, sondern das map wird angewendet.

Im Endeffekt geht es um die Frage, was hier…

Function<S, T> function = ...;
Function<Collection<S>, Collection<T>> result = SENSIBLE_NAME(function);

anstelle von SENSIBLE_NAME stehen könnte. Ich dachte an forEach, lift, map, spread oder ähnliches, aber alles erscheint irgendwie unpassend :confused: Im Beispiel unten heißt die Funktion forEach, mit den zu erwartenden „Erweiterungen“, wenn man das auf höherstellige Funktionen verallgemeinern will.

Websuchen liefern immer entweder random crap, oder eben genau das, was mit dem Namen gemeint ist, den man mit in die Websuche aufnimmt, und was dann meistens nicht das ist, was man sucht, sondern das, was dieser Name eben bedeutet …

Hat jemand einen passenden Namen dafür?

package de.javagl.fungen.temp;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;


public class FunGenExample
{
    public static void main(String[] args)
    {
        forEach();
        forEach0();
        forEach1();
        forEachCartesian();
    }
    
    private static void forEach()
    {
        List<String> strings = Arrays.asList("0", "1", "2", "3");
        Function<String, Integer> function = Integer::parseInt;
        
        Function<Collection<String>, Collection<Integer>> result = forEach(function);
        
        Collection<Integer> results = result.apply(strings);
        System.out.println("forEach " + results);
    }
    
    private static void forEach0()
    {
        List<Integer> integers = Arrays.asList(0, 1, 2, 3);

        BiFunction<Integer, Integer, Integer> function = (i, j) -> i + j;
        
        BiFunction<Collection<Integer>, Integer, Collection<Integer>> result = forEach0(function);
        
        Collection<Integer> results = result.apply(integers, 123);
        System.out.println("forEach0 " + results);
    }
    
    private static void forEach1()
    {
        List<Integer> integers = Arrays.asList(0, 1, 2, 3);

        BiFunction<Integer, Integer, Integer> function = (i, j) -> i + j;
        
        BiFunction<Integer, Collection<Integer>, Collection<Integer>> result = forEach1(function);
        
        Collection<Integer> results = result.apply(123, integers);
        System.out.println("forEach1 " + results);
    }

    private static void forEachCartesian()
    {
        List<Integer> integers = Arrays.asList(0, 1, 2, 3);

        BiFunction<Integer, Integer, Integer> function = (i, j) -> i + j;
        
        BiFunction<Collection<Integer>, Collection<Integer>, Collection<Integer>> result = forEachCartesian(function);
        
        Collection<Integer> results = result.apply(integers, integers);
        System.out.println("forEachCartesian " + results);
    }
    
    
    
    private static <S, T> Function<Collection<S>, Collection<T>> forEach(
        Function<S, T> function)
    {
        return ss -> 
        {
            List<T> ts = new ArrayList<T>();
            for (S s : ss)
            {
                ts.add(function.apply(s));
            }
            return ts;
        };
    }
    
    private static <T, U, R> BiFunction<Collection<T>, U, Collection<R>> forEach0(
        BiFunction<T, U, R> function)
    {
        return (ts, u) -> forEach(curry1(function, u)).apply(ts); 
    }

    private static <T, U, R> BiFunction<T, Collection<U>, Collection<R>> forEach1(
        BiFunction<T, U, R> function)
    {
        return (t, us) -> forEach(curry0(function, t)).apply(us); 
    }

    private static <A, B, R> Function<B, R> curry0(
        BiFunction<? super A, ? super B, ? extends R> f, A a)
    {
        return b -> f.apply(a, b);
    }
    private static <A, B, R> Function<A, R> curry1(
        BiFunction<? super A, ? super B, ? extends R> f, B b)
    {
        return a -> f.apply(a, b);
    }
    
    private static <T, U, R> BiFunction<Collection<T>, Collection<U>, Collection<R>> forEachCartesian(
        BiFunction<T, U, R> function)
    {
        return (ts, us) -> 
        {
            List<R> rs = new ArrayList<R>();
            for (T t : ts)
            {
                for (U u : us)
                {
                    rs.add(function.apply(t, u));
                }
            }
            return rs;
        };
    }
    
    
}

intermediateDisclose? objectTransformation? weiterFälltMirNichtEin? :smile:

Edt: Type punning, Recasts?

Ich glaube nicht, dass es dafür einen Namen gibt. Es ist nicht map, sondern die Anwendung von map - da würde mir als Erstes curry einfallen:

const _ = require("lodash/fp");

const square = value => value * value;

const SENSIBLE_NAME = func => _.curry(_.map(func));

const squareMap = SENSIBLE_NAME(square);

const result = squareMap([1, 2, 3]);

console.log(result); // -> [1, 4, 9]

Erinnert irgendwie an einen Programmablauf. Nicht unbedingt wegen dem Objekt Function, sodnern mehr wegen dem apply. Vllt. passt ja Schrittkette oder Sequencer aus der SPS, nur dass es halt nicht gelooped ist.

@darekkay Nahh, curry kommt ja schon in dem Snippet vor, und das ist eher ~„eine n-ary function auf eine (n-1)-ary-function festpinnen“. Vielleicht hilft ein @ an @Landei ? :wink:

Ich würde „Funktor“ vorschlagen.

Dabei ist das „strukturerhaltend“ der wichtigste Teil. Damit ist gemeint, dass z.B. eine Liste nach Anwendung der Abbildung genausoviel Elemente wie vorher enthält. In der Kategorientheorie kann die Abbildung zwischen beliebigen Kategorien stattfinden, in der Programmierung bleibt normalerweise der „äußere“ Typ (wie „Liste“) erhalten, so dass es sich genaugenommen um „Endofunktoren“ handelt.

Während in der Programmierung oft die Datentypen (wie Liste) als Funktoren bezeichnet werden, ist das nicht ganz korrekt. In der Kategorientheorie sind die Datentypen Kategorien, und der Funktor die Abbildung zwischen diesen, also im Prinzip Ausprägung einer Funktion, an die aber bestimmte Anforderungen gestellt werden. Insbesondere müssen folgende Bedingungen erfüllt sein:

C.map(x -> x)  ==  C
C.map(x -> g(f(x)))  ==  C.map(y -> f(y)).map(z -> g(z))

Dummerweise lassen sich in Java zwar konkrete Funktoren schreiben, aber nicht zu einem Interface abstrahieren. Dazu müsste Java Typen höherer Ordnung zulassen (wie Scala oder Haskell), oder anders ausgedrückt, man müsste nicht nur List<X>, sondern auch X<String> schreiben können (wobei X dann z.B. List, Set u.s.w. sein könnte). Es gibt Tricks um das zu umgehen, aber ob man sich das antun will, muss jeder selbst entscheiden.

Die genannten Bedingungen ~„sollten“ gelten. Die Unschönheit, das ganze entweder auf Collection festpinnen zu müssen, oder Spezialisierungen für Set, List & Co anbieten zu müssen, hatte ich schon im Hinterkopf (wobei es eigentlich nur für List wirklich funktionieren kann - spätestens, wenn die Funktion alles auf das gleiche Element abbildet).

Der Name „Funktor“ ist wohl recht allgemein, und ich denke, zu allgemein für den Vorgang, der hier durchgeführt wird (auch wenn Kategorie-Theorie-Videos immernoch auf meiner TODO-Liste steht :roll_eyes: ).

Es wird ja aus einer Funktion eine andere gemacht: Aus einer Funktion f(x) = y wird eine Funktion f(X) = Y für Elemente x,y aus den (Teil)Mengen X,Y. Im Zweifel werde ich wohl bei forEach bleiben, aber ich hätte vermutet, dass es dafür einen Begriff gibt, und ich nur zu blöd bin, ihn zu finden…

Naja, was gefällt dir an dem Begriff map/mapping/mapper/subsetMapper nicht? Oder reduce, shrink, embody? Ich denke auch, einen „festen“/de-facto Fachbegriff aus der funktionalen Programmierung gibt es dafür nicht. Viele funktionale Programmiersprachen sind ja auch gar nicht typisiert… Hm. :cold_face:

Wenn man eine „normale“ Funktion auf einen neuen Kontext (Funktor, Applicative, Monade…) „anhebt“ (z.B. aus A -> B die neue Funktion List<A> -> List<B>), spricht man auch von „liften“. Die Funktionen dafür heißen nach Anzahl Argumente der Ausgangsfunktion meist lift, lift2, lift3 u.s.w

Ein Beispiel: https://hackage.haskell.org/package/utility-ht-0.0.14/docs/Control-Applicative-HT.html#v:lift

„lift“ sieht spontan nach dem richtigen Begriff aus. Ich muss zwar erst mehr Haskell-Doku lesen, um da sicher zu sein, aber es sieht so aus, als wäre es das gesuchte.