Permalänk
Medlem

Hjälp med en uppgift i Java

Hej!

Har börjat läsa systemvetenskap och har nu börjat påkursen mjukvaruutveckling. Är helt ny inom programmering och behöver lite hjälp med en uppgift som lyder:

Skriv ett program som beräknar kvadratmeterpriset per månad för en lägenhet. Programmet
skall läsa in lägenhetsinnehavarens namn, lägenhetens storlek samt månadshyra. Programmet
skall därefter:
 beräkna kvadratmeterpriset per månad.
 ändra personens namn till versaler.
 skriva ut namn samt att kvadratmeterpriset per månad i kr. Använd klassen
DecimalFormat. Kvadratmeterpriset per månad ska bestå av två decimaler.

Inga metodanrop får ske inne i System.out.print- satsen

Ett exempel på hur ditt program skall uppträda (kursivt och fet = inmatat värde):
Ange ägarens namn: Rolke
Ange din månadshyra: 3550
Ange lägenhetens storlek i kvadratmeter: 45
ROLKE du betalar 78,89 kr per kvadratmeter i månaden.

Förstår inte riktigt vad läraren menar med att Inga metodanrop får ske inne i System.out.print- satsen.

Hur skulle ni göra för att lösa denna uppgiften?

Tack!

Permalänk
Medlem

Tjena!

Kul att du börjat läsa systemvetenskap! Jag läste datavetenskap 2010-2013 och har jobbat som utvecklare sedan dess.

Normalt sett här på forumet så får du inte hjälp att lösa själva uppgiften utan den behöver du ta dig ann själv. Det är bättre att du istället frågar specifika saker gällande programmering eller koncept som vi kan svara på. Detta är för att man inte ska kunna låta andra göra uppgiften åt en.

När läraren skriver "Inga metodanrop får ske inne i System.out.print -satsen" så menar den tex.
Tillåtet: System.out.print("Ange ägarens namn: " + ownerName);
Int tillåtet: System.out.print("Ange ägarens namn: " + getOwnerNameInput());
I detta fall så är GetOwnerNameInput en metod (kallas ibland funktion) som innehåller kod som läser in text från konsollen och sedan skickar tillbaka texten som du vill skriva ut.

För mer information om metoder i Java se:
https://www.youtube.com/watch?v=7MBgaF8wXls&list=PLFE2CE09D83...

Notera att klippet är del av en videoserie. Om det är bitar du inte förstår så rekommenderar jag att du tittar på de andra klippen innan. Det kommer ge dig en jättebra grund att bygga på på dina föreläsningar.

Permalänk
Inaktiv

Finns det någon vits med kommentaren läraren gjorde att "Inga metodanrop får ske inne i System.out.print- satsen"? Det är ju inte så att det blir en enklare lösning rent teknisk.
Jag tänker mig "one line solution" på TS problem,vilket går att göra i många språk.

Annars är uppgiften typ första övningen i boken och det är bara se på exemplen. Om du har en kas bok så skaffa denna: https://www.bokus.com/bok/9789144104317/java-direkt-med-swing...

Permalänk
Medlem

Om jag var du skulle jag försöka bryta ner uppgiften i flera delar och hantera de för sig själv.

Skriv ett program som beräknar kvadratmeterpriset per månad för en lägenhet. Programmet
skall läsa in lägenhetsinnehavarens namn, lägenhetens storlek samt månadshyra. Programmet
skall därefter:
 beräkna kvadratmeterpriset per månad.
 ändra personens namn till versaler.
 skriva ut namn samt att kvadratmeterpriset per månad i kr. Använd klassen
DecimalFormat. Kvadratmeterpriset per månad ska bestå av två decimaler.

En tänkbar lösning är att du först försöker få någon input från användaren och sparar den. Därefter göra en beräkning på den datan och tillslut skriva ut den till skärmen igen.

Permalänk
Medlem
Permalänk
Medlem
Skrivet av sau_ahh:

Lite väl mycket hjälp kan man tycka.

Visa signatur

i7 4790k | Asus Z97M-Plus | 16gb ram | Asus Strix 970 | Phanteks PH-TC12DX | Samsung 850 Evo 250gb | Corsair Force GS 120gb | Fractal Design Integra M 650w | Fractal Design Define Mini C

Permalänk
Medlem
Skrivet av Snorlena:

Lite väl mycket hjälp kan man tycka.

Han undra ju hur vi/jag skulle lösa den ^^

Permalänk
Medlem
Skrivet av 2infinity:

Tjena!

Kul att du börjat läsa systemvetenskap! Jag läste datavetenskap 2010-2013 och har jobbat som utvecklare sedan dess.

Normalt sett här på forumet så får du inte hjälp att lösa själva uppgiften utan den behöver du ta dig ann själv. Det är bättre att du istället frågar specifika saker gällande programmering eller koncept som vi kan svara på. Detta är för att man inte ska kunna låta andra göra uppgiften åt en.

När läraren skriver "Inga metodanrop får ske inne i System.out.print -satsen" så menar den tex.
Tillåtet: System.out.print("Ange ägarens namn: " + ownerName);
Int tillåtet: System.out.print("Ange ägarens namn: " + getOwnerNameInput());
I detta fall så är GetOwnerNameInput en metod (kallas ibland funktion) som innehåller kod som läser in text från konsollen och sedan skickar tillbaka texten som du vill skriva ut.

För mer information om metoder i Java se:
https://www.youtube.com/watch?v=7MBgaF8wXls&list=PLFE2CE09D83...

Notera att klippet är del av en videoserie. Om det är bitar du inte förstår så rekommenderar jag att du tittar på de andra klippen innan. Det kommer ge dig en jättebra grund att bygga på på dina föreläsningar.

Tack så mycket! Borde läst/förstått att jag inte skulle bett om en hel lösning, är helt ny på sweclockers också, din förklaring gjorde klarade upp instruktionerna väldigt mycket så stort tack för det

Videoserien verkar vara sjukt bra så de ska jag definitivt kolla på

Skickades från m.sweclockers.com

Permalänk
Medlem
Skrivet av sau_ahh:

Tack så jättemycket! Detta sätter några bitar på plats i skallen!

Skickades från m.sweclockers.com

Permalänk
99:e percentilen
Skrivet av sau_ahh:

Kan nog inte riktigt rekommendera TS att ta efter detta exempel. Framförallt undrar jag varför du har instansvariablerna hyra, storlek, namn, res och priskvm. Det saknas också all form av hantering av felaktigt inmatad data.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
99:e percentilen
Skrivet av xReeLa:

 beräkna kvadratmeterpriset per månad.
 ändra personens namn till versaler.

Där har du två klockrena tillfällen att öva på att skriva rena funktioner! En ren funktion (som av en matematiker enbart skulle kallas funktion) är en mappning från input till output, och inget annat. Den läser inte av några muterbara variabler och den modifierar inget state eller gör någon I/O (till exempel System.out.println). Ger du den samma argument får du alltid tillbaka samma svar. Allt detta gör den väldigt pålitlig, lätt att förstå, enkel att flytta eller förbättra samt använda tillsammans med annan kod.

I Java implementeras rena funktioner som vanliga metoder, och språket har inget sätt att skilja mellan rena funktioner och metoder som inte är sådana, utan det ansvaret ligger helt på programmeraren. För att hjälpa dig på traven kan jag bidra med typsignaturer för tänkbara representationer av ovanstående mappningar:

public static double monthlyRentPerSquareMeter(double monthlyRent, int squareMeters) {} public static String uppercase(String s) {}

Åtminstone den sistnämnda kan absolut sägas vara overkill att skapa en funktion för, med tanke på hur simpel dess implementation kommer vara; tanken är framförallt att demonstrera hur en ren funktion kan se ut.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem

@Alling:

public static String uppercase(String s) { return s.toUpperCase(); }

Känns väl inte overkill? Jag föreslår att vi implementerar en funktion som används för att kalla på den här funktionen med så vi tar till oss en av OOP's grundpelare: Inkapsling!

(Kan slänga in att jag inte är säker på OOP's grundpelare och hur dom var tänkt att användas)

Permalänk
99:e percentilen
Skrivet av Sholdar:

@Alling:

public static String uppercase(String s) { return s.toUpperCase(); }

Känns väl inte overkill? Jag föreslår att vi implementerar en funktion som används för att kalla på den här funktionen med så vi tar till oss en av OOP's grundpelare: Inkapsling!

(Kan slänga in att jag inte är säker på OOP's grundpelare och hur dom var tänkt att användas)

Inkapsling har ingenting att göra med ditt skämtsamma förslag, tyvärr.

Som sagt, det kan absolut sägas vara overkill att skapa en public static String uppercase(String s), men den är inte helt redundant. Den har nämligen (den i TS fall irrelevanta, och generellt inte så särkilt stora) fördelen att den kan användas som argument till en högre ordningens funktion, utan en extra funktionsabstraktion à la x -> x.toUpperCase().

EDIT: Som @jclr visade nedan kan även instansmetoder rakt av användas som argument till högre ordningens funktioner.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem

Att försöka skriva java med bara referentiellt transparenta ("rena") funktioner är krångligt och kommer resultera i svårläst kod. Fördelarna med "funktionell programmering" och "rena" funktioner visa sig först vid lite större program. Det här programmet är så kort att man kan ha hela koden i huvudet och dessutom består det i stort sett bara av sidoeffekter.

Eftersom det verkar vara första uppgiften så har man antagligen inte gått igenom det som behövs för att hantera felaktigt inmatad data och läraren förväntar sig antagligen en enkel lösning i stil med:

... println(…) var name = nextLine println(…) ... var rentPerSqm = rent / sqm var formatter = new DecimalFormat(...) var rentStr = formatter.format(...) ...

Skrivet av Alling:

public static double monthlyRentPerSquareMeter(double monthlyRent, int squareMeters) {}

Det finns bara en vettig implementation: monthlyRent / squareMeters Vilket gör den funktionen helt onödig och dessutom blir koden svårare att förstå. Ser man monthlyRent: double / squareMeters: int direkt i koden vet man att resultatet kommer följa IEEE-754 standarden vid division med 0. Enda sättet att förstå vad monthlyRentPerSquareMeter gör om squareMeters är 0 är att gå in i funktionen och kolla hur den är skriven, den kanske returnerar -1.0 istället för NaN/Inf/-Inf.

Skrivet av Alling:

Som sagt, det kan absolut sägas vara overkill att skapa en public static String uppercase(String s), men den är inte helt redundant. Den har nämligen (den i TS fall irrelevanta, och generellt inte så särkilt stora) fördelen att den kan användas som argument till en högre ordningens funktion, utan en extra funktionsabstraktion à la x -> x.toUpperCase().

Man kan använda referenser till instansmetoder som argument till en hof. foo.map(String::toUpperCase)

Permalänk
99:e percentilen
Skrivet av jclr:

Att försöka skriva java med bara referentiellt transparenta ("rena") funktioner är krångligt och kommer resultera i svårläst kod.

Det går såklart inte att bara använda rena funktioner i Java. Men de är i regel mycket värdefulla även i det språket. De gör inte i allmänhet koden svårläst, snarare tvärtom.

Citat:

Fördelarna med "funktionell programmering" och "rena" funktioner visa sig först vid lite större program. Det här programmet är så kort att man kan ha hela koden i huvudet och dessutom består det i stort sett bara av sidoeffekter.

Det håller jag med om.

Citat:

Det finns bara en vettig implementation: monthlyRent / squareMeters Vilket gör den funktionen helt onödig och dessutom blir koden svårare att förstå.

Mm. Ville ge TS ett konkret exempel, men du har nog rätt i att det i det här fallet kanske till och med är kontraproduktivt att återuppfinna division.

Citat:

Ser man monthlyRent: double / squareMeters: int direkt i koden vet man att resultatet kommer följa IEEE-754 standarden vid division med 0. Enda sättet att förstå vad monthlyRentPerSquareMeter gör om squareMeters är 0 är att gå in i funktionen och kolla hur den är skriven, den kanske returnerar -1.0 istället för NaN/Inf/-Inf.

Bra poäng!

Citat:

Man kan använda referenser till instansmetoder som argument till en hof. foo.map(String::toUpperCase)

Ser man på! Java upphör aldrig att förvåna mig med all sin komplexitet.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem
Skrivet av Alling:

Det går såklart inte att bara använda rena funktioner i Java. Men de är i regel mycket värdefulla även i det språket. De gör inte i allmänhet koden svårläst, snarare tvärtom.

Problemet är att så fort man börjar blanda in sidoeffekter på några ställen så tappar man tyvärr många av fördelarna. Jag programmerar inte i java men testade att göra den här uppgiften med bara referentiellt transparenta funktioner för att se ungefär hur det skulle se ut.

Alla funktioner i programlogiken är generiska i F. tex <F> F<Integer> rentInput(Monad<F> M, Console<F> C) som frågar efter hyran ger tillbaka F<Integer> där F kan vara vilken typkonstruktör som helst så länge man kan definera en Monad<F> och Console<F>. Monad<F>/Console<F> är vilka egenskaper funktionen behöver tillgång till. Monad<F> för programflöde och Console<F> för in/utmatning. Funktionerna (sqmInput/rentInput/program) i programlogiken är bara en beskrivning av vad man vill göra. För att verkligen få de att göra någon behöver man en Monad/Console för en viss F.

Monad/Console för IO sparar en kedja av funktionsanrop som först körs när man anropar .unsafeRunSync() i slutet av programmet och då läser/skriver via println/readLine.
Monad/Console för State tar instället en TestData(List<String> in, List<String out). in innehåller inmatning man vill simulera/testa och out kommer innehålla utmatningen från programmet.

Så visst går det att programmera funktionellt i java men speciellt smidigt är det inte.

class Unit { public final static Unit Unit = new Unit(); private Unit() {} } interface Hkt<F, A> {} interface Hkt2<F, A, B> extends Hkt<Hkt<F, A>, B> {} interface Monad<F> { <A> Hkt<F, A> pure(A a); <A, B> Hkt<F, B> flatMap(Hkt<F, A> fa, Function<A, Hkt<F, B>> f); default <A, B> Hkt<F, B> flatMap_(Hkt<F, A> fa, Hkt<F, B> fb) { return flatMap(fa, dummy -> fb); } default <A, B> Hkt<F, B> map(Hkt<F, A> fa, Function<A, B> f) { return flatMap(fa, f.andThen(this::pure)); } } abstract class IO<A> implements Hkt<IO.m, A> { static final class m {} static <A> IO<A> narrowK(Hkt<IO.m, A> Fa) { return (IO<A>) Fa; } public static final IOMonad monad = new IOMonad() {}; public static final IOConsole console = new IOConsole() {}; abstract A unsafeRunSync(); } interface IOMonad extends Monad<IO.m> { @Override default <A> IO<A> pure(A a) { return new IO<>() { @Override A unsafeRunSync() { return a; } }; } @Override default <A, B> IO<B> flatMap(Hkt<IO.m, A> fa, Function<A, Hkt<IO.m, B>> f) { return new IO<>() { @Override B unsafeRunSync() { return ((IO<B>) f.apply(((IO<A>) fa).unsafeRunSync())).unsafeRunSync(); } }; } } interface Console<F> { Hkt<F, Unit> putStrLn(String s); Hkt<F, String> getStrLn(); } interface IOConsole extends Console<IO.m> { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); default IO<Unit> putStrLn(String s) { return new IO<>() { @Override Unit unsafeRunSync() { System.out.println(s); return Unit; } }; } default IO<String> getStrLn() { return new IO<>() { @Override String unsafeRunSync() { try { return in.readLine(); } catch (IOException e) { throw new RuntimeException(); } } }; } } class Tuple<A, B> { public final A _1; public final B _2; Tuple(A a, B b) { _1 = a; _2 = b; } } class State<S, A> implements Hkt2<State.m, S, A> { public static final class m {} static <S, A> State<S, A> narrowK(Hkt<Hkt<State.m, S>, A> Fa) { return (State<S, A>) Fa; } public static final StateMonad monad = new StateMonad() {}; public static final StateConsole console = new StateConsole() {}; public final Function<S, Tuple<S, A>> run; State(Function<S, Tuple<S, A>> run) { this.run = run; } public static <S, A> State<S, A> pure(A a) { return new State<>(s -> new Tuple<>(s, a)); } public static <S> State<S, Unit> set(S s) { return new State<>(dummy -> new Tuple<>(s, Unit)); } public static <S> State<S, S> get() { return new State<>(s -> new Tuple<>(s, s)); } public static <S> State<S, Unit> modify(Function<S, S> f) { return new State<>(s -> new Tuple<>(f.apply(s), Unit)); } public <B> State<S, B> flatMap(Function<A, State<S, B>> f) { return new State<>(s -> { var tmp = run.apply(s); return f.apply(tmp._2).run.apply(tmp._1); }); } public <B> State<S, B> map(Function<A, B> f) { return flatMap(a -> pure(f.apply(a))); } } class TestData { public final List<String> in; public final List<String> out; TestData(List<String> in, List<String> out) { this.in = in; this.out = out; } } interface StateConsole extends Console<Hkt<State.m, TestData>> { @Override default State<TestData, Unit> putStrLn(String s) { return State.modify(td -> { var tdOut = new ArrayList<>(td.out); tdOut.add(s); return new TestData(td.in, List.copyOf(tdOut)); }); } @Override default State<TestData, String> getStrLn() { return new State<>(td -> { var tdIn = new ArrayList<>(td.in); var head = tdIn.remove(0); var tdOut = new ArrayList<>(td.out); tdOut.add("(User input: " + head + ")"); return new Tuple<>(new TestData(List.copyOf(tdIn), List.copyOf(tdOut)), head); }); } } interface StateMonad<S> extends Monad<Hkt<State.m, S>> { @Override default <A> State<S, A> pure(A a) { return State.pure(a); } @Override default <A, B> State<S, B> flatMap(Hkt<Hkt<State.m, S>, A> fa, Function<A, Hkt<Hkt<State.m, S>, B>> f) { return ((State<S, A>) fa).flatMap(a -> (State<S, B>) f.apply(a)); } } public class Main { private static Optional<Integer> parseInt(String s) { try { return Optional.of(Integer.parseInt(s)); } catch (NumberFormatException e) { return Optional.empty(); } } private static <A, B> B fold(Optional<A> oa, Supplier<B> empty, Function<A, B> present) { if(oa.isPresent()) return present.apply(oa.get()); else return empty.get(); } private static <F> Hkt<F, Integer> rentInput(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange din månadshyra:"), M.flatMap(C.getStrLn(), rentStr -> fold(parseInt(rentStr), () -> M.flatMap_(C.putStrLn("Du måste ange månadshyran som ett heltal"), rentInput(M, C)), rent -> rent < 0 ? M.flatMap_(C.putStrLn("Hyran kan inte vara negativ"), rentInput(M, C)) : M.pure(rent)))); } private static <F> Hkt<F, Integer> sqmInput(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange lägenhetens storlek i kvadratmeter:"), M.flatMap(C.getStrLn(), sqmStr -> fold(parseInt(sqmStr), () -> M.flatMap_(C.putStrLn("Du måste ange lägenhetens storlek som ett heltal"), sqmInput(M, C)), sqm -> sqm <= 0 ? M.flatMap_(C.putStrLn("Lägenhetens storlek måste vara över 0 kvm"), sqmInput(M, C)) : M.pure(sqm)))); } private static String rentPerSqm(int rent, int sqm) { var formatter = new DecimalFormat("0.##"); return formatter.format((double) rent / sqm); } private static <F> Hkt<F, Unit> program(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange ditt namn:"), M.flatMap(M.map(C.getStrLn(), String::toUpperCase), name -> M.flatMap(rentInput(M, C), rent -> M.flatMap(sqmInput(M, C), sqm -> C.putStrLn(name + " du betalar " + rentPerSqm(rent, sqm) + "kr per kvadratmeter i månaden."))))); } public static void main(String[] args) { IO<Unit> program1 = IO.narrowK(program(IO.monad, IO.console)); program1.unsafeRunSync(); State<TestData, Unit> program2 = State.narrowK(program(State.monad, State.console)); program2.run.apply(new TestData(List.of("John Doe", "2500kr", "-100", "4500", "0m2", "0", "55"), List.of()))._1.out.forEach(System.out::println); } }

Permalänk
99:e percentilen
Skrivet av jclr:

Problemet är att så fort man börjar blanda in sidoeffekter på några ställen så tappar man tyvärr många av fördelarna. Jag programmerar inte i java men testade att göra den här uppgiften med bara referentiellt transparenta funktioner för att se ungefär hur det skulle se ut.

Alla funktioner i programlogiken är generiska i F. tex <F> F<Integer> rentInput(Monad<F> M, Console<F> C) som frågar efter hyran ger tillbaka F<Integer> där F kan vara vilken typkonstruktör som helst så länge man kan definera en Monad<F> och Console<F>. Monad<F>/Console<F> är vilka egenskaper funktionen behöver tillgång till. Monad<F> för programflöde och Console<F> för in/utmatning. Funktionerna (sqmInput/rentInput/program) i programlogiken är bara en beskrivning av vad man vill göra. För att verkligen få de att göra någon behöver man en Monad/Console för en viss F.

Monad/Console för IO sparar en kedja av funktionsanrop som först körs när man anropar .unsafeRunSync() i slutet av programmet och då läser/skriver via println/readLine.
Monad/Console för State tar instället en TestData(List<String> in, List<String out). in innehåller inmatning man vill simulera/testa och out kommer innehålla utmatningen från programmet.

Så visst går det att programmera funktionellt i java men speciellt smidigt är det inte.

Klicka för mer information

class Unit { public final static Unit Unit = new Unit(); private Unit() {} } interface Hkt<F, A> {} interface Hkt2<F, A, B> extends Hkt<Hkt<F, A>, B> {} interface Monad<F> { <A> Hkt<F, A> pure(A a); <A, B> Hkt<F, B> flatMap(Hkt<F, A> fa, Function<A, Hkt<F, B>> f); default <A, B> Hkt<F, B> flatMap_(Hkt<F, A> fa, Hkt<F, B> fb) { return flatMap(fa, dummy -> fb); } default <A, B> Hkt<F, B> map(Hkt<F, A> fa, Function<A, B> f) { return flatMap(fa, f.andThen(this::pure)); } } abstract class IO<A> implements Hkt<IO.m, A> { static final class m {} static <A> IO<A> narrowK(Hkt<IO.m, A> Fa) { return (IO<A>) Fa; } public static final IOMonad monad = new IOMonad() {}; public static final IOConsole console = new IOConsole() {}; abstract A unsafeRunSync(); } interface IOMonad extends Monad<IO.m> { @Override default <A> IO<A> pure(A a) { return new IO<>() { @Override A unsafeRunSync() { return a; } }; } @Override default <A, B> IO<B> flatMap(Hkt<IO.m, A> fa, Function<A, Hkt<IO.m, B>> f) { return new IO<>() { @Override B unsafeRunSync() { return ((IO<B>) f.apply(((IO<A>) fa).unsafeRunSync())).unsafeRunSync(); } }; } } interface Console<F> { Hkt<F, Unit> putStrLn(String s); Hkt<F, String> getStrLn(); } interface IOConsole extends Console<IO.m> { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); default IO<Unit> putStrLn(String s) { return new IO<>() { @Override Unit unsafeRunSync() { System.out.println(s); return Unit; } }; } default IO<String> getStrLn() { return new IO<>() { @Override String unsafeRunSync() { try { return in.readLine(); } catch (IOException e) { throw new RuntimeException(); } } }; } } class Tuple<A, B> { public final A _1; public final B _2; Tuple(A a, B b) { _1 = a; _2 = b; } } class State<S, A> implements Hkt2<State.m, S, A> { public static final class m {} static <S, A> State<S, A> narrowK(Hkt<Hkt<State.m, S>, A> Fa) { return (State<S, A>) Fa; } public static final StateMonad monad = new StateMonad() {}; public static final StateConsole console = new StateConsole() {}; public final Function<S, Tuple<S, A>> run; State(Function<S, Tuple<S, A>> run) { this.run = run; } public static <S, A> State<S, A> pure(A a) { return new State<>(s -> new Tuple<>(s, a)); } public static <S> State<S, Unit> set(S s) { return new State<>(dummy -> new Tuple<>(s, Unit)); } public static <S> State<S, S> get() { return new State<>(s -> new Tuple<>(s, s)); } public static <S> State<S, Unit> modify(Function<S, S> f) { return new State<>(s -> new Tuple<>(f.apply(s), Unit)); } public <B> State<S, B> flatMap(Function<A, State<S, B>> f) { return new State<>(s -> { var tmp = run.apply(s); return f.apply(tmp._2).run.apply(tmp._1); }); } public <B> State<S, B> map(Function<A, B> f) { return flatMap(a -> pure(f.apply(a))); } } class TestData { public final List<String> in; public final List<String> out; TestData(List<String> in, List<String> out) { this.in = in; this.out = out; } } interface StateConsole extends Console<Hkt<State.m, TestData>> { @Override default State<TestData, Unit> putStrLn(String s) { return State.modify(td -> { var tdOut = new ArrayList<>(td.out); tdOut.add(s); return new TestData(td.in, List.copyOf(tdOut)); }); } @Override default State<TestData, String> getStrLn() { return new State<>(td -> { var tdIn = new ArrayList<>(td.in); var head = tdIn.remove(0); var tdOut = new ArrayList<>(td.out); tdOut.add("(User input: " + head + ")"); return new Tuple<>(new TestData(List.copyOf(tdIn), List.copyOf(tdOut)), head); }); } } interface StateMonad<S> extends Monad<Hkt<State.m, S>> { @Override default <A> State<S, A> pure(A a) { return State.pure(a); } @Override default <A, B> State<S, B> flatMap(Hkt<Hkt<State.m, S>, A> fa, Function<A, Hkt<Hkt<State.m, S>, B>> f) { return ((State<S, A>) fa).flatMap(a -> (State<S, B>) f.apply(a)); } } public class Main { private static Optional<Integer> parseInt(String s) { try { return Optional.of(Integer.parseInt(s)); } catch (NumberFormatException e) { return Optional.empty(); } } private static <A, B> B fold(Optional<A> oa, Supplier<B> empty, Function<A, B> present) { if(oa.isPresent()) return present.apply(oa.get()); else return empty.get(); } private static <F> Hkt<F, Integer> rentInput(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange din månadshyra:"), M.flatMap(C.getStrLn(), rentStr -> fold(parseInt(rentStr), () -> M.flatMap_(C.putStrLn("Du måste ange månadshyran som ett heltal"), rentInput(M, C)), rent -> rent < 0 ? M.flatMap_(C.putStrLn("Hyran kan inte vara negativ"), rentInput(M, C)) : M.pure(rent)))); } private static <F> Hkt<F, Integer> sqmInput(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange lägenhetens storlek i kvadratmeter:"), M.flatMap(C.getStrLn(), sqmStr -> fold(parseInt(sqmStr), () -> M.flatMap_(C.putStrLn("Du måste ange lägenhetens storlek som ett heltal"), sqmInput(M, C)), sqm -> sqm <= 0 ? M.flatMap_(C.putStrLn("Lägenhetens storlek måste vara över 0 kvm"), sqmInput(M, C)) : M.pure(sqm)))); } private static String rentPerSqm(int rent, int sqm) { var formatter = new DecimalFormat("0.##"); return formatter.format((double) rent / sqm); } private static <F> Hkt<F, Unit> program(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange ditt namn:"), M.flatMap(M.map(C.getStrLn(), String::toUpperCase), name -> M.flatMap(rentInput(M, C), rent -> M.flatMap(sqmInput(M, C), sqm -> C.putStrLn(name + " du betalar " + rentPerSqm(rent, sqm) + "kr per kvadratmeter i månaden."))))); } public static void main(String[] args) { IO<Unit> program1 = IO.narrowK(program(IO.monad, IO.console)); program1.unsafeRunSync(); State<TestData, Unit> program2 = State.narrowK(program(State.monad, State.console)); program2.run.apply(new TestData(List.of("John Doe", "2500kr", "-100", "4500", "0m2", "0", "55"), List.of()))._1.out.forEach(System.out::println); } }

Visa mer

Jag får säga att det var imponerande att du orkade implementera Monad, State och IO i Java.

Men jag ser inte att det du presenterar är ett seriöst argument mot vad jag faktiskt skrev:

Skrivet av Alling:

Det går såklart inte att bara använda rena funktioner i Java. Men de är i regel mycket värdefulla även i det språket. De gör inte i allmänhet koden svårläst, snarare tvärtom.

Jag har inte sagt att någonting blir bra om man skriver Java-kod som uteslutande består av rena funktioner. Som du ser skrev jag till och med explicit att det inte går (bra) att bara använda rena funktioner. (Jag skrev dessutom tydligt att jag tyckte du hade rätt om TS program, så litet och simpelt som det är.)

Men det finns i allmänhet ett enormt värde, som jag upplever att personer som lär sig Java påfallande sällan får höra om, i en ren funktion – en bit kod som är enkel att testa, verifiera, återanvända, flytta och koppla ihop med annan kod. En medhjälpare som man kan lita på ger tillbaka ett förutsägbart värde varje gång och aldrig förstör någonting genom att mutera något state.

Det betyder inte att samtliga rader i ett Java-program måste vara helt pure.

Det enda jag vill göra är att lyfta fram ett kompletterande – och för många nytt – sätt att se problemlösning på: mappning från input till output snarare än sekventiella uppmaningar att mutera state. I praktiken kan det innebära att en kodbas består av ett antal rena funktioner och en relativt liten "smutsig" del som gör all input och output (som vanligt, utan någon monadisk abstraktion), där de rena funktionerna då används för att lösa de delproblem som har en naturlig funktionell representation.

Jag kan visa ett konkret exempel på en sådan kodbas, nämligen Userscript Proxy. Den består av nio filer med enbart rena funktioner (utom modules/inject.py, som exporterar en "smutsig" funktion) och en helt imperativ fil som "kopplar ihop allt" och gör all I/O (injector.py).

Värdet i funktionell programmering i imperativa språk ligger givetvis, som allt annat, i att använda det på rätt sätt. Jag förstår helt ärligt inte hur din extremt överkomplicerade kod ovan (som inte liknar något jag föreslagit) är ett relevant argument.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem
Skrivet av jclr:

Problemet är att så fort man börjar blanda in sidoeffekter på några ställen så tappar man tyvärr många av fördelarna. Jag programmerar inte i java men testade att göra den här uppgiften med bara referentiellt transparenta funktioner för att se ungefär hur det skulle se ut.

Alla funktioner i programlogiken är generiska i F. tex <F> F<Integer> rentInput(Monad<F> M, Console<F> C) som frågar efter hyran ger tillbaka F<Integer> där F kan vara vilken typkonstruktör som helst så länge man kan definera en Monad<F> och Console<F>. Monad<F>/Console<F> är vilka egenskaper funktionen behöver tillgång till. Monad<F> för programflöde och Console<F> för in/utmatning. Funktionerna (sqmInput/rentInput/program) i programlogiken är bara en beskrivning av vad man vill göra. För att verkligen få de att göra någon behöver man en Monad/Console för en viss F.

Monad/Console för IO sparar en kedja av funktionsanrop som först körs när man anropar .unsafeRunSync() i slutet av programmet och då läser/skriver via println/readLine.
Monad/Console för State tar instället en TestData(List<String> in, List<String out). in innehåller inmatning man vill simulera/testa och out kommer innehålla utmatningen från programmet.

Så visst går det att programmera funktionellt i java men speciellt smidigt är det inte.

class Unit { public final static Unit Unit = new Unit(); private Unit() {} } interface Hkt<F, A> {} interface Hkt2<F, A, B> extends Hkt<Hkt<F, A>, B> {} interface Monad<F> { <A> Hkt<F, A> pure(A a); <A, B> Hkt<F, B> flatMap(Hkt<F, A> fa, Function<A, Hkt<F, B>> f); default <A, B> Hkt<F, B> flatMap_(Hkt<F, A> fa, Hkt<F, B> fb) { return flatMap(fa, dummy -> fb); } default <A, B> Hkt<F, B> map(Hkt<F, A> fa, Function<A, B> f) { return flatMap(fa, f.andThen(this::pure)); } } abstract class IO<A> implements Hkt<IO.m, A> { static final class m {} static <A> IO<A> narrowK(Hkt<IO.m, A> Fa) { return (IO<A>) Fa; } public static final IOMonad monad = new IOMonad() {}; public static final IOConsole console = new IOConsole() {}; abstract A unsafeRunSync(); } interface IOMonad extends Monad<IO.m> { @Override default <A> IO<A> pure(A a) { return new IO<>() { @Override A unsafeRunSync() { return a; } }; } @Override default <A, B> IO<B> flatMap(Hkt<IO.m, A> fa, Function<A, Hkt<IO.m, B>> f) { return new IO<>() { @Override B unsafeRunSync() { return ((IO<B>) f.apply(((IO<A>) fa).unsafeRunSync())).unsafeRunSync(); } }; } } interface Console<F> { Hkt<F, Unit> putStrLn(String s); Hkt<F, String> getStrLn(); } interface IOConsole extends Console<IO.m> { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); default IO<Unit> putStrLn(String s) { return new IO<>() { @Override Unit unsafeRunSync() { System.out.println(s); return Unit; } }; } default IO<String> getStrLn() { return new IO<>() { @Override String unsafeRunSync() { try { return in.readLine(); } catch (IOException e) { throw new RuntimeException(); } } }; } } class Tuple<A, B> { public final A _1; public final B _2; Tuple(A a, B b) { _1 = a; _2 = b; } } class State<S, A> implements Hkt2<State.m, S, A> { public static final class m {} static <S, A> State<S, A> narrowK(Hkt<Hkt<State.m, S>, A> Fa) { return (State<S, A>) Fa; } public static final StateMonad monad = new StateMonad() {}; public static final StateConsole console = new StateConsole() {}; public final Function<S, Tuple<S, A>> run; State(Function<S, Tuple<S, A>> run) { this.run = run; } public static <S, A> State<S, A> pure(A a) { return new State<>(s -> new Tuple<>(s, a)); } public static <S> State<S, Unit> set(S s) { return new State<>(dummy -> new Tuple<>(s, Unit)); } public static <S> State<S, S> get() { return new State<>(s -> new Tuple<>(s, s)); } public static <S> State<S, Unit> modify(Function<S, S> f) { return new State<>(s -> new Tuple<>(f.apply(s), Unit)); } public <B> State<S, B> flatMap(Function<A, State<S, B>> f) { return new State<>(s -> { var tmp = run.apply(s); return f.apply(tmp._2).run.apply(tmp._1); }); } public <B> State<S, B> map(Function<A, B> f) { return flatMap(a -> pure(f.apply(a))); } } class TestData { public final List<String> in; public final List<String> out; TestData(List<String> in, List<String> out) { this.in = in; this.out = out; } } interface StateConsole extends Console<Hkt<State.m, TestData>> { @Override default State<TestData, Unit> putStrLn(String s) { return State.modify(td -> { var tdOut = new ArrayList<>(td.out); tdOut.add(s); return new TestData(td.in, List.copyOf(tdOut)); }); } @Override default State<TestData, String> getStrLn() { return new State<>(td -> { var tdIn = new ArrayList<>(td.in); var head = tdIn.remove(0); var tdOut = new ArrayList<>(td.out); tdOut.add("(User input: " + head + ")"); return new Tuple<>(new TestData(List.copyOf(tdIn), List.copyOf(tdOut)), head); }); } } interface StateMonad<S> extends Monad<Hkt<State.m, S>> { @Override default <A> State<S, A> pure(A a) { return State.pure(a); } @Override default <A, B> State<S, B> flatMap(Hkt<Hkt<State.m, S>, A> fa, Function<A, Hkt<Hkt<State.m, S>, B>> f) { return ((State<S, A>) fa).flatMap(a -> (State<S, B>) f.apply(a)); } } public class Main { private static Optional<Integer> parseInt(String s) { try { return Optional.of(Integer.parseInt(s)); } catch (NumberFormatException e) { return Optional.empty(); } } private static <A, B> B fold(Optional<A> oa, Supplier<B> empty, Function<A, B> present) { if(oa.isPresent()) return present.apply(oa.get()); else return empty.get(); } private static <F> Hkt<F, Integer> rentInput(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange din månadshyra:"), M.flatMap(C.getStrLn(), rentStr -> fold(parseInt(rentStr), () -> M.flatMap_(C.putStrLn("Du måste ange månadshyran som ett heltal"), rentInput(M, C)), rent -> rent < 0 ? M.flatMap_(C.putStrLn("Hyran kan inte vara negativ"), rentInput(M, C)) : M.pure(rent)))); } private static <F> Hkt<F, Integer> sqmInput(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange lägenhetens storlek i kvadratmeter:"), M.flatMap(C.getStrLn(), sqmStr -> fold(parseInt(sqmStr), () -> M.flatMap_(C.putStrLn("Du måste ange lägenhetens storlek som ett heltal"), sqmInput(M, C)), sqm -> sqm <= 0 ? M.flatMap_(C.putStrLn("Lägenhetens storlek måste vara över 0 kvm"), sqmInput(M, C)) : M.pure(sqm)))); } private static String rentPerSqm(int rent, int sqm) { var formatter = new DecimalFormat("0.##"); return formatter.format((double) rent / sqm); } private static <F> Hkt<F, Unit> program(Monad<F> M, Console<F> C) { return M.flatMap_(C.putStrLn("Ange ditt namn:"), M.flatMap(M.map(C.getStrLn(), String::toUpperCase), name -> M.flatMap(rentInput(M, C), rent -> M.flatMap(sqmInput(M, C), sqm -> C.putStrLn(name + " du betalar " + rentPerSqm(rent, sqm) + "kr per kvadratmeter i månaden."))))); } public static void main(String[] args) { IO<Unit> program1 = IO.narrowK(program(IO.monad, IO.console)); program1.unsafeRunSync(); State<TestData, Unit> program2 = State.narrowK(program(State.monad, State.console)); program2.run.apply(new TestData(List.of("John Doe", "2500kr", "-100", "4500", "0m2", "0", "55"), List.of()))._1.out.forEach(System.out::println); } }

Du råkar inte koda Scala till vardags?

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem
Skrivet av Teknocide:

Du råkar inte koda Scala till vardags?

Stämmer bra Java koden var i princip scala översatt till java men med "fejkade" higher-kinded types.