This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-04-07
Channels
- # announcements (1)
- # babashka (77)
- # beginners (219)
- # chlorine-clover (6)
- # cider (52)
- # clj-kondo (14)
- # cljdoc (5)
- # clojure (173)
- # clojure-europe (49)
- # clojure-finland (1)
- # clojure-germany (2)
- # clojure-italy (1)
- # clojure-nl (39)
- # clojure-sweden (4)
- # clojure-uk (51)
- # clojurescript (25)
- # code-reviews (9)
- # conjure (25)
- # data-science (2)
- # figwheel-main (6)
- # fulcro (74)
- # graalvm (1)
- # graphql (11)
- # jobs-discuss (17)
- # keechma (4)
- # lein-figwheel (4)
- # leiningen (1)
- # luminus (10)
- # malli (14)
- # mid-cities-meetup (2)
- # off-topic (28)
- # re-frame (5)
- # reagent (76)
- # reitit (30)
- # ring (7)
- # ring-swagger (1)
- # shadow-cljs (163)
- # spacemacs (11)
- # specter (2)
- # sql (43)
- # tools-deps (13)
- # vim (6)
- # yada (1)
Greetings! I read Joy of Clojure, 2nd ed. (JoC) cover-to-cover back in 2014 when it came out — helped me a lot in my clojure journey. In 2017, various happenings pulled me away from clojure and back to node.js, python, etc. As of today, my team of 6 (myself being the only one w/ prior clojure experience) needs to ramp up fast on the core clojure lang (broader ecosystem not so important, for now). Any recommendations re: books, etc.? Is there a book/site considered the “successor” to JoC? I’m mainly thinking of my teammates, i.e. how much I appreciated JoC helping bootstrap me into clojure and functional programming. I can personally sort out what’s changed/added since 2017, but I’d like to give them some good resources for starting from scratch; they’re all accomplished in JS and some other langs, but none of them has touched a Lisp.
Hi Michael, I'd suggest these 2 courses for quickest dive into to the core concepts: 1 - https://purelyfunctional.tv/courses/3-functional-tools/ 2 - https://purelyfunctional.tv/courses/clojure-combinators/
Programming Clojure 3rd ed is probably the most up to date (1.9 era)
aside: I still fondly remember Strange Loop 2011 where I completely-accidentally ended up going to dinner at Pappy’s Smokehouse on Olive (in St. Louis City) with Dr. Sussman and a random group of attendees — he came out of some side-room, saw a group of us standing around, and asked us if we’re hungry and whether we would like to have dinner with him.
![parrot](https://emoji.slack-edge.com/T03RZGPFR/parrot/d3b061bf9cff6f2e.gif)
another newbie question: if i wanted to throw an exception AND return a custom json response, how would i do that?
ex-info
is usually used to pack in more info along with the exception. You can retrieve the info back with ex-data
.
https://clojuredocs.org/clojure.core/ex-info
https://clojuredocs.org/clojure.core/ex-data
Yup, what he said. ex-info
is designed for "information-bearing exceptions" and this sounds like a good fit.
(and the latest version of Clojure adds ex-message
as a standardized way to get the message from any exception)
If ex-info
is for information-bearing exceptions, the answer for non-information-bearing exceptions are classic java exceptions?
What I mean, is that when throwing my own errors, are there situations where I should use java exceptions (like this one for example https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/NoSuchFieldException.html), or should I always use ex-info
for my own exceptions?
It depends. You could, e.g. thrown an IllegalArgumentException
when you receive an invalid input data although it's not very common. However, if you feel like you want to create your own exception class then you should use ex-info
instead, imho.
@U7S5E44DB if an existing Java exception matches the problem you want to report -- and you don't need to convey any additional information -- them it's reasonable to throw the Java exception.
Just remember that exceptions are not intended for "expected error conditions" or flow of control -- they're for "I can't handle this!" situations.
awesome advice everyone! i was looking at ex-info
yesterday and it does throw the exception with the info i provided however i would like the exception thrown and also a response like
{
"status": "4xx",
"message": "My Custom Message Here"
}
but i am just seeing an HTML response of the exception being returnedI think ex-data
is what you need. Whatever you return from the HTTP handler will be sent back to the browser.
ok so after looking at this more i THINK what i really want is clojure's spec
functionality to do validation on incoming client requests
@U0D5YAGUX That's one of the ways we use Spec -- we write specs for our API's input parameters and then we s/conform
the params and check if s/invalid?
-- if it is, we respond with an s/explain-data
mapped to client error messages, else we used the conformed parameter data.
We opened sourced some of our low-level spec utilities for dealing with API parameters https://github.com/worldsingles/web-specs
@seancorfield do you have any examples of how to use web-specs?
(s/conform ::some-spec some-value)
Then check s/invalid?
on the result. Just like you'd use any other spec.
Not sure what to say beyond that. They're just specs.
@U0D5YAGUX Have a look at the tests and let me know if you still have questions https://github.com/worldsingles/web-specs/blob/master/test/ws/web/spec_expectations.clj
You can also use s/valid?
with the spec directly, but that doesn't give you any conforming of input data.
Hi, I am getting namespace not available in some template code where all I did was that I renamed the existing files and namespaces to something new. Now, I can just restart from scratch and be more careful to avoid this situation, but I figure there should be some way to debug this, right? So what can I check to fix such an error? I tried to read docs but that just explains at length what is happening when everything is working, but there is not a word anywhere on what to do when anything is not as is intended
Hi Michael, I'd suggest these 2 courses for quickest dive into to the core concepts: 1 - https://purelyfunctional.tv/courses/3-functional-tools/ 2 - https://purelyfunctional.tv/courses/clojure-combinators/
$ lein new lib foo
Failed to resolve version for lib:lein-template:jar:RELEASE: Could not find metadata lib:lein-template/maven-metadata.xml in local (/Users/nick/.m2/repository)
Failed to resolve version for lib:lein-template:jar:RELEASE: Could not find metadata lib:lein-template/maven-metadata.xml in local (/Users/nick/.m2/repository)
This could be due to a typo in :dependencies, file system permissions, or network issues.
If you are behind a proxy, try setting the 'http_proxy' environment variable.
Could not find template lib on the classpath.
$ lein new app foo
Generating a project called foo based on the 'app' template.
Does anyone know why lib lein template is the only one that's falling?
I suspect they're using different templates between lib
(explicit) and default/implicit.
I'm looking at the code in core.clj for clojure-1.10.0. I notice the following function definition:
(defn decimal?
"Returns true if n is a BigDecimal"
{:added "1.0"
:static true}
[n] (instance? BigDecimal n))
Is this correct? is BigDecimal
the only type of decimal number which clojure supports?For example, this causes 1.0 not to be rational? (rational 1.0)
returns false
, while (rational 1)
and (rational 1/3)
both return true
.
more playing with types, It is a surprise that (symbol? :x)
returns false
. In clojure, keywords are not considered to be symbols?
Keywords and symbols are disjoint sets of values in Clojure.
The literal 1.0
in source code is a Java Double
type, a 64-bit IEEE 754 floating point number. Thus it can only exactly represent a "few" fractions exactly.
decimal?
is just Clojure's predicate function to determine if a number is BigDecimal
or not. Java integer and Clojure's Ratio
type are also exact numeric representations with no loss of precision. Not sure if that answers any of your questions, though.
I have a problem I don't know how to approach in Clojure. I have a dynamic variable, in my case it's named *rte-known*
. It has a default value which is a map (hashmap). The intent is that an application may rebind this variable, and extend it using a call to assoc
to establish more known values for a certain dynamic extent. That works find and great. However, during the ensuing computations, I'd like to memoize some expensive computation results, and have them go away when the user's binding of *rte-known*
goes away.
If I attempt to use a global mutable object, then after the user's dynamic binding finishes its dynamic extent, then my globally memoized data is likely to be invalid.
I think this means that I want to associate a mutable value with a dynamic binding. Is this possible?
The only way I know of doing this, as to provide a macro which the caller is expected to use to rebind *rte-known*
. And that macro would bind two values, *rte-known*
and a secret mutable object.
Is that the correct way? Or is there a better way?
(defn expensive-computation* [_ a b c] ...)
(def expensive-computation (memoize (fn [a b c] (expensive-computation* *rte-known* a b c)))
perhaps?
If I understand your problem correctly then you can bind the dynamic variable to an e.g. atom to hold the computation state
@UTQEPUEH4 looking at the implementation of memoize
, I doubt your suggestion will work. It seems memoize creates a closure, which closes over an atom on which it uses as a memoization table for that function. this memoization table remains until the closure is garbage collected, if ever. It is not recreated and released when the user binds a particular named variable.
in stead, according to my interpretation of the function, if the value of goes out of dynamic scope, then these values will stay in VM, memoized on that secret atom, until the JVM session ends.
@UKQS17P3L, I think that means I'd implement a macro for the user to use to rebind the value. the user would not call assoc
directly, but would call my with-known-values
macro.
it's a good idea, saving me the effort of implementing a memoization protocol.
Glad I asked this question, as I didn't know about memoize, and its a function I've written from scratch multiple times in other lisps. The documentation says it is used to memoize the return values of a pure function. However, the introduction of a dynamic value violates this assumption. i.e., the return value now depends on both the arguments and the dynamic extent.
Of course the memoization store remains; but if your dynamic var has changed since something was cached there, those cached results will not be used for the next calls
I've got the code wrong though, of course. Should be something like
(def expensive-computation* (memoize (fn [_ a b c] ....)))
(defn expensive-computation [a b c] (expensive-computation *your-dynamic-var* a b c))
I'm not sure about dynamic vars, but if your top level map was an atom, you could set up a watcher and reset! another atom with the cached data as soon as your first atom is blank
Hello, can somebody explain why the function conj applied to a list conjoins at the beginning but when I use it on vector it appends it at the end, thanks!
It's because of the differing implementation details of those collections. You can add things onto either the front or back of either datatype, but conj uses the faster operation.
thank you very much!
Can I implement a new def without a macro? I think I need a macro to write stuff like (defrule burrows-wheeler {})
where burrows-wheeler is undefined.
in general, defwhatever is usually expect to be something that defs a var and is typically a macro that emits some other def form (def, defn, etc)
Thanks. First time I've had use for (writing) a macro. Finally XD
it's probably worth looking at the source
for some other def macros
(defmacro defrule
[name val]
`(def ~name ~val))
Thanks 🙂
Question about running tests (specifically with lein test
in my case) - I am structuring some tests like so
(deftest addition
(is (= 4 (+ 2 2)))
(is (= 7 (+ 3 4))))
(deftest subtraction
(is (= 1 (- 4 3)))
(is (= 3 (- 7 4))))
(deftest arithmetic
(addition)
(subtraction))
It appears however that the default test selector causes each deftest to run, causing the same tests to be run multiple times. Is the conventional approach to annotate arithmetic
with some a metadata key like ^:regression
, and in my project.clj
configure lein to only run tests tagged ^:regression
? I feel like I also might not be understanding how clojure.test
works, and am duplicating my tests on accident. Am I composing my tests incorrectly? Do I need the metadata / lein configuration? Thank youaddition
and subtraction
can just be functions
calling a function with is
calls inside inside a test just works
calling a deftest inside a deftest seems like it would never be a good idea
which docs?
right - I think functions are more likely to allow the behavior you want, I can't think of a usecase where you'd both define a test (which means clojure.test would run it when testing an ns) and also call it from another test
unless you were using binding
to set dynamic vars, or calling tests from another ns
That is odd that that example is in the docs.
So how would you have nested tests, if deftest
isn't the way to go?
From the docs:
(deftest arithmetic
(addition)
(subtraction))
> The names of the nested tests will be joined in a list, like
> "(arithmetic addition)", in failure reports. You can use nested
> tests to set up a context shared by several tests.I don't understand the premise, I have never needed a test that invoked other tests, I use the testing
macro for that
(ins)user=> (source clojure.test/testing)
(defmacro testing
"Adds a new string to the list of testing contexts. May be nested,
but must occur inside a test function (deftest)."
{:added "1.1"}
[string & body]
`(binding [*testing-contexts* (conj *testing-contexts* ~string)]
~@body))
nil
OK, that works. Thanks.
I dunno how I missed that.
Really I just want to group similar tests together. I may have confused myself with the examples in the docs.
From the deftest
docs:
>
> Usage: (deftest name & body)
> Defines a test function with no arguments. Test functions may call
> other tests, so tests may be composed. If you compose tests, you
> should also define a function named test-ns-hook; run-tests will
> call test-ns-hook instead of testing all vars.
Suggests you create your own function test-ns-hook
to call just the tests that should be called, to avoid double running.
I ended up doing something akin to
(deftest related-features
(testing "Feature A"
(test-feature-A)
(test-feature-A-adjacent))
(testing "Feature B"
...))
where (test-feature-X) are just functions using the is
macro. Works fineHi, is it acceptable to share a let
binding with two defn
s?
(let [x (atom 10)]
(defn f [] (inc @x))
(defn g [] (dec @x)))
I mean, these are just examples. Actually I want to share a websocket connection with two functions.
the idiomatic thing is to make x a def - normal clojure code doesn't do much data hiding
so if you want to decompose things like that the thing to do is to make addition and subtraction a regular defn
I am a little confused about swap!
. The docstring states: "Atomically swaps the value of atom to be (apply f current-value-of-atom args)
"
However, (swap! (atom 0) max [])
throws, while (apply max @(atom 0) []) => 0
. Is the simplest way to get around this just to use reset!
with the current state of my atom (`(let [a (atom 0)] (reset! a (apply max (conj [] @a))))`)?
because args is the list of arguments, and your list of arguments contains one thing (an empty vector)
ah right, thank you
I want to use (apply swap! ...)
probably not, you want to use swap! your-atom max
(if you really want to call max
with just one argument, that is the value of your atom)
if you have a coll and want to swap! to assign to the max of current value / values in coll, (apply swap! a max coll)
makes sense
(cmd)(user=> (def alternates (repeatedly 10 rand))
#'user/alternates
(ins)user=> alternates
(0.9628561654175068 0.11902505003214825 0.3835041588382112 0.5507751502080879 0.18499959134452892 0.013132287289970068 0.00873908073221663 0.890933501821531 0.8008623048866589 0.9502918850062402)
(ins)user=> (def a (atom 0))
#'user/a
(ins)user=> (apply swap! a max alternates)
0.9628561654175068
(ins)user=> @a
0.9628561654175068
yup, that's precisely what I'm doing!
kind of looks like a pasting artifact from emacs?
my readline config asks for the vi mode of my prompt to be visible
Is there any way to tell leiningen to install a package from the command line similar to how npm
works (ie npm install react
=> lein install cljfmt)
? It kinda sucks to have to manually edit project.cjs every time you want to add a dep
this is fundamentally opposed to the premise of lein (and other m2 based dep managers like maven, gradle, and deps.edn) the goal is that every dep is project local and versioned, so you never break a project by changing a dep for another project, you can't do this if you are using global installs
that said, you can create a profile in ~/.lein/profiles.clj
that automatically includes the deps you want when experimenting and not committed to a specific project
that way you can opt-in via lein's with-profile
to add the deps to a repl, without polluting all projects
In the case of cljfmt
, the readme describes tools.deps settings that can be wrapped in an alias/script and used independently of lein
% cat ~/bin/cljfmt
#!/bin/sh
clojure -Sdeps '{:deps {cljfmt {:mvn/version "0.6.4"}}}' \
-m cljfmt.main $*
% cljfmt check core.clj
and tools.deps / deps.edn has its own user config for opting into a set of deps for your repl or task via a profile (using the terse -A flag instead of lein's verbose -with-profile)
A util that added the latest dep version as a line to your deps.edn or project.clj might be a nice convenience
% cat ~/.clojure/deps.edn
{:aliases
{:cljfmt {:extra-deps {cljfmt {:mvn/version "0.6.4"}}
:main-opts ["-m" "cljfmt.main"]}}}
% alias cljfmt='clj -Acljfmt'
% cljfmt check run.clj
...
So there are a few issues here that have been brought up. However, my original question was not relating to those (although thank you for the information).
The two issues are: 1) Global vs Local installs. Sorry I was not clear, but I was explicitly referring to Local dependencies and the convenience of installing them via command line.
In other words, I was hoping there was a simple lein task/command to install local dependencies in your project.clj via command line. Ala npm
npm install react
This automatically updates the project-local package.json
with the latest react
package dependency.
Similarly I was hoping something like:
lein install clfmt
Automatically updates the :dependency
value in project.clj
with the latest version of cljfmt
However, there doesn't seem to be anything like this (at least from what I've googled). Which is a bummer
To the latter point, I have seen lein ancient
https://github.com/xsc/lein-ancient that can find deps in your project that can be updated.
As to adding deps from the command line... it just doesn't seem like something lein is trying to be. But lein is also extensible; maybe someone has/will do it in a plugin. :man-shrugging:
this tool seems to do what you want for deps.edn https://github.com/hagmonk/find-deps
and you can tell lein to use deps.edn, so that should be what you want? add find-deps to your personal deps.edn under the alias find-deps then follow the instructions on the readme
@U050MP39D Which editor do you use ?
2/3 months ago I was looking for the exact same answer and today I actually use find-deps but also clj-refactor
in Emacs / CIDER which allows me to add the libraries via the cljr-add-project-dependency
command like this:
By the way, it works for Leiningen and project.clj too
Direct visualization in GIF
if I were writing enough clojure to warrant it I'd be using cursive. but atm I just use command line repl + occasional vim, so find-deps via clj -A:find-deps -F:save
works for me
I'm trying to write a GUI in Clojure, and it seems like I need to pick between JavaFX and Electron bindings. What are the pros or cons of each? (I don't know either framework, so I'm going to have to learn one of them)
there’s also membrane, https://github.com/phronmophobic/membrane although it’s still in beta
Hi! I wrote cljfx, and I’ve been trying to make somewhat unbiased comparison between that and electron after I wrote Year of Clojure on the Desktop (https://vlaaad.github.io/year-of-clojure-on-the-desktop) and received a lot of criticism from the js devs 🙂 In short: - both cljfx and electron are resource hogs (cpu and memory). - Clojure has better language semantics than ClojureScript: can define macros in the same compilation unit, not hindered by underlying javascript vm semantics. - Clojure is more performant: while modern js is actually multithreaded, and this multithreading is as easy to use as promises, which in turn super easy to use in core.async, js semantics require data serialization between threads, while JVM is able to share it. - both js and java library ecosystems are huge, you can find whatever library you need. java ecosystem is more mature: less breakage of maven itself, less breakage in individual libraries - industry support is better in js world. JavaFX is developed by small team at Oracle/Gluon. Cljfx is maintained by one guy who hangs out on clojurians slack all the time. Electron and js has much more stakeholders.
Also, for very simple GUIs, Seesaw works pretty well: https://github.com/daveray/seesaw
@U47G49KHQ I think #1 should be investigated further. My gut feeling is cljfx wouldn't use as much CPU, and memory should be a constant overhead, but might be wrong.
I know, because my laptop only has 4GB RAM, and no swap 😛 I can't even run Google Chrome without it trashing
the differentiator for membrane is that many other gui libraries start with some underlying tech (javafx, the DOM, swing, cocoa, etc) and build up. membrane tries to take th opposite approach. think “what would be the ideal?” approach and build down. think it of it like ring for guis. the same ui code can the run on top of multiple implementations
so it’s in a weird spot where it’s not mutually exclusive with other frameworks and libraries
I think that approach might have some drawbacks: it has to be lowest common denominator, right?
btw, I’ve been recently checking out cljfx and it’s very cool
@U0K064KQV you could try clj -Sdeps '{:deps {hn {:git/url "
there’s nothing getting in the way if you want to have platform specific specializations
and if we’re comparing it to the DOM in the browser and javafx, then we’re already in lowest common denominator territory
> there’s nothing getting in the way if you want to have platform specific specializations but then you are effectively tied to that platform?
yes, that’s the tradeoff, but since the whole stack is in clojure, it’s a lot easier
hmm, I think that might be useful when you need to target multiple stacks at the same time
but i’m totally with you on getting a lot of push back from the crowd that’s used to the web
we’re on the same team!
Hum, so REBL does run with Xmx100m, and doesn't OOM, but you're right, process memory is still around 600mb
thanks for verifying! I think cljfx/hn might OOM depending on the size of pages you open, since it additionally embeds a browser…
I think to me, a JVM based option does feel an easier environment then trying to do Electron
The last point in your comparison might actually be in favor of cljfx (only half joking). There are less people to help maintain the project, true, but also less people making a mess and deviating from the founder's vision
I've wanted to check out Cljfx for a while. It's a Java FX wrapper with a react-like API. Seems simpler to work with than dragging in all of Electron. https://github.com/cljfx/cljfx
There was a Hacker News frontpage post about it a week or so ago, but I can't seem to find it.
If you go with JavaFX/Cljfx, you’re building a Java-based app in Clojure, but if you go with Electron you’re building a JavaScript-based app in CLJS. That can make a difference in what libraries you have available to build the rest of your app with, apart from the GUI.
(like database engines, etc)
Thanks for the article @teodorlu. I'm probably going with Java, since I can't find a good reason to use Electron, but that decision could change.
I just realized that when clj-new
generates a new app template, it puts a file in resources/.keep
. What is this used for?
it is likely just there for git, because you can't check an empty directory into git
yeah https://github.com/boot-clj/boot-new/issues/30 (clj-new is forked from boot-clj)
Thanks @hiredman
Specifically, with boot
you got an error if a folder was on the list of paths to include but it didn't exist, so you had the situation where you'd create a project (with an empty resources
folder) and it would all work locally, then you'd commit it to git and someone else would clone it -- and the empty folder would not be there because of git's policy on empty folders -- and then you would get an error trying to run the project.
It doesn't affect clojure
CLI in the same way but it was easier just to keep it in the clj-new
templates so that folks have a resources
folder and the project is more "complete" as a starting point.
there’s also membrane, https://github.com/phronmophobic/membrane although it’s still in beta
Is anyone familiar enough with clojure.tools.cli
who could explain to me how to wire it up to handle subcommands? I'm aware of the :in-order
argument to parse-opts
, and that my subcommand and its options will be returned in the :arguments
key in the parse-opts response (which I'll have to re-parse) but it isn't super clear to me how (or if it is possible) to set up a subcommand on the initial parse-opts invocation. I'd like to show the subcommand in my top level usage summary.
actually after typing that out it dawns on me that I need to just write my own usage summary with my subcommands outlined
or a separate parse-opts config for the subcommands, then a "join" operation
and then just toss the returned arguments into another parse-opts invocation, with a separate set of options for the subcommand
the join op is a good idea as well - can I produce a usage summary from a given set of options without invoking parse-opts?
I saw there was a public summarize function, but that takes only a compiled option spec
I suppose just invoking parse-opts with an empty argument vector & your options and grabbing the returned summary works just fine, just seemed slightly heavy
> The default summary function `clojure.tools.cli/summarize` is public and may be useful within your own `:summary-fn` for generating the default summary.
looks like it's worth checking at least
Given that there have been simultaneous questions about tools.cli
in both #beginners and #clojure just now, that's definitely an indication I need to spend a fair bit of time expanding the documentation! 😐
I suspect adding several example programs (in the tests) would also help people out a lot...
It really doesn't help that it has two completely different versions of argument handling in one file either (because the deprecated legacy handling was never actually removed).
@seancorfield I think my unfamiliarity with the command line nomenclature is also not helping - arguments, options, argument options, etc etc
The sample program was a great help - I just realized I wanted to turn my required option into a subcommand 🙂
I think if there was some "post-parse" phase that supported additional validation in a standard way, it would help with a lot of these things, as well as better support for subcommands (which would recursively apply tools.cli
on the :in-order
arguments).
I haven't given that library much love lately but it was already pretty solid when I took it over in 2015...
(and most of the first release I did was just documenting what Sung had already implemented but had gotten around to releasing as 0.3.2)
Hi! I wrote cljfx, and I’ve been trying to make somewhat unbiased comparison between that and electron after I wrote Year of Clojure on the Desktop (https://vlaaad.github.io/year-of-clojure-on-the-desktop) and received a lot of criticism from the js devs 🙂 In short: - both cljfx and electron are resource hogs (cpu and memory). - Clojure has better language semantics than ClojureScript: can define macros in the same compilation unit, not hindered by underlying javascript vm semantics. - Clojure is more performant: while modern js is actually multithreaded, and this multithreading is as easy to use as promises, which in turn super easy to use in core.async, js semantics require data serialization between threads, while JVM is able to share it. - both js and java library ecosystems are huge, you can find whatever library you need. java ecosystem is more mature: less breakage of maven itself, less breakage in individual libraries - industry support is better in js world. JavaFX is developed by small team at Oracle/Gluon. Cljfx is maintained by one guy who hangs out on clojurians slack all the time. Electron and js has much more stakeholders.