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
Hjälp med en uppgift i Java
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.
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...
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.
https://gyazo.com/45d113fd1125df9ed9cbfc88f94a9cde
update: https://gyazo.com/278a03bd55c2a795c5ed2dd98b49775d <-- där har du med 2 decimaler med DecimalFormat klassen
https://gyazo.com/45d113fd1125df9ed9cbfc88f94a9cde
update: https://gyazo.com/278a03bd55c2a795c5ed2dd98b49775d <-- där har du med 2 decimaler med DecimalFormat klassen
Lite väl mycket hjälp kan man tycka.
Lite väl mycket hjälp kan man tycka.
Han undra ju hur vi/jag skulle lösa den ^^
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
https://gyazo.com/45d113fd1125df9ed9cbfc88f94a9cde
update: https://gyazo.com/278a03bd55c2a795c5ed2dd98b49775d <-- där har du med 2 decimaler med DecimalFormat klassen
Tack så jättemycket! Detta sätter några bitar på plats i skallen!
Skickades från m.sweclockers.com
https://gyazo.com/45d113fd1125df9ed9cbfc88f94a9cde
update: https://gyazo.com/278a03bd55c2a795c5ed2dd98b49775d <-- där har du med 2 decimaler med DecimalFormat klassen
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.
Skrivet med hjälp av Better SweClockers
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.
Skrivet med hjälp av Better SweClockers
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)
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.
Skrivet med hjälp av Better SweClockers
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(...)
...
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.
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)
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.
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.
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.
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!
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.
Skrivet med hjälp av Better SweClockers
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);
}
}
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);
}
}
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:
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.
Skrivet med hjälp av Better SweClockers
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?
Kom-pa-TI-bilitet
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.
- Igår Datorhallar åker på miljardstor skattesmäll – ljög om att utvinna krypto 38
- Igår Veckans fråga: Möss eller ljud – Vad lägger du mest pengar på? 63
- 16 / 4 X kan råda bot på bottar med betallösning 35
- 16 / 4 Blizzards tidigare chef vill kunna dricksa spelutvecklare 95
- 16 / 4 Youtube attackerar appar med reklamfritt Youtube 82
- Sälj råd: Olika datordelar2
- Tillför "Overencumbered" något till spel?12
- MAX 15k dator för CS22
- Dagens fynd, vad räknas som fynd?3
- Varför finns det inga Apple Airpods på blocket?8
- World of Warcraft: The War Within 20th Anniversary Collector's Edition2
- Entusiast portar tusentals moderna program till Windows 9526
- Omstart PSU krävs vid start av PC7
- Forskare sätter Antarktis på Pokémon-kartan16
- X kan råda bot på bottar med betallösning35
- Köpes CPU Köpes - 5800X3D / 5700X3D / 13600K/KF
- Säljes Intressekoll: Asus ROG RTX 3080 Ti Strix OC
- Säljes 4090 RTX TUF ASUS OG OC 24GB
- Säljes Raspberry Pi 4 Model B 8GB + Cooler Master Raspberry Pi 4 Case 40
- Köpes På jakt efter en lite äldre prisvärd ultrabook-dator
- Säljes ASUS 15,6" ZenScreen MB169C+
- Säljes Stativ - Velbon C600
- Säljes Acer 34" Predator X34GS IPS 21:9 Curved 180 Hz
- Säljes GoPro HERO10 Black
- Säljes Apple Mac Mini - M2 | 16GB | 512GB
- Här är systemkraven för Ghost of Tsushima till PC21
- Välj rätt TV för ljusa rummet6
- Bli ett RGB-Pro med Razers nya musmatta för 1 100 kronor10
- Datorhallar åker på miljardstor skattesmäll – ljög om att utvinna krypto38
- Veckans fråga: Möss eller ljud – Vad lägger du mest pengar på?63
- Western Digital slår lagringsrekord med SD-kort på 4TB28
- Asus lanserar 8K-skärm med Mini LED32
- Forskare sätter Antarktis på Pokémon-kartan16
- X kan råda bot på bottar med betallösning35
- Blizzards tidigare chef vill kunna dricksa spelutvecklare95
Externa nyheter
Spelnyheter från FZ
- 70 % av utvecklarna oroliga över live service-spelens hållbarhet igår
- Vad är viktigast när du väljer gamingskärm? igår
- Säsong två av Fallout får deathclaws och annat Fallout-"ikoniskt" igår
- Nier: Automata-man tycker Stellar Blade är "mycket bättre" än hans spel igår
- Nintendo håller indieshow – med Hollow Knight: Silksong-datum!? igår