惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
O
OpenAI News
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
S
Schneier on Security
Latest news
Latest news
F
Full Disclosure
T
Tenable Blog
T
Troy Hunt's Blog
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
L
LangChain Blog
有赞技术团队
有赞技术团队
Project Zero
Project Zero
Cloudbric
Cloudbric
爱范儿
爱范儿
GbyAI
GbyAI
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
The Exploit Database - CXSecurity.com
S
Security @ Cisco Blogs
Hugging Face - Blog
Hugging Face - Blog
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
Last Week in AI
Last Week in AI
C
Cisco Blogs
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V2EX - 技术
V2EX - 技术
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Schneier on Security
Schneier on Security
T
Threat Research - Cisco Blogs
M
MIT News - Artificial intelligence
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
Kaspersky official blog
The Hacker News
The Hacker News
V
V2EX
F
Fortinet All Blogs
L
LINUX DO - 最新话题
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News | PayPal Newsroom
博客园 - 三生石上(FineUI控件)
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

Romes' Blog RSS Feed

Running out of Disk Space in Production How I turned my Anki side project into a Kickstarter: A Walkthrough Haskell Debugger for GHC 9.14 Lazy Linearity for a Core Functional Language (POPL 2026) Automatically Packaging a Haskell Library as a Swift Binary XCFramework Implementing Unsure Calculator in 100 lines of Haskell Calling Haskell from Swift Computed Properties for Haskell Records Creating a macOS app with Haskell and Swift Introducing ghc-toolchain to GHC Writing prettier Haskell with Unicode Syntax and Vim Monthly Update on a Haskell Game Engine Equality Saturation in Haskell, a tutorial Graphical Applications in Haskell with FRP and Reflex Graphical Applications in Haskell with MVC and Gloss Haskell 102 Lecture Notes Haskell 101 Lecture Notes
Planning Weekly Workouts in 100 lines of Haskell
Rodrigo Mesq · 2024-08-14 · via Romes' Blog RSS Feed

I have recently started doing some outdoors bodyweight workouts. I also want to start running again, but I’m recovering from a minor knee injury until the start of next month.

Tonight I decided to put together a weekly schedule to start following next month. The first pen and paper versions were fine, but I wasn’t completely satisfied. The next logical step was to write a quick program to see what possible plans I was missing.

The schedule must satisfy a few constraints, but the core of it is that I should do, every week, on one axis, one short run (high-intensity) and one long run (long distance), and, on the other axis, have two pull days (as in pull-ups), two push days (as in push-ups), and two leg days (as in squats).

Finding a weekly workout that satisfies certain constraints is an answer-set-programming kind of problem, best solved by some kind of logic programming. Rather than turning to Prolog or Clingo, I decided to just stick to Haskell and use the logic-programming monad from logict!

A workout planner in 100 lines of Haskell

What follows is mostly just the demonstration of using logict applied to this particular problem. I believe the weeklySchedule function can be easily understood in general, even by anyone unfamiliar with Haskell and/or logic programming – and that’s the meat of this short post and program.

Note that the program is a cabal shell script which can be run by executing the script file (as in ./ScheduleExercise, as long as cabal is in path). It is standalone, and exactly 100 lines (with comments, shebangs and everything). Feel free to try and modify it!

#!/usr/bin/env cabal
{- cabal:
build-depends: base, logict
-}

import Control.Applicative
import Control.Monad
import Control.Monad.Logic
import Data.List
import Data.Maybe

workout = ["Push day", "Pull day", "Leg day", "No workout"]
running = ["Long run", "Short run", "No run"]
weekdays = ["Seg.", "Ter.", "Qua.", "Qui.", "Sex.", "Sab.", "Dom."]

weeklySchedule :: Logic [(String, [String])]
weeklySchedule = do
  -- For every weekday, pick an element from `workout` and one from `running`
  -- that satisfy the "nested" conditions
  p <- forM weekdays $ \d -> do
    w <- choose workout
    r <- choose running

    -- No running on leg day
    w == "Leg day" ==> r == "No run"

    -- Short intervals run is after an outdoor pull/push workout
    r == "Short run" ==> w /= "No workout"

    -- Workout on Monday outdoors always, not legs
    d == "Seg." ==> w /= "No workout"
    d == "Seg." ==> w /= "Leg day"

    -- Pull day during the week?
    w == "Pull day" ==> (d /= "Sab." && d /= "Dom.")

    pure [w,r]

  -- Now, pick the set `p` of (weekdays X exercises) that satisfy the following conditions:

  -- One long run, one short run
  exactly 1 "Long run" p
  exactly 1 "Short run" p

  -- Two push, two pull, two leg
  exactly 2 "Push day" p
  exactly 2 "Pull day" p
  exactly 2 "Leg day" p

  -- Long run on weekend
  onDay "Long run" "Sab." p <|> onDay "Long run" "Dom." p

  -- Run spaced out at least 2 days
  daysBetween 2 "Short run" "Long run" p
  daysBetween 2 "Long run" "Short run" p

  -- Space out workouts at least 2 days
  spacedOut "Push day" 2 p
  spacedOut "Pull day" 2 p
  spacedOut "Leg day" 2 p

  -- No leg day before short run
  daysBetween 1 "Leg day" "Short run" p
  -- No leg day before a long run
  daysBetween 1 "Leg day" "Long run"  p

  -- At least one of the runs without a leg day after please
  daysBetween 1 "Short run" "Leg day" p <|> daysBetween 1 "Long run" "Leg day" p

  return (zip weekdays p)

--------------------------------------------------------------------------------
-- Logic utils

choose = foldr ((<|>) . pure) empty

exactly n s p = guard (length (filter (s `elem`) p) == n)

onDay s d p = guard (s `elem` getDay d p) where
  getDay s p = p !! fromMaybe undefined (elemIndex s weekdays)

-- space out as at least n days
spacedOut a n p = guard (all (>n) dists) where
  dists = zipWith (-) (drop 1 is) is
  is = findIndices (a `elem`) (take 14 $ cycle p {- cycle week around -})

daysBetween n a b p =
  forM_ (take 14 (cycle p) `zip` [1..]) $ \(x,i) -> do
    a `elem` x ==> all (b `notElem`) (take n $ drop i (take 14 (cycle p)))

(==>) a b = guard (not a || b)
infixr 0 ==>

--------------------------------------------------------------------------------
-- Main

printSched = mapM (\(d, ls) -> putStrLn (d ++ " " ++ intercalate ", " ls))

main = do let r = observeAll weeklySchedule
          mapM (const (putStrLn "") <=< printSched) r

The heavy lifting is done by the logict package. It allows us to consider alternative values as solutions to logic statements. The possible alternatives are separated by <|>, and observeAll returns a list with all valid alternatives. A trivial example:

x = pure "a" <|> pure "b" <|> pure "c"
print (observeAll x)

Result: ["a", "b", "c"]

If the alternative is empty, it is not an answer and therefore not included in the result. The guard combinator takes a boolean and returns empty if it is false, therefore constraining the solution.

x = pure "a" <|> empty <|> pure "c"
print (observeAll x)

Result: ["a", "c"]

Finally, we can use the monadic do notation to combine sets of alternatives. Put together with guard, we can write slightly more interesting programs:

x = do
  y <- pure "a" <|> pure "b" <|> pure "c"
  z <- pure "a" <|> pure "b" <|> pure "c"
  guard (y /= z)
  return (y,z)
print (observeAll x)

Result: [("a","b"),("a","c"),("b","a"),("b","c"),("c","a"),("c","b")]

All in all, the body of weeklySchedule uses just these principles plus a few domain-specific combinators I wrote in the Logic utils section of the code (which themselves use also only these principles from logict, plus some laziness-coolness). The program entry point (main) prints out the schedule.

And by the way, these are the workout schedules satisfying all those constraints:

Seg. Pull day, No run
Ter. Push day, Short run
Qua. No workout, No run
Qui. Leg day, No run
Sex. Pull day, No run
Sab. Push day, Long run
Dom. Leg day, No run

Seg. Pull day, No run
Ter. Leg day, No run
Qua. Push day, No run
Qui. Pull day, Short run
Sex. Leg day, No run
Sab. Push day, No run
Dom. No workout, Long run

Seg. Pull day, No run
Ter. Leg day, No run
Qua. Push day, No run
Qui. Pull day, Short run
Sex. Leg day, No run
Sab. No workout, No run
Dom. Push day, Long run