BuckleScript (OCaml) https://github.com/bloomberg/bucklescript is the only one that produces reasonable output that doesn't require large runtime libraries all the time.
Koka https://github.com/koka-lang/koka is a very interesting research language that focuses on explicitly specifying effects in types. It even deconstructs Haskell's notion of purity into "throws exceptions" + "may never terminate". Unfortunately, the code generation (which BTW exists for both JS and C#) is uh… not very good.
But honestly, I haven't felt the need to use a functional language in frontend development. JavaScript (ES6) is good enough, and it's actually supported in browsers, so you can develop without any compiling, and only use Babel to make production builds. If the project is not small, use TypeScript to add some type safety.
Currently I'm writing in Purescript but I'm constantly checking BuckleScript and I'm liking direction of it. What is OCaml polymorphism story? Does it have analogue of typeclasses? I'm currently using Purescript for writing build steps for webpack and I benefit from Haskell ideas like lenses, refolds and free monads. It would be great if I could transfer those tools. Maybe you could point me to some literature about transforming recursive data structures (ASTs) in OCaml? Maybe thera are some interesting tools and techniques?
"TEA is not composable, but through brute force. ... you are stuck building and maintaining some giant types, and writing a ton of boilerplate. The ways around this problem are fairly weak, and amount to, “don’t write components” ..."
This has basically been my experience with Elm. But don't say that Elm is boilerplate heavy to any elm-acolytes, they will stick their fingers in their ears and go "nananananana"
Anything that is usually solved with something like typeclasses, or module functors, or inheritance can only be solved in Elm with typing lots and lots of concrete implementations.
Other things can't be solved at all, since Elm is so tightly coupled to TEA.
Happy user of typescript here. imo, pure FP is just like pure OO: it's just not very good. Why constraint ourselves? The world is not pure, our programs don't become ANY better because of the IO monad (Task in Elm); Let's stop wankering or at least, just do it for the math and the research value but let's not kid ourselves it's very useful.
As someone who did OO for ten years and is doing FP full time, this statement is absurd.
Why constraint ourselves?
You are not constrained in any way by FP, you just have to make everything explicit so programs are easier to read and reason about. There's nothing I can't do in a pure FP language.
The world is not pure,
The world is not a touring machine either, does this argument have more substance or is it just an offhanded comment...?
our programs don't become ANY better because of the IO monad
Do you actually have any experience with functional programming? Non rhetorical...it's such an odd thing to say.
Programs absolutely become better when you encapsulate IO in a monadic context. The entire point is to demarcate what functions interact with the world and which don't. This makes testing easier, reading code easier, composition easier. What functional language do you have experience with that would lead you to these bizzare conclusions? :)
> The entire point is to demarcate what functions interact with the world and which don't. This makes testing easier, reading code easier, composition easier.
This is 1. a tradeoff , 2. subjective. Words like 'bizarre' aren't really necessary, and come off as feigned surprise (passive aggressive).
You think a function being easier to test (without reaching for extra tools like mocking libraries, presumably), justifies needing to write and understand 'monads' in order to interact with the real world.
Another programmer finds myObject.doExplicitThingToWorld() far more expressive and easy to understand and is very familiar with the mocking tools/practices they need to test that.
As for composition ... well composhmishion. It's so rare you can actually compose your business logic/app-code (beyond the original use-case you wrote the thing for, which you might well have chosen to write in a 'compositional' way).
It's neat for frameworks and libraries to create composable stuff but it just doesn't come up as often as we'd all like to hope in our day-to-day code in my experience. And if you really want to compose something OO absolutely has ways to do it.
(So composition is not the big win over OO some FP fans make it out to be IMO).
Another programmer finds myObject.doExplicitThingToWorld() far more expressive and easy to understand
By definition, I can reason more about my function returning a monadic context than I can about theirs. I can get free theorems from my function, I can see clearly if I'm doing both input/output, (or just output), and I have laws to help me understand how I can compose this function with others. None of that comes for free when side effecting freely.
And again that supposed benefit of yours is entirely subjective, no matter how objective you try to make it sound.
To me that's not a good enough reason to write my code in that style, when to me (subjective), OOP models the world in a way that is more expressive, I/O is not a thing I need to run scared from, and the methods I write that mutate the world do so clearly enough to me, just in their naming.
How is their argument subjective? If I understand your argument correctly, you are saying that for example:
> myObject.doExplicitThingToWorld() [is] far more expressive
But if I put the word "than" after that, consider what you would type next. Are you arguing that "myObject.doExplicitThingToWorld()" is more expressive than "doExplicitThingToWorld(myObject)"? Because you could write code like that in the FP world, and it's basically the same as the OO world, except (assuming immutability) you'll also know that myObject is not changed after that call.
But runT1ME is talking about how the FP equivalent would come with more information ("I can see clearly if I'm doing both input/output, (or just output), and I have laws to help me understand how I can compose this function with others").
This added information is not subjective. There objectively is more information. The stuff you conveyed in your OO code by naming a method "doExplicitThingToWorld()" can also be conveyed in FP code by naming a function "doExplicitThingToWorld()", but the OO world misses out on the information that monads bring (and monads are just an example of this).
Sure, saying that "more information is not necessarily a good thing" is true. But are you arguing in favor of having less information? Because in that case, why isn't procedural-style programming better (subjectively) to you? After all, one could make the case that OO pollutes code with unnecessary information (classes and hierarchies) and that just like how FP overhypes composition, OO overhypes inheritance
> This added information is not subjective. There objectively is more information.
Yeah the overall win is what's subjective. To many, programming in a functional style, and embracing concepts like 'monads', is not appealing, regardless of that additional information.
There are lots of things we could do to add more information to our programs. We could write tooling that religiously enforces a certain amount of commenting for example. which would be 'more information'. But it's debatable/subjective that adds value overall, given the cost to developers.
> But are you arguing in favor of having less information?
Short answer, no, it's the manner in which it's conveyed, and the hoop-jumping required to do so that constitutes the subjective element.
Agreed. I've used many nice libs using various monads, applicative builders, functors, yada yada under the hood; It can make the libs more pleasant to use for an end user for sure. But telling people that you should write EVERYTHING using this style because it's "objectively" superior strikes me as being almost religious.
Surely, you don't need IO monads to write, say, an API server.
At least in Haskell, things like `Monad`, `Functor`, `Applicative`, etc. are just interfaces ("type classes"). You can ignore them if you like, so you don't need IO monads, or any other monads, to write, say, an API server; in the same way that, for example, in Java just because you have an Iterable value doesn't mean you need to iterate over it.
In comparison, `IO` is a type (actually a "type constructor", AKA a generic). Since many useful functions (e.g. `readFile`) return values of this type, it's pretty hard to avoid; although you can probably get quite far by ignoring types completely and having everything inferred.
In that sense you do need `IO` to write, say, an API server; but I think that's pretty reasonable, since you want you server to read input and write output. Whether or not `IO` is a `Monad`, `Functor`, `Monoid`, `Alternative`, etc. doesn't really matter for that.
Of course, you can always wrap your code in `do { ...; }`, cause all the side-effects you like, and ignore the fact that behind the scenes it just-so-happens that you're using a monad; after all, that's what pretty much all imperative languages do!
More seriously, I think that the Haskell community should try to avoid this habit of saying "the Foo monad", when "a Foo value" would be more accurate, since I think it can confuse new users about types, values, type classes and instances. In particular it can give the false impression (reflected in your comment) that writing simple programs in Haskell (or FP in general) requires learning fancy things like monads. In reality, that's no more true than claiming that before writing a Web site in PHP you have to learn inversion-of-control containers. They're a commonly used framework/scaffolding, but there's no reason to confront them before you've written a few applications without, and hence are able to appreciate why they exist and what problems they do/don't solve.
This mostly seems to be a problem for IO and Maybe. In particular, "State monad" and "Reader monad" don't have this problem, since their values have more widely used names like pairs/tuples and functions, respectively. Likewise, I've not seen this happen with other type classes, like "it returns a Maybe functor", or "I need to combine two List monoids".
I think what I was trying to say is that not all monads are equal.
While the List or Maybe Monad or a JSON validation applicative builder brings me instant value (composition, no nulls, accumulation of error and composition again) I always scratch my head when looking at the IO monad. But maybe that's because I mostly use scala, where Futures are used a lot and they cover 90% of my IO usage.
In the case of Elm, what I dislike is that everything ends up being a Task/Side Effect/IO. Even asking for a value to a Javascript Money library. That, to me is an unacceptable tradeoff.
> Another programmer finds myObject.doExplicitThingToWorld() far more expressive and easy to understand and is very familiar with the mocking tools/practices they need to test that.
Well hopefully said programmer knows their mocking/practices will not be nearly as exhaustive and fully understand the price they pay for a little initial gain in expressiveness.
Perhaps correctness is of low enough importance it doesn't matterin their case, but I would hope an honest analysis is done.
In my experience either IO absorbs everything but the most trivial trinkets that I wouldn't ever mess up anyway, or I go so far out of my way to avoid IO that it's near impossible to keep track of all the layers of abstraction. The first case adds nothing and the second case hurts more than it helps.
That said, most of my work is "doing stuff", not data manipulation, so perhaps that's the difference. Also "OOP" for me is C#, which has plenty of FP features (though F# style records and `with` syntax are annoyingly missing); if I was comparing Haskell to Java 1.0 code then I could imagine seeing things differently.
> In my experience either IO absorbs everything but the most trivial trinkets that I wouldn't ever mess up anyway, or I go so far out of my way to avoid IO that it's near impossible to keep track of all the layers of abstraction.
I half-expected this in my Haskell static site generator real-world-learning-project-of-sorts (reading files, writing files, "calling some smarty-pants composed combinators in between", nah not really just functions all the way down.. and somehow such a simple tool grew out of all proportion soon enough with ever more powerful markup notations to support etc pp..) but was profoundly surprised just how little of the code-base ended up in the IO monadic context and how much logic ended up as purely functional modules. Pleasantly eye-opening! (Sure even the IO monad is "purely functional" at least for the programmer though not the final assembler, but y'know-what-I-mean..)
That's an interesting anecdote. I'm curious to know though, did Haskell's advanced type system help out at all? Like the various not-quite-IO monads such as State, Reader, Writer? Or other instances of HKTs such as Functor or Applicative? Did it force you to think about the problem any differently? Or was the only real benefit just the assurance that you weren't accidentally doing IO/mutation unnecessarily?
Interesting you ask, because honestly the answer is, not really. In the grand scheme of things, business logic isn't that difficult. Take some data in, process it, output it. And what Haskell provides over C#, an explicit separation of purity and impurity, isn't that great of a help. I design most of my logic to be pure anyway, and when it's not there's a reason for it. Forcing me to jump through hoops to handle those few cases of necessary impurity generally harms more than it helps.
"Difficult" code is stuff that involves things like distributed transactions and whatnot. All that stuff has to be bundled in a big wad of IO anyway and so Haskell has nothing to help out with that in the first place (if there was a language that could, I'd be all over it). At least that's my experience. YMMV.
Exactly. It doesn't document much.
In any non trivial Elm program, 2/3 of your update fonctions are going to return Tasks. So what, nothing is pure? ok.
imo, types shouldn't be used to compensate for the lack of separation of concerns (simple data manipulation VS IO)
Yeah, being a math person I jumped on the pure FP bandwagon full time for four years. But my experience has been that in anything but pet projects, it gets to the point of replacing impurity with incompatible layers of abstractions to create the illusion of some minimal level of allowed impurity and awkward buggy glue code in hopes to hold everything together. Working with an illusion of impurity on top of purity isn't any better than working with impurity directly.
Or in Greenspun's Tenth Rule Of Programming terms: Any sufficiently complicated Haskell/PureScript program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a Python VM trying to share state with an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a JS VM trying to communicate with an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a .Net VM.
"...in Greenspun's Tenth Rule Of Programming terms...slow implementation of half of a .Net VM"... ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.[1]
TL;DR: The 'impurity' of the world is an illusion created by our brains.
I have a physics degree and I completely disagree. The world IS pure.
Think of it like this: Time is the successive application of a pure function that takes the old universe as an argument and returns the new universe. All science models the real world like this. This is the foundation of mathematics. Nowhere in physics or maths do we mutate anything. Even quantum applies this perspective. This is what leads to the many worlds interpretation of quantum.
What's actually going on is your brain is such a powerful illusion machine that it has convinced 'you' that the world is composed of 'objects' that 'change'. This is entirely inside your brain. It is a side effect of the perception process inside your brain. It does not mean the real world is actually composed of 'objects' that 'change'.
This illusion is so strong that we then go on to model the real world inside computers with 'objects' that 'change'. This is a mistake that leads to no end of problems.
Once you realise the world is immutable, and that the process of time is a function, and that what we see as time is actually a succession of immutable 'universe-values', you then start modelling your problems like this. And then you realise what a better fit FP is for modelling the real world. And then you realise why science models the world using mathematics... that is functions and values... which are immutable.
When I argue this with OO developers I ask them "Is the real world mutable, or immutable?" When they answer mutable, I then ask them to go back to last Tuesday and change their lotto numbers. They can't. It's almost as if the past.... is immutable! And the present becomes the past the very moment it comes into being. And the future is unknown.
The present also cant be changed. We can affect the future by using our muscles to apply 'forces' to the 'universe-value' thereby changing the way the time-function of the universe returns the new-universe value (the future), but we cannot reach in and alter any of these immutable values directly, not the past, the present, or the future.
The world might be pure behind the scenes, but it stubbornly presents an imperative interface, and refuses to present a pure one where old values would stay accessible forever. That has major implications for computing.
What physics class do they teach all this in? I must have missed that one....
"Many worlds" is more philosophy than physics, isn't the most accepted interpretation, and even going down that path leads to lots of edge cases that could still imply impurity.
Besides, if the world is pure, and OO is part of the world, then OO must be pure too so what's the problem?
So? The universe is also full of black holes, has quantum things popping out of nothing, and is full of paradoxes. Does that mean we need to model all that into our business logic too?
Agree somewhat - I like the balance something like clojure and F# strike, immutability and high level abstractions by default but interoperability with OO and imperative code as needed - if they fit your problem domain they can be a huge productivity boost - typescript still has that C#/Java style verbosity when doing data manipulation and all the pitfalls of shared mutable state + all the pitfalls of insane JS semantics.
I do not have a single 'this' pointer in my programs. Not a single class/prototype. Imo, that leaves: Bad/no pattern matching support, many operators create statements and not expressions (e.g if/else) but they can be extracted to functions.
Also, relative path imports. This is nuts. But you can use root imports with webpack+TS and the problem is solved.
mutable objects/arrays is annoying. But we have the convention of never doing it, and it's been working for a long time. (https://github.com/AlexGalays/immupdate)
Granted, I would prefer immutable by default for sure.
Do you have other insane JS semantics in mind?
And yeah, F# looks brillant. Just wished it was not a .NET thing. Fable could interest me but it's just like bucklescript/reasonml: there is no momentum, no community, no libs. It would be a big leap of faith to heavily invest in it as an individual.
I generally like the FP approach with imperative escape hatches. Need more performance? Some code is actually more elegant in imperative form? Go for it.
F# can do it, scala can do it too (unfortunately with some crappy java compatibility concerns to deal with)
I actually think momentum behind Reason/Bucklescript is going to start building, possibly spurred on by React people. Saw this tweet recently [1] re: React bindings to Reason at Facebook. (Neat demo and interesting replies to the tweet).
>> Early preview of @reasonml bindings to ReactJS. Compiles to regular JS so interop/debugging is easy. Haven't been this excited since React!
You don't need to constrain yourself. AltJS allows users to mix and match different languages for different problems. Use a pure functional language where it makes sense for you, and don't where it doesn't.
In theory yes. In practice, just one ecosystem can be a big investment. Knowledge sharing, build tooling, libraries, be it third-party or yours.
At least that's my experience building a lot of single page apps. That could be seen as a weakness of SPAs. With fully separated pages, it's easier to mix technologies.
In addition to the options identified in the article definitely take a look at ScalaJS and ClojureScript. I've used ScalaJS quite a bit and it's a more or less flawless experience.
It compares pretty well. It is possible to define Scala bindings to JS libraries, so you can use them more or less seamlessly. Bindings to the most popular ones (jQuery, React, etc.) are already defined, and actively maintained. It is relatively simple to add your own. A nice thing is that, since Scala is a strongly-typed language, you get code-completion and refactoring "for free". However, if you prefer to use dynamic typing, ScalaJS lets you do that easily too.
Also, like the grandparent comment said, ClojureScript is a very nice language too, with powerful tooling.
The nice thing about ScalaJS and ClojureScript is that they both can run on the browser, and on a JVM backend. Scala also has an LLVM backend that is being actively developed, but on its early stages. (ScalaJS is very mature and performant)
I'd be very curious to hear about the OPs specific experience with Flow. I've found the opposite to be true of Flow vs Typescript with the added benefit that Flow is easy to integrate into an existing codebase.
> with the added benefit that Flow is easy to integrate into an existing codebase.
I converted our Node codebase to Typescript and had no problems at all. I don't know much about Flow, but what is it that makes you think it's easy comparative to Typescript?
Both are pretty trivial to integrate, because you can essentially white-list the rules you want applied, but the documentation on how to properly do this is lacking for typescript in my opinion. There is also a lot of contradictory information on best approaches when using Typescript.
From a functional programming point of view, the fact it is javascript is almost the opposite of everything you tend to appreciate about your functional language of choice. So that simply counts it out for some people. However you an still do functional programming in js, and making use of typescript and ramda is a pretty good base for doing it. (well, this is what I do anyways). I find this a much easier approach when blending all kinds of javascript libraries together than using a cross compiler .
The problem with Javascript is that it enables you many ways to screw up. Unless you already know how not to screw up, If you are a good functional programmer, like Brian Lonsdorf aka DrBoolean, then yes - you can compose some extremely mind blowing crazy stuff without shooting yourself in the foot. Languages like Clojurescript, Elm and Purescript are better simply because they enforce you specific discipline and that helps you to write arguably better code.
> I’m not even going to consider a system valid without managing IO in some way. So yes, all flavors of Lisp are off the table as well.
We're talking functional in the context of JavaScript, I think ClojureScript deserves more of an explanation on why it doesn't even qualify. That is, if you're trying to hold to the "neutral as possible" thing in the beginning.
Yep. And with that closed mindedness he cuts himself off from trying what IMHO is the best reactive front end ecosystem around today. That is one of the react based clojurescript frontend systems.
Sorry, but your amazing, functional, pragamatic system is INVALID. Because I have deemed it so! Good one!
I agree - Clojurescript is extremely pragmatic, yet quite functional. Tooling is extremely nice, FFI to existing Javascript simple, can be used on both back-end and front-end.
I've recently used Elm for a real-world internal tool and was looking over the PureScript documentation today to see whether it'd be worth looking at PureScript in the future. I was disillusioned with the boilerplate of writing JSON decoders and encoders in Elm.
This is pretty much the Elm Architecture, except fully understanding every detail there requires the reader to be conversant with some pretty hairy concepts, including free monads and the Free functor. That's not just something that's under the hood. It's built right into the user-facing API, and the user even has to tend to this free construction when writing `eval` functions by manually writing lines like `pure next` or `pure (continue value)`, which occur in the first example.
It seriously concerns me that the user will have to write lines that they couldn't truly understand without understanding free monads, because frankly nobody new to Haskell and PureScript is going to be comfortable with free monads until they've spent some serious time with the language.
I happen to be very well-versed in Haskell so this stuff is not an issue for me -- in fact the use of Free here is not only clever but kind of beautiful -- but I wouldn't really want to onboard someone onto a PureScript project who'd never used Haskell or PureScript before and didn't even know what a functor was. "Why do I have to write this `pure (continue value)` line? What does that mean?" -- "Oh boy... grab a seat and clear your schedule."
Some boilerplate for JSON decoding looks pretty tame in comparison, to be honest.
Halogen is not the only option for building web applications with PureScript. There are simpler options like Pux and Thermite, which are possibly much easier to teach. Halogen is optimized for a different use case.
Thanks! Both of those definitely look more beginner-friendly or at least easier to teach. I'm going to have to look more into PureScript in the coming days.
Can anyone explain what is meant by "managing IO" in the statement "I’m not even going to consider a system valid without managing IO in some way. So yes, all flavors of Lisp are off the table as well."? Is that like monads?
It means restricting to where you can perform IO actions, eg. printing to a log, writing to a shared area of memory, etc.
Haskell does this by representing IO actions as values, eg. "IO String" (something like "IO<String>", for people more familiar with that syntax), representing an IO action that, when later evaluated, will yield a string that you can use in subsequent actions.
Haskell's "main" (entry-point) function is a value of type "IO ()" (IO of a value representing nothing useful; you don't get anything out of it, so you just have a nothing placeholder). When a Haskell program is run, it ostensibly (hand-waving here) takes that IO value and interprets it, performing the action IO.
You have functions like putStrLn (print a line) that takes a String, and returns an "IO ()" . Accordingly, if you have
main = putStrLn "Hello, World!"
... you have a value that lines up with the expected return value of main "IO ()". More complicated examples, like
... are doing the same thing (checking the types line up), just with more moving parts (the Monad thing you mentioned, which we're using to relate the "read line" function with the "print line" function).
The key bit is that it's just "IO Blah" values standing in for IO actions, and like jigsaw pieces, these IO-returning functions only fit together where the types line up, restricting how you can use them.
Monads are tangential to this; the type-checking of that jigsaw puzzle is what matters here.
> Edit: Also, why do fewer and fewer sites have an RSS feed? :(
Bit sad, that. Somehow it happened that a critical mass of bloggers/readers moved over Google Reader while during the same period all those social-feed web apps kept gaining momentum, then Reader shut off.. things can change like that over a decade or so. A 20-year-old today was perhaps 7-10 when XML feeds were hot currency. I'm still content with my numerous working subscribed feeds in Thunderbird though, can't catch up as it is.
It was briefly mentioned in another comment, but was written off for it's lack of momentum, community, and libraries.
I'll respond to these important concerns one by one:
Momentum: The fable project began last year. There are still active discussions about tweaks getting it to v1.0, but already there is enough there to make complete applications. There isn't hackernews/reddit hype, but the gitter is busy and there are some using it in production already.
Community: I assume that the concern with the community is that it isn't large. F# has never had as large of a community in any of its incarnations compared to mainstream OO-first languages, but the community has always been there to help me out and I've never gone more than a day or two waiting on an answer to a library/language question (most fable related questions are answered very quickly on gitter). The latter fact alone makes it more impressive to me than some other communities. Community is also important because of the amount and variety of libraries that are created for a language. I'd argue that this isn't as important for transpiled languages as it is for separate compiled languages, where for example a ruby library can't be used in python, but I can use any javascript library in fable.
Libraries: Library usage is interesting when talking about transpiled languages, and mostly depends on the Dynamic and Foreign interfaces available. Because of Fable's dynamic abilities, I can dynamically call any javascript code I need to. I use this for a couple of libraries in the React-Native application I'm building using Exponent. The other method is Foreign interfaces, which is fantastic in fable because of how the F# type system (and inference) works in the first place. There is a utility to convert from typescript definitions to fable definitions (some will need tweaking afterwards for greater type safety) which adds a ton of libraries itself. I'd argue because of that fact that Fable has more libraries than any other functional-first transpile-to-js language!
I'm using a setup like this on a current project deployed with docker-compose to digitalocean.
Full-stack F# has been nice to work with so far.
One really nice experience I had was being able to reuse viewmodels and their validators.
Android/iOS -> Exponent
Web -> Fable-Elmish
Server -> Suave
I had viewmodels and partial validation functions which could sit in a shared folder and be referenced by all of the projects.
I change it in one place, and I can update all 3 instantly.
All of the rest calls depend on the types definition, so I didn't have to do anything after adding a new property to the type and saving the document.
The server reloaded, the frontend reloaded, the mobile app reloaded.
I'm not sure If I'll keep it that way, as coordinating production updates is something I haven't fully figured out yet, but for now it's neat to explore.
Would I recommend someone who doesn't know functional programming to switch their applications to fable? No.
Would I recommend fable to someone familiar with functional programming looking for a better way to manage their ui? Yes.
Koka https://github.com/koka-lang/koka is a very interesting research language that focuses on explicitly specifying effects in types. It even deconstructs Haskell's notion of purity into "throws exceptions" + "may never terminate". Unfortunately, the code generation (which BTW exists for both JS and C#) is uh… not very good.
But honestly, I haven't felt the need to use a functional language in frontend development. JavaScript (ES6) is good enough, and it's actually supported in browsers, so you can develop without any compiling, and only use Babel to make production builds. If the project is not small, use TypeScript to add some type safety.