Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Locked thread
JewKiller 3000
Nov 28, 2006

by Lowtax

ultrafilter posted:

Speaking of functional languages for web development, there's a (very early) OCaml to Javascript compiler that might be interesting.

There is also this from the Ocsigen project, which is more mature: http://ocsigen.org/js_of_ocaml/

Adbot
ADBOT LOVES YOU

fart simpson
Jul 2, 2005

DEATH TO AMERICA
:xickos:


About a year and a half ago, I got an email from some UK book publisher asking if I would be interested in writing an Elm book.

Pollyanna
Mar 5, 2005

Milk's on them.


The Lisp thread got archived or something, so I'm gonna guess that this is where we ask Clojure questions.

I've been trying to implement the card game War in Clojure. It's very simple: technically, there's two players, but there are basically no decisions made by either player so it can be programmed as a zero-player game. This boils the game down to an initial state, and a certain pipeline it goes through for each "tick" of the game. That means you can play the game by just pushing the state through the pipeline over and over, and rendering it to the screen when it comes out of the pipe.

I understand the logic of the game, and know what steps need to be taken, but the code I wrote for it is goddamn awful:

Lisp code:
;<excerpt>

(defn play-war-cards [game-state]
  (let [{:keys [player-1 player-2]} game-state
        new-player-1 (p/play-war-card player-1)
        new-player-2 (p/play-war-card player-2)]
    (-> game-state
        (assoc :player-1 new-player-1)
        (assoc :player-2 new-player-2))))

(defn player-1-wins [game-state]
  (let [{:keys [player-1 player-2]} game-state
        new-players (p/beats player-1 player-2)
        {:keys [new-player-1 new-player-2]} new-players]
    (-> game-state
        (assoc :player-1 new-player-1)
        (assoc :player-2 new-player-2))))

(defn player-2-wins [game-state]
  (let [{:keys [player-1 player-2]} game-state
        new-players (p/beats player-2 player-1)
        {:keys [new-player-1 new-player-2]} new-players]
    (-> game-state
        (assoc :player-1 new-player-1)
        (assoc :player-2 new-player-2))))

(defn start-war [game-state]
  (let [{:keys [player-1 player-2]} game-state
        new-player-1 (p/go-to-war player-1 3)
        new-player-2 (p/go-to-war player-2 3)]
    (-> game-state
        (assoc :player-1 new-player-1)
        (assoc :player-2 new-player-2))))

(defn resolve-war-cards [game-state]
  (let [{:keys [player-1 player-2]} game-state
        war-card-1 (:war-card player-1)
        war-card-2 (:war-card player-2)]
    (cond
      (> (:value war-card-1) (:value war-card-2)) (player-1-wins game-state)
      (< (:value war-card-1) (:value war-card-2)) (player-2-wins game-state)
      (= (:value war-card-1) (:value war-card-2)) (start-war game-state))))

(defn tick [game-state]
  (-> game-state
      (play-war-cards)
      (resolve-war-cards)))

;</excerpt>
I feel like I'm doing something horribly wrong here. There's lets everywhere, a whole bunch of code repetition, and overall the whole thing feels way too verbose and weird. I tried to take a top-down approach with programming the game, but I feel like I totally missed the mark. I'm not entirely sure if my approach of making Player and Card records was a good one, and I don't feel like I'm really taking advantage of Clojure's strengths here.

How can I improve this code, and the project in general? How would someone who's better at programming in Lisp than I am approach a problem like this? Does what I'm doing make sense, or is it in need of serious refactoring?

fart simpson
Jul 2, 2005

DEATH TO AMERICA
:xickos:

Excuse me, but in War you can count your opponent's cards and secretly put your cards in a certain order when shuffling your winnings back in.

MononcQc
May 29, 2007

Almost every function is the same: extract players off game state, do an operation on one or the other, and then replace the players in the game state.

I'd probably make an abstract function for these that takes care of doing that. Pass in a pair of players, expect a pair back. Then the function that calls it just has to deal with extracting/reinserting them. That will save up a bunch of lines on repeated abstractions.

Pollyanna
Mar 5, 2005

Milk's on them.


I think part of the problem is that I'm constantly associng things into game-state as the return value for every function. I feel like if I have to do that so often, I might be doing it wrong. Functional data structures are kind of a pain to work with in my experience since you constantly have to haul around a reference to the top-level data structure and assoc new attributes into it, which can reach several layers deep. But maybe that's just because of how I approached the problem :(

dougdrums
Feb 25, 2005
CLIENT REQUESTED ELECTRONIC FUNDING RECEIPT (FUNDS NOW)
How would you explain polymorphic lambda calculus well enough for another person to be able to derive typing judgements by hand? Let's say that this person is a Java programmer that is loosely familiar with prop logic. I can't do it without puking out formalisms or being too vague.

Asymmetrikon
Oct 30, 2009

I believe you're a big dork!
Do they already understand untyped/simply-typed lambda calculus, or are you trying to bypass those?

dougdrums
Feb 25, 2005
CLIENT REQUESTED ELECTRONIC FUNDING RECEIPT (FUNDS NOW)
I can probably assume untyped. I'm trying to help a fellow student, but I took a bunch of logic courses so it's new to me also, but I wouldn't know what it would be like to start out by learning lambda calculus.

dougdrums fucked around with this message at 04:36 on Feb 19, 2016

The Laplace Demon
Jul 23, 2009

"Oh dear! Oh dear! Heisenberg is a douche!"

Pollyanna posted:

I feel like I'm doing something horribly wrong here. There's lets everywhere, a whole bunch of code repetition, and overall the whole thing feels way too verbose and weird. I tried to take a top-down approach with programming the game, but I feel like I totally missed the mark. I'm not entirely sure if my approach of making Player and Card records was a good one, and I don't feel like I'm really taking advantage of Clojure's strengths here.

How can I improve this code, and the project in general? How would someone who's better at programming in Lisp than I am approach a problem like this? Does what I'm doing make sense, or is it in need of serious refactoring?

Here's a pretty great talk from the last Clojure/conj about implementing a card/board game from the bottom-up. Maybe this kind of approach would work better for you?

https://www.youtube.com/watch?v=Tb823aqgX_0

quickly
Mar 7, 2012

dougdrums posted:

How would you explain polymorphic lambda calculus well enough for another person to be able to derive typing judgements by hand? Let's say that this person is a Java programmer that is loosely familiar with prop logic. I can't do it without puking out formalisms or being too vague.

I would start by constructing proof trees using typing rules for simply-typed lambda calculus, unless there's some pressing need to skip straight into System F. I don't see any way to avoid the formalism - in the simply-typed case, it is straightforward and easy to apply. The typing rules have fairly obvious interpretations which might make them easier to remember and understand.

dougdrums
Feb 25, 2005
CLIENT REQUESTED ELECTRONIC FUNDING RECEIPT (FUNDS NOW)
The only pressing need is that we're taking it as a course. Regular human math is just nonsense to me and I just had to drop a course due to this, so I have a ton of sympathy for this fella who helped me in a previous math course but is finding lambda calculus to be absolutely opaque, and he isn't the only one. I had the idea of trying to write up a little crash course and a little index of notations with how their semantics work, try to approach it like programming a computer with funny symbols, but beyond, "follow the god drat simple rules, and if you gently caress up, which you will, do it again." I am at a loss, but I know this is exactly how my god drat math tutor feels and I know how it feels to look at a page of symbolism and go fuuuuuuuuk in my head.

We're starting to get into System F in class so I kinda wanted to make it as fast as possible. The most useful advice I seem to have given was, "imagine the term as a little computer program, what types would you assign the variables?" I mean, I don't think I really had an intuition for what domain and range were until I did the proofs (and it is mentioned in passing, confusing those who might not be so solid on functions in the first place), so it's hard for me to backtrack and approach it from the top-down, since I didn't even learn it that way. Really after writing this, I think the only way to learn is to do proofs and gently caress them up over and over again, but that seems like useless advice to give someone. I think I'll just have to let them struggle ... :-/

After learning all of that higher order stuff I love lambda calculus. It is so deceptively simple, and there are a ton of variations with interesting properties. It's just that fuckin learnin and teaching and learning in general is such a god drat frustrating exercise.

To keep this somewhat on topic: I understand Racket uses GNU Lightning, but I haven't done a lot of functional programming beyond F#. Generally speaking how does it compare in speed to other implementations?

Also I've never actually tried Haskell up until this moment, despite having read a poo poo-ton about it. http://tryhaskell.org/ output is great! Maybe I should just try to explain it using Haskell, duh!

dougdrums fucked around with this message at 01:34 on Feb 25, 2016

Gasbraai
Oct 25, 2010

Lictor my Dictor
I'm looking for an FP language to learn and eventually do actual work in. My current day job is ruby mostly but it seems we will be moving towards jruby / jvm based dev and deployment. I have poked around with haskell in the past and done the first 6 or so adventofcode problems in haskell but I don't really see how I would be able to use it productively or teach it to my coworkers.

I initially thought that maybe scala would be the way to go but it seems that it would be really easy for someone to fall back to writing imperative code in scala. Would typed clojure be a good middle ground / starting point between dynamically typed imperative code and typed functional code? Does typed clojure have a mechanism for 'typeclasses' like haskell, ie, can I specify a "monad" interface for types and have separate implementations per type?

Votlook
Aug 20, 2005

Pollyanna posted:


I feel like I'm doing something horribly wrong here. There's lets everywhere, a whole bunch of code repetition, and overall the whole thing feels way too verbose and weird. I tried to take a top-down approach with programming the game, but I feel like I totally missed the mark. I'm not entirely sure if my approach of making Player and Card records was a good one, and I don't feel like I'm really taking advantage of Clojure's strengths here.

How can I improve this code, and the project in general? How would someone who's better at programming in Lisp than I am approach a problem like this? Does what I'm doing make sense, or is it in need of serious refactoring?

Some hints:

You can use the update and update-in functions to modify entries directly:

Lisp code:
(defn play-war-cards [game-state]
  (-> game-state
      (update :player-1 p/play-war-card)
      (update :player-2 p/play-war-card)))
You can destructure argument directly, no need to using let for that:

code:
(defn play-war-cards [{:keys [player-1 player-2] :as game-state}]
  ...)

raminasi
Jan 25, 2005

a last drink with no ice

Lunixnerd posted:

I initially thought that maybe scala would be the way to go but it seems that it would be really easy for someone to fall back to writing imperative code in scala.

I'm in the camp that considers this a good thing, although I don't know anything about Scala or Clojure specifically.

HappyHippo
Nov 19, 2003
Do you have an Air Miles Card?

GrumpyDoctor posted:

I'm in the camp that considers this a good thing, although I don't know anything about Scala or Clojure specifically.

When you're trying to learn functional programming for the first time I think it's a good idea to use a language that forces you to use it.

fart simpson
Jul 2, 2005

DEATH TO AMERICA
:xickos:

Yeah, I thought I knew functional programming pretty well until I started forcing myself to actually use Haskell for new hobby projects.

Asymmetrikon
Oct 30, 2009

I believe you're a big dork!
Once you get used to them, it's nice to use pure functional languages that force you to cordon off side effects.

quickly
Mar 7, 2012
I learned to program in Haskell, and the worst thing about having to use other languages in school (Java, C, Ruby, OCaml) is the lack of typed side effects (and the entire apparatus Haskell uses to accomplish this). I'm pretty convinced that monads should be Haskell's selling point (from a software development standpoint), but I might just be brainwashed or something.

quickly fucked around with this message at 05:30 on Feb 27, 2016

Ralith
Jan 12, 2011

I see a ship in the harbor
I can and shall obey
But if it wasn't for your misfortune
I'd be a heavenly person today
Haskell is easily my favorite imperative language. There's just so many tools available to express your intentions and reason about your code, even without getting into the really clever abstractions.

Also, lens is amazing.

Doc Hawkins
Jun 15, 2010

Dashing? But I'm not even moving!


drat, what's your favorite functional language then?

sarehu
Apr 20, 2007

(call/cc call/cc)
My favorite functional language is Python. It has lambdas and list comprehensions.

Ralith
Jan 12, 2011

I see a ship in the harbor
I can and shall obey
But if it wasn't for your misfortune
I'd be a heavenly person today

Doc Hawkins posted:

drat, what's your favorite functional language then?
C++14? :shrug:

Mostly I'm trying to make a point about paradigm availability.

Jo
Jan 24, 2005

:allears:
Soiled Meat
I'm going to harken back to the Sieve of Eratosthenes question from page 4.

I'm starting to fiddle with Clojure (I'd started in Racket but didn't like the docs). One of my biggest motivations was being able to more simply implement symbolic differentiation and machine learning stuff. The tricky part with that is I'm generally only familiar with the cases where you have really big matrices which are updated a whole bunch of times. I've heard Clojure keeps around old references, and that worries me. I know they don't use copy-on-write, but something more efficient. All the same, when you've got structures that don't fit into memory and require dozens of updates, it makes me a little concerned about how that's going to work out in a purely functional language, or if it's not objectionable to have side-effects by design. Does the matrix become the mutable state of the application?

Ralith
Jan 12, 2011

I see a ship in the harbor
I can and shall obey
But if it wasn't for your misfortune
I'd be a heavenly person today
My experience in Haskell, at least, has been that purity isn't so much about never having side effects but rather quantifying them as precisely as possible, e.g. with various monads. I don't know what the idiomatic approach to that is in Closure, but if your computational task really demands mutable state that certainly doesn't mean it's poorly suited to implementation in a pure functional language.

Tequila Bob
Nov 2, 2011

IT'S HAL TIME, CHUMPS

Jo posted:

I'm going to harken back to the Sieve of Eratosthenes question from page 4.

I'm starting to fiddle with Clojure (I'd started in Racket but didn't like the docs). One of my biggest motivations was being able to more simply implement symbolic differentiation and machine learning stuff. The tricky part with that is I'm generally only familiar with the cases where you have really big matrices which are updated a whole bunch of times. I've heard Clojure keeps around old references, and that worries me. I know they don't use copy-on-write, but something more efficient. All the same, when you've got structures that don't fit into memory and require dozens of updates, it makes me a little concerned about how that's going to work out in a purely functional language, or if it's not objectionable to have side-effects by design. Does the matrix become the mutable state of the application?

Clojure is totally cool with your mutable state. If you want to mutate a huge matrix while using idiomatic code, I'd recommend that you use "transients" (http://clojure.org/reference/transients). Clojure's vectors are capable of transient operations, so you should be able to represent your matrix as a transient vector of transient vectors. You can code like it's Clojure and consume memory like it's imperative code manipulating one global blob of memory!

There are also matrix libraries you could look at, like https://github.com/mikera/core.matrix. I have no experience with it, but you might take a look.

Finally, I suggest that you not worry about memory consumption before writing the code. Instead, I think you should write your code first, then determine if it's too slow or too memory-heavy, and then consider ways to deal with it. Unless you've already written some code and observed a memory problem, you are currently engaged in premature optimization.

Jo
Jan 24, 2005

:allears:
Soiled Meat

Tequila Bob posted:

Clojure is totally cool with your mutable state. If you want to mutate a huge matrix while using idiomatic code, I'd recommend that you use "transients" (http://clojure.org/reference/transients). Clojure's vectors are capable of transient operations, so you should be able to represent your matrix as a transient vector of transient vectors. You can code like it's Clojure and consume memory like it's imperative code manipulating one global blob of memory!

There are also matrix libraries you could look at, like https://github.com/mikera/core.matrix. I have no experience with it, but you might take a look.

Finally, I suggest that you not worry about memory consumption before writing the code. Instead, I think you should write your code first, then determine if it's too slow or too memory-heavy, and then consider ways to deal with it. Unless you've already written some code and observed a memory problem, you are currently engaged in premature optimization.

I had no idea about transients! That seems to be precisely what I need. It's going to take me a while to figure out how to re-fit my mental model of the problem to use them, but I'm thinking that will handle it.

I've been bitten by memory consumption problems with this problem domain before, so I'm always on the lookout. Our production server has 128 gigs of ram and I'm still bumping up against that limit when I run the application full tilt. I'd love to get utilization down, but there's only so much that you can do when you've got a giant multi-gigabyte matrix that you're constantly rebuilding.

EDIT: Follow-up question because I don't want to bump.

I wrote a simple automatic differentiation method and was wondering about a certain operator comparison.

code:
(require 'clojure.walk)

(defn grad 
	[f wrt]
	(cond
		(keyword? f) (if (= f wrt) 1 0)
		(number? f) 0
		(seq? f) (let [op (first f), params (rest f)] 
			(cond
				(or (= op 'clojure.core/+) (= op '+)) `(+ ~@(map #(grad % wrt) params))
				(or (= op 'clojure.core/*) (= op '*)) `(+ (* ~(grad (first params) wrt) ~(second params)) (* ~(first params) ~(grad (second params) wrt)))
				:else (print "poo poo"))
			)
		:else (print "gently caress")
	)
)

(defn eval-fun [fun varmap] (eval (clojure.walk/postwalk-replace varmap fun)))
(defn eval-grad [fun varmap wrt] (eval (clojure.walk/postwalk-replace varmap (grad fun wrt))))
I'm pleased with how small it is, but the operator comparison (or (= op 'clojure.core/+) (= op '+)) looks messy to me.

Is there a consistent way to check operators? Sometimes they seem to go to clojure.core/+, sometimes to +, and sometimes to #<native method blah blah.>

Jo fucked around with this message at 23:48 on Mar 10, 2016

9-Volt Assault
Jan 27, 2007

Beter twee tetten in de hand dan tien op de vlucht.
If people are looking for some lunchtime videos to watch, both Erlang Solutions 2016 and Lambda Days 2016 recently took place and videos of the various talks have been posted on the Erlang Solutions youtube channel: https://www.youtube.com/user/ErlangSolutions/videos.

And there was also a virtual F# conference whose videos can also be watched now: https://channel9.msdn.com/events/FSharp-Events/fsharpConf-2016

Banned King Urgoon
Mar 15, 2015

Jo posted:

I'm pleased with how small it is, but the operator comparison (or (= op 'clojure.core/+) (= op '+)) looks messy to me.

Is there a consistent way to check operators? Sometimes they seem to go to clojure.core/+, sometimes to +, and sometimes to #<native method blah blah.>

This is a job for case:

code:
(case op
  (clojure.core/+ +) `(+ ~@(map #(grad % wrt) params))
  (clojure.core/* *) `(+ (* ~(grad (first params) wrt) ~(second params))
			 (* ~(first params) ~(grad (second params) wrt)))
  "poo poo")

Pollyanna
Mar 5, 2005

Milk's on them.


Okay, I'm having trouble writing idiomatic Clojure code in terms of architecture and top-down vs. bottom-up programming. I'm scaling way the gently caress back and just making a plain library that plays War.

The idea is that there is one data structure: the game.

Lisp code:
(ns libwar.domain)

(def war-example
  {:p1 {:deck [{:type :red
                :value 1}
               {:type :black
                :value 2}
               {:type :red
                :value 3}]
        :cards []}
   :p2 {:deck [{:type :black
                :value 1}
               {:type :red
                :value 2}
               {:type :black
                :value 3}]
        :cards []}
   :state :peace})
A game is just a map with two players keyed by ID, and a game state (:peace, :war, :game-over). A player is a map of their deck, and their cards on the field. Their decks and field cards basically behave like stacks. That's it. That's the only data structure that exists, and the bare minimum of information I need. I have no idea if this is the best data structure to use, but gently caress it, it works.

The hard part is coming up with the functions.

I want to just do something like this to advance the game state one tick:

Lisp code:
(defn tick-game [{:keys [state] :as game}]
  (case state
    :peace (peace-transition game)
    :war (war-transition game)
    :game-over (reset-game game)))

;; e.g. (tick-game game)
That's reasonable, right? All I need to do is implement the state transitions for a given state.

Lisp code:
(defn reset-game [game]
  war-example)

(defn war-transition [{:keys [p1 p2] :as game}]
  (if (evenly-matched? p1 p2)
    (play-cards game 3)
    (assoc (resolve-match game) :state :peace)))

(defn peace-transition [{:keys [p1 p2] :as game}]
  (if (or (no-cards? p1) (no-cards? p2))
    (assoc game :state :game-over)
    (assoc (play-cards game 1) :state :war)))
This involves some querying and updates to the data structure, so we also need to implement those functions.

Lisp code:
(defn evenly-matched? [p1 p2]
  (= (top-card-value p1) (top-card-value p2)))

(defn no-cards? [p]
  (= 0 (count (:deck p))))

(defn resolve-match [{:keys [p1 p2] :as game}]
  ; TODO: make this less awful
  (let [p1-card (first (:cards p1))
        p2-card (first (:cards p2))]
    (if (> p1-card p2-card)
      (-> game
          (update :p1 #(award-winnings % (:cards p2)))
          (update :p2 #(assoc % :cards [])))
      (-> game
          (update :p2 #(award-winnings % (:cards p1)))
          (update :p1 #(assoc % :cards [])))))
  )

(defn play-n-cards [n {:keys [deck] :as p}]
  (-> p
      (update :deck #(drop n %))
      (update :cards #(concat (vec (take n deck)) %))))

(defn play-cards [game n]
  (-> game
      (update :p1 #(play-n-cards n %))
      (update :p2 #(play-n-cards n %))))
Which also require these utility functions:

Lisp code:
(defn top-card-value [p]
  (:value (first (:cards p))))

(defn award-winnings [{:keys [deck] :as p} cards]
  ;; TODO: make less awful
  (-> p
      (assoc :deck (vec (concat deck (shuffle (concat (:cards p) cards)))))
      (assoc :cards [])))
And that should work. (tick-game war-example) results in more or less what I want, barring some vec-related bullshit.

You can tell that I'm not terribly satisfied with the results. Sure, it theoretically works, but I'm not sure how to test and debug it. lein repl doesn't seem to have any step-debugging or breakpoints, so I have to rely on println, which sucks butt. And in terms of application architecture, this ended up being top-down as opposed to bottom-up, because that's just how I'm used to doing things. It's a lot more straightforward to go from a full representation of the game as opposed to individually defining cards, then decks, then players, then the game. I'm also realizing that most of these functions should be marked private, outside of tick-game.

Plus, some of them are just plain awful. award-winnings and resolve-match are particularly ugly, and I don't feel like I understand Clojure (or Lisp in general) well enough to write these functions right the first time.

What am I missing here? Did I take a sensible approach to developing this program? What should my workflow be? What do I need to learn to be good at this language, and make things that are better than just endlessly nested functions on a data structure or two? I see people putting together loving, like, graphs and nodes and channels and systems everywhere, and I have no idea how I'd do that in Ruby, let alone Clojure. :psyduck:

QuantumNinja
Mar 8, 2013

Trust me.
I pretend to be a ninja.

Pollyanna posted:

You can tell that I'm not terribly satisfied with the results. Sure, it theoretically works, but I'm not sure how to test and debug it. lein repl doesn't seem to have any step-debugging or breakpoints, so I have to rely on println, which sucks butt. And in terms of application architecture, this ended up being top-down as opposed to bottom-up, because that's just how I'm used to doing things. It's a lot more straightforward to go from a full representation of the game as opposed to individually defining cards, then decks, then players, then the game. I'm also realizing that most of these functions should be marked private, outside of tick-game.

Plus, some of them are just plain awful. award-winnings and resolve-match are particularly ugly, and I don't feel like I understand Clojure (or Lisp in general) well enough to write these functions right the first time.

What am I missing here? Did I take a sensible approach to developing this program? What should my workflow be? What do I need to learn to be good at this language, and make things that are better than just endlessly nested functions on a data structure or two? I see people putting together loving, like, graphs and nodes and channels and systems everywhere, and I have no idea how I'd do that in Ruby, let alone Clojure. :psyduck:

One thing I've found invaluable for Scheme is trace-define, and it looks like Clojure has its own version. Look at that for debugging.

The architecture is close, IMO. Here are some suggestions.

I'd expect resolve-match to look more like this, though (keeping in mind I know Scheme but not Clojure so the syntax probably isn't right):

code:
(defn resolve-match [game]
  (let [(cards game) (next-set game)] # hands back the cards and removes them from the player's decks, shadowing the previous binding of game
    (cond
      (evenly-matched? cards) (add-p1-spoils (list (first cards)) (add-p2-spoils (second cards) game))
      (p1-wins? cards)        (add-p1-spoils cards game)
      else                    (add-p2-spoils cards game))))
Then add-x-spoils does the obvious thing. Bonus points if you write add-spoils that takes either :p1 or :p2 as an argument. Then the main loop is something like:

code:
(defn play-war [{:keys [p1 p2] :as game}]
    (if (or (no-cards? p1) (no-cards? p2))
      (let [game (play-war (claim-spoils game))]
        (if (or (no-cards? p1) (no-cards? p2))
             (set-game-state :game-over game)
             (play-war game))
      (play-war (resolve-match game)))
You can get rid of the need for both recursive calls to play-war by adding a game state option that represents that you've already reshuffled and one of the players is still cardless, like:

code:
(defn play-war [{:keys [p1 p2 game-state] :as game}]
    (let [shuffle? (or (no-cards? p1) (no-cards? p2))]
       (if (and shuffle? (eq? game-state :init-game))) # we just started over, and one player has no cards
           (set-game-state :game-over game)
           (play-game (if shuffle? (claim-spoils game) game))))# now claim-spoils also resets the game state to init-state probably
That's what I'd do, anyway, as a Scheme programmer. Maybe wait for someone who does Clojure here more to weigh in.

Zemyla
Aug 6, 2008

I'll take her off your hands. Pleasure doing business with you!

gonadic io posted:

Then you'd have 5(?) problems. Lenses have 5 type param these days right?

Apparently, one of the lenses they were going to implement started taking type arguments in the form i m a s t a b u. They took the hint and decided not to implement it.

Magissima
Apr 15, 2013

I'd like to introduce you to some of the most special of our rocks and minerals.
Soiled Meat
Is this code readable by functional standards? I'm in the middle of writing my first non-trivial project in haskell and I don't know if it's total spaghetti or I'm just not used to reading functional code. I know it would be less clunky if I used a more appropriate data structure but that's what I've got to work with for now.

code:
getGraphFromFile :: String -> [[Int]]
getGraphFromFile text = withoutIndex
  where (header:edges) = map (map read . words) . lines $ text
        numVertices = head header
        empties = foldr (\_ acc -> []:acc) [] [0..numVertices - 1]
        explicitIndex = zip [0..length empties] empties
        addEdge value index acc = (fst . (!! index) $ acc,
                                    (:) value . snd . (!! index) $ acc)
        result = foldr (\(first:(second:_)) acc ->
                        sortBy (\(i, _) (j, _) -> compare i j) .
                        nubBy (\(i, _) (j, _) -> i == j) .
                        (:) (addEdge first second acc) .
                        (:) (addEdge second first acc)
                        $ acc) explicitIndex edges
        withoutIndex = map snd result
If you're curious, it takes a file with a list of edges and turns it into an adjacency list representation of a graph.

e: noticed a mistake right after I posted :v:

Magissima fucked around with this message at 18:01 on Mar 13, 2016

gonadic io
Feb 16, 2011

>>=
I don't know about the structure of the code as a whole, but I'd probably clean it up to be like this:

code:
getGraphFromFile :: String -> [[Int]]
getGraphFromFile text = map snd result
  where ([numVertices]:edges) = map (map read . words) (lines text)
        empties = replicate [] numVertices
        explicitIndex = zip [0..] empties
        addEdge value index acc = (fst . (!! index) $ acc,
                                    (:) value . snd . (!! index) $ acc)
        result = foldr (\(first:(second:_)) acc ->
                        sortBy (comparing fst) .
                        nubBy ((==) `on` fst) .
                        (:) (addEdge first second acc) .
                        (:) (addEdge second first acc)
                        $ acc) explicitIndex edges
but honestly that giant lambda in a fold, as well as the use of (!!), is a huge red flag. I recommend using Data.Map instead of [(Int, Int)] as your main data structure and seeing if that's nicer.

Magissima
Apr 15, 2013

I'd like to introduce you to some of the most special of our rocks and minerals.
Soiled Meat
I think you're right that the problems originate from the data structure. The reason I have it that way is that this is from an assignment that was originally meant to be in java until the professor changed his mind and said we could use whatever, so I just directly took the java data structure and used that. It's obvious to me now that it makes a lot more sense to have a meaningful list index when you're working with loops than with functions, so lesson learned on that front. Thanks for the feedback!

QuantumNinja
Mar 8, 2013

Trust me.
I pretend to be a ninja.

Puella Magissima posted:

I think you're right that the problems originate from the data structure. The reason I have it that way is that this is from an assignment that was originally meant to be in java until the professor changed his mind and said we could use whatever, so I just directly took the java data structure and used that. It's obvious to me now that it makes a lot more sense to have a meaningful list index when you're working with loops than with functions, so lesson learned on that front. Thanks for the feedback!

The data structure isn't even the problem. You can do this just fine without explicit indicies and still get what you want because your edges are the indicies. Assuming an input that looks like:

code:
5
1 2
2 4
3 4
this code yields the same output as the original code you posted:

code:
getGraphFromFile :: String -> [[Int]]
getGraphFromFile text = result
  where
    ([vertcount]:edges) = (map (map read . words) $ lines text) :: [[Int]]
    empties             = replicate vertcount [] 
    addEdge value       = updateWith ((:) value)
    result              = map (nub . sort) $ foldr (\ [v1, v2] -> addEdge v2 v1 . addEdge v1 v2)
                                                   empties edges
    updateWith :: (a -> a) -> Int -> [a] -> [a]
    updateWith f 0 (y:ys) = f y : ys
    updateWith f n []     = []
    updateWith f n (y:ys) = y : updateWith f (n - 1) ys 
You don't need the indicies at any point, and updateWith does the bulk of the work.

If you need node names for some reason (like strings or something), just zip them with numbers, build a list of number-number edges, run this, then translate them all back.

gonadic io is right, though, that this should really use Data.Map (or at least a vector) because this sort of implementation is comparatively inefficient.

Magissima
Apr 15, 2013

I'd like to introduce you to some of the most special of our rocks and minerals.
Soiled Meat
Whoa, that's way better than what I had. I don't think it even occurred to me to recurse to the correct list instead of manually indexing, but when you put it like that it makes total sense. I guess I'm still not used to thinking functionally. I probably should have come here before spending an hour banging my head against the wall coming up with my lovely solution. Thanks a lot for your help. I'll look into Data.Map as well.

gonadic io
Feb 16, 2011

>>=
I disagree with your last point - you "banging your head" was you learning Haskell, and you seeing QuantumNinja's solution without having come up with your own wouldn't be nearly as helpful.

It'd, of course, be entirely different if you just needed a solution and didn't care how it worked. In that case I'd recommend that you use Data.Graph instead of reimplementing it.

AWWNAW
Dec 30, 2008

Reading The Little Schemer helped me think more "functionally" and that was before I even started using functional languages for real.

Adbot
ADBOT LOVES YOU

HappyHippo
Nov 19, 2003
Do you have an Air Miles Card?

AWWNAW posted:

Reading The Little Schemer helped me think more "functionally" and that was before I even started using functional languages for real.

I also found Scheme to be a good intro, although I read SICP (or part of it anyway).

  • Locked thread